Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ dist
*.tgz
*.bun-build

# staged Agent Inspector SPA (copied from @aws/agent-inspector at build time)
src/assets/agent-inspector/

# code coverage
coverage
*.lcov
Expand Down
735 changes: 734 additions & 1 deletion bun.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@aws-sdk/client-cloudwatch-logs": "^3.1092.0",
"@aws-sdk/client-iam": "^3.1080.0",
"@aws-sdk/client-sts": "^3.1092.0",
"@aws/agent-inspector": "0.6.1",
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/exporter-metrics-otlp-http": "^0.221.0",
"@opentelemetry/otlp-transformer": "0.213.0",
Expand Down
25 changes: 25 additions & 0 deletions scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,29 @@ function discoverAssets(): string[] {
return files.sort().map((relativePath) => join(ASSETS_DIR, relativePath));
}

/**
* Stage the prebuilt Agent Inspector SPA into the asset tree (gitignored) so
* both distribution paths ship it through the ordinary asset machinery:
* `bundle` mirrors it into dist/assets/, `compile` embeds it in the binary.
* Every file gains a neutral `.asset` suffix — compile passes assets as
* Bun.build entrypoints, and a bare .html entrypoint would be bundled through
* Bun's HTML-imports pipeline instead of embedded verbatim. InspectorAssets
* strips the suffix when reading.
*/
async function stageInspectorAssets(): Promise<void> {
const source = join(REPO_ROOT, "node_modules", "@aws", "agent-inspector", "dist-assets");
if (!(await Bun.file(join(source, "index.html")).exists())) {
throw new Error("@aws/agent-inspector is not installed — run `bun install` before building.");
}
const target = join(ASSETS_DIR, "agent-inspector");
await $`rm -rf ${target}`;
await $`mkdir -p ${target}`;
const files = [...new Bun.Glob("**/*").scanSync({ cwd: source, onlyFiles: true })];
for (const file of files) {
await $`cp ${join(source, file)} ${join(target, `${file}.asset`)}`;
}
}

/** Force asset files through the file loader so template .ts/.js are embedded as bytes, not compiled. */
function assetLoaderPlugin(): Bun.BunPlugin {
return {
Expand Down Expand Up @@ -89,6 +112,7 @@ async function assertAssetsAreText(assets: string[]): Promise<void> {
// Bun.build rejects with an AggregateError on failure (throw defaults to true),
// so build errors propagate to runWithExitCode like any other.
async function bundle(): Promise<void> {
await stageInspectorAssets();
await Bun.build({
entrypoints: [ENTRYPOINT],
outdir: DIST,
Expand All @@ -105,6 +129,7 @@ async function bundle(): Promise<void> {
}

async function compile(target: string): Promise<void> {
await stageInspectorAssets();
const assets = discoverAssets();
await assertAssetsAreText(assets);

Expand Down
53 changes: 53 additions & 0 deletions src/core/dev/inspector/respond.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/** Small response and parsing helpers shared by the Inspector route modules. */
import type { HttpResponse } from "../../../io/httpServer";

export function json(status: number, body: unknown): HttpResponse {
return {
status,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
};
}

export function apiError(status: number, error: string): HttpResponse {
return json(status, { success: false, error });
}

/** Parse a JSON request body, or undefined when it is not valid JSON. */
export function parseJsonBody(body: Buffer): Record<string, unknown> | undefined {
try {
const parsed: unknown = JSON.parse(body.toString());
if (parsed && typeof parsed === "object") return parsed as Record<string, unknown>;
} catch {
// fall through
}
return undefined;
}

/**
* Parse an optional epoch-milliseconds query parameter. Returns the value (or
* undefined when absent) or an error response matching the reference wording.
*/
export function parseTimeParam(
url: URL,
name: string,
): { value: number | undefined; error?: HttpResponse } {
const raw = url.searchParams.get(name);
if (raw === null) return { value: undefined };
const value = Number(raw);
if (Number.isNaN(value)) {
return {
value: undefined,
error: apiError(400, `${name} must be a number (epoch milliseconds)`),
};
}
return { value };
}

export function asString(value: unknown): string | undefined {
return typeof value === "string" ? value : undefined;
}

export function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
Loading
Loading