Skip to content
Open
11 changes: 11 additions & 0 deletions .changeset/dev-inspect-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@agent-native/core": minor
---

`agent-native dev --inspect` (and `--inspect-brk`, optionally `=<port>`) 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.
67 changes: 64 additions & 3 deletions packages/core/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>;
};
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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/templates/default/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion templates/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion templates/brain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion templates/calendar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion templates/clips/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion templates/content/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion templates/design/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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", () => ({
Expand All @@ -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([
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { getOrgContext } from "@agent-native/core/org";
import {
FeatureNotConfiguredError,
getSession,
runWithRequestContext,
startBuilderDesignSystemIndex,
} from "@agent-native/core/server";
import {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion templates/dispatch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion templates/forms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 .",
Expand Down
2 changes: 1 addition & 1 deletion templates/macros/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion templates/mail/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion templates/slides/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Loading