diff --git a/.changeset/dev-inspect-flag.md b/.changeset/dev-inspect-flag.md new file mode 100644 index 0000000000..332e928513 --- /dev/null +++ b/.changeset/dev-inspect-flag.md @@ -0,0 +1,11 @@ +--- +"@agent-native/core": minor +--- + +`agent-native dev --inspect` (and `--inspect-brk`, optionally `=`) now +attaches the Node inspector to **only** the Nitro API-server process, on a +single known port (default 9229). It selects Nitro's `node-process` dev runner +so the server is a real, attachable process, and injects `NODE_OPTIONS` through +a Vite preload that runs before Vite's own startup — so Vite, pnpm, and the CLI +are never inspected and there is exactly one debugger target. Set +`NITRO_DEV_RUNNER` yourself to override the runner. diff --git a/packages/core/src/cli/index.ts b/packages/core/src/cli/index.ts index e06e640bbd..7aa9e0d3aa 100644 --- a/packages/core/src/cli/index.ts +++ b/packages/core/src/cli/index.ts @@ -276,6 +276,22 @@ function findViteBin(): string { return findBinUpwards("vite") ?? "vite"; } +function findViteJsEntry(): string | null { + try { + const require = createRequire(path.join(process.cwd(), "package.json")); + const pkgJsonPath = require.resolve("vite/package.json"); + const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8")) as { + bin?: string | Record; + }; + const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.vite; + if (!rel) return null; + const entry = path.join(path.dirname(pkgJsonPath), rel); + return fs.existsSync(entry) ? entry : null; + } catch { + return null; + } +} + function findTsxBin(): string { const localTsx = path.resolve("node_modules/.bin/tsx"); if (fs.existsSync(localTsx)) return localTsx; @@ -386,15 +402,31 @@ function isWorkspaceRoot(): boolean { } } +function extractNodeInspectFlag(args: string[]): { + inspectFlag: string | null; + rest: string[]; +} { + const rest: string[] = []; + let inspectFlag: string | null = null; + for (const arg of args) { + if (/^--inspect(-brk)?(=.+)?$/.test(arg)) { + inspectFlag = arg; + continue; + } + rest.push(arg); + } + return { inspectFlag, rest }; +} + function run( cmd: string, cmdArgs: string[], - opts?: { stdio?: "inherit" | "pipe" }, + opts?: { stdio?: "inherit" | "pipe"; env?: NodeJS.ProcessEnv }, ) { const child = spawn(cmd, cmdArgs, { stdio: opts?.stdio ?? "inherit", shell: process.platform === "win32", - env: process.env, + env: opts?.env ?? process.env, }); child.on("exit", (code) => process.exit(code ?? 0)); // Forward signals to child so Cmd+C doesn't leave zombie processes holding ports @@ -545,7 +577,36 @@ switch (command) { break; } const vite = findViteBin(); - run(vite, args); + const { inspectFlag, rest } = extractNodeInspectFlag(args); + if (!inspectFlag) { + run(vite, rest); + break; + } + const viteJsEntry = findViteJsEntry(); + if (!viteJsEntry) { + console.warn( + "[agent-native] Could not resolve Vite's JS entry; starting dev " + + "server without the debugger.", + ); + run(vite, rest); + break; + } + // Attach inspect flag to server process (not Vite or Nitro process) + const parsed = inspectFlag.match(/^--(inspect(?:-brk)?)(?:=(.+))?$/); + const kind = parsed?.[1] ?? "inspect"; + const target = parsed?.[2] ?? "9229"; + const directive = `--${kind}=${target}`; + const preload = + "data:text/javascript," + + encodeURIComponent( + `process.env.NODE_OPTIONS=((process.env.NODE_OPTIONS??"")+" ${directive}").trim();`, + ); + const env = { + ...process.env, + NITRO_DEV_RUNNER: process.env.NITRO_DEV_RUNNER ?? "node-process", + }; + console.log(`[agent-native] API server debugger listening on ${target}`); + run(process.execPath, ["--import", preload, viteJsEntry, ...rest], { env }); break; } diff --git a/packages/core/src/templates/default/package.json b/packages/core/src/templates/default/package.json index ea502b390d..68adf22aac 100644 --- a/packages/core/src/templates/default/package.json +++ b/packages/core/src/templates/default/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "typecheck": "agent-native typecheck", diff --git a/templates/assets/package.json b/templates/assets/package.json index dc169d1d3d..0d1e188d02 100644 --- a/templates/assets/package.json +++ b/templates/assets/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "test": "vitest --run", diff --git a/templates/brain/package.json b/templates/brain/package.json index b14b4fa674..0cf647d0cf 100644 --- a/templates/brain/package.json +++ b/templates/brain/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "test": "vitest --run --config vitest.config.ts", diff --git a/templates/calendar/package.json b/templates/calendar/package.json index aecaaa40fa..69c4369e60 100644 --- a/templates/calendar/package.json +++ b/templates/calendar/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "test": "vitest --run", diff --git a/templates/clips/package.json b/templates/clips/package.json index 8af9abd568..f89223487b 100644 --- a/templates/clips/package.json +++ b/templates/clips/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "typecheck": "agent-native typecheck", diff --git a/templates/content/package.json b/templates/content/package.json index 4aa5f45f29..e41368ded3 100644 --- a/templates/content/package.json +++ b/templates/content/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "dev:database": "node scripts/check-native-deps.mjs && node scripts/dev-database.mjs", "build": "agent-native build", "start": "agent-native start", diff --git a/templates/design/package.json b/templates/design/package.json index 9f7ef4ce10..5a4d01920a 100644 --- a/templates/design/package.json +++ b/templates/design/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "test": "vitest --run", diff --git a/templates/design/server/handlers/index-design-system-with-builder.test.ts b/templates/design/server/handlers/index-design-system-with-builder.test.ts index a16dfdc3a9..df087eb36c 100644 --- a/templates/design/server/handlers/index-design-system-with-builder.test.ts +++ b/templates/design/server/handlers/index-design-system-with-builder.test.ts @@ -1,18 +1,25 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ + getOrgContext: vi.fn(), getRequestHeader: vi.fn(), getSession: vi.fn(), readMultipartFormData: vi.fn(), setResponseStatus: vi.fn(), startBuilderDesignSystemIndex: vi.fn(), upsertBuilderProxyDesignSystem: vi.fn(), + runWithRequestContext: vi.fn((_ctx, fn) => fn()), +})); + +vi.mock("@agent-native/core/org", () => ({ + getOrgContext: mocks.getOrgContext, })); vi.mock("@agent-native/core/server", () => ({ FeatureNotConfiguredError: class FeatureNotConfiguredError extends Error {}, getSession: mocks.getSession, startBuilderDesignSystemIndex: mocks.startBuilderDesignSystemIndex, + runWithRequestContext: mocks.runWithRequestContext, })); vi.mock("h3", () => ({ @@ -35,6 +42,7 @@ describe("Builder .fig multipart preflight", () => { email: "designer@example.com", orgId: null, }); + mocks.getOrgContext.mockResolvedValue({ orgId: "org-1" }); mocks.getRequestHeader.mockReturnValue("1024"); mocks.readMultipartFormData.mockResolvedValue([ { diff --git a/templates/design/server/handlers/index-design-system-with-builder.ts b/templates/design/server/handlers/index-design-system-with-builder.ts index 5ae12784cb..61e5a2c16f 100644 --- a/templates/design/server/handlers/index-design-system-with-builder.ts +++ b/templates/design/server/handlers/index-design-system-with-builder.ts @@ -1,6 +1,8 @@ +import { getOrgContext } from "@agent-native/core/org"; import { FeatureNotConfiguredError, getSession, + runWithRequestContext, startBuilderDesignSystemIndex, } from "@agent-native/core/server"; import { @@ -89,28 +91,43 @@ export const indexDesignSystemWithBuilder = defineEventHandler( .replace(/[-_]+/g, " ") .trim() || "Imported brand"; + // `session.orgId` is set at sign-in and not refreshed when the user + // switches orgs; `getOrgContext` resolves the live active-org setting. + let orgId: string | undefined; try { - const result = await startBuilderDesignSystemIndex({ - projectName: suggestedTitle, - files: [ - { - name: filename, - data: part.data, - mimeType: "application/octet-stream", - }, - ], - }); - const proxy = await upsertBuilderProxyDesignSystem({ - result, - ownerEmail: session.email, - orgId: session.orgId ?? null, - projectName: suggestedTitle, - }); - return { - ...result, - ...proxy, - uploadedFileCount: 1, - }; + orgId = (await getOrgContext(event)).orgId ?? undefined; + } catch { + // Org tables can be unavailable during first boot; fall back below. + } + orgId ??= session.orgId ?? undefined; + + try { + return await runWithRequestContext( + { userEmail: session.email, orgId }, + async () => { + const result = await startBuilderDesignSystemIndex({ + projectName: suggestedTitle, + files: [ + { + name: filename, + data: part.data, + mimeType: "application/octet-stream", + }, + ], + }); + const proxy = await upsertBuilderProxyDesignSystem({ + result, + ownerEmail: session.email, + orgId: orgId ?? null, + projectName: suggestedTitle, + }); + return { + ...result, + ...proxy, + uploadedFileCount: 1, + }; + }, + ); } catch (err) { if (err instanceof FeatureNotConfiguredError) { setResponseStatus(event, 412); diff --git a/templates/dispatch/package.json b/templates/dispatch/package.json index 397744d493..57c660f7cf 100644 --- a/templates/dispatch/package.json +++ b/templates/dispatch/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "typecheck": "agent-native typecheck", diff --git a/templates/forms/package.json b/templates/forms/package.json index 332404ed27..ee63c47829 100644 --- a/templates/forms/package.json +++ b/templates/forms/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "format.fix": "oxfmt --write .", diff --git a/templates/macros/package.json b/templates/macros/package.json index a4c4cb3188..f0ad991fd4 100644 --- a/templates/macros/package.json +++ b/templates/macros/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "test": "vitest --run --passWithNoTests", diff --git a/templates/mail/package.json b/templates/mail/package.json index 431bef10d5..8b20f82f57 100644 --- a/templates/mail/package.json +++ b/templates/mail/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "test": "vitest --run", diff --git a/templates/slides/package.json b/templates/slides/package.json index 89d6548962..b2c5dcad5d 100644 --- a/templates/slides/package.json +++ b/templates/slides/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "agent-native dev", + "dev": "agent-native dev --inspect", "build": "agent-native build", "start": "agent-native start", "test": "vitest --run", diff --git a/templates/slides/server/handlers/index-design-system-with-builder.test.ts b/templates/slides/server/handlers/index-design-system-with-builder.test.ts index dac59d270b..9344a54113 100644 --- a/templates/slides/server/handlers/index-design-system-with-builder.test.ts +++ b/templates/slides/server/handlers/index-design-system-with-builder.test.ts @@ -39,6 +39,16 @@ vi.mock("../lib/builder-design-system-proxy.js", () => ({ mockUpsertBuilderProxyDesignSystem(...args), })); +vi.mock("./request-auth-context.js", () => ({ + withSlidesRequestContext: async ( + event: unknown, + fn: (ctx: { email?: string; orgId?: string }) => unknown, + ) => { + const session = await mockGetSession(event); + return fn({ email: session?.email, orgId: session?.orgId }); + }, +})); + import { indexDesignSystemWithBuilder } from "./index-design-system-with-builder"; describe("indexDesignSystemWithBuilder", () => { diff --git a/templates/slides/server/handlers/index-design-system-with-builder.ts b/templates/slides/server/handlers/index-design-system-with-builder.ts index e99175ab62..5411b5cceb 100644 --- a/templates/slides/server/handlers/index-design-system-with-builder.ts +++ b/templates/slides/server/handlers/index-design-system-with-builder.ts @@ -11,6 +11,7 @@ import { } from "h3"; import { upsertBuilderProxyDesignSystem } from "../lib/builder-design-system-proxy.js"; +import { withSlidesRequestContext } from "./request-auth-context.js"; const MAX_FIG_BYTES = 200 * 1024 * 1024; const MULTIPART_OVERHEAD_BYTES = 1024 * 1024; @@ -72,27 +73,29 @@ export const indexDesignSystemWithBuilder = defineEventHandler( .trim() || "Imported brand"; try { - const result = await startBuilderDesignSystemIndex({ - projectName: suggestedTitle, - files: [ - { - name: part.filename || "brand.fig", - data: part.data, - mimeType: "application/octet-stream", - }, - ], - }); - const proxy = await upsertBuilderProxyDesignSystem({ - result, - ownerEmail: session.email, - orgId: session.orgId ?? null, - projectName: suggestedTitle, + return await withSlidesRequestContext(event, async ({ orgId }) => { + const result = await startBuilderDesignSystemIndex({ + projectName: suggestedTitle, + files: [ + { + name: part.filename || "brand.fig", + data: part.data, + mimeType: "application/octet-stream", + }, + ], + }); + const proxy = await upsertBuilderProxyDesignSystem({ + result, + ownerEmail: session.email, + orgId: orgId ?? null, + projectName: suggestedTitle, + }); + return { + ...result, + ...proxy, + uploadedFileCount: 1, + }; }); - return { - ...result, - ...proxy, - uploadedFileCount: 1, - }; } catch (err) { if (err instanceof FeatureNotConfiguredError) { setResponseStatus(event, 412);