@beignet/cli 0.0.26 → 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +29 -10
- package/dist/choices.d.ts +8 -0
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +14 -0
- package/dist/choices.js.map +1 -1
- package/dist/db.js +2 -2
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +109 -13
- package/dist/index.js.map +1 -1
- package/dist/inspect.js +51 -3
- package/dist/inspect.js.map +1 -1
- package/dist/make/inbox.d.ts +63 -0
- package/dist/make/inbox.d.ts.map +1 -0
- package/dist/make/inbox.js +1747 -0
- package/dist/make/inbox.js.map +1 -0
- package/dist/make/payments.d.ts +61 -0
- package/dist/make/payments.d.ts.map +1 -0
- package/dist/make/payments.js +1900 -0
- package/dist/make/payments.js.map +1 -0
- package/dist/make/shared.d.ts +575 -0
- package/dist/make/shared.d.ts.map +1 -0
- package/dist/make/shared.js +2166 -0
- package/dist/make/shared.js.map +1 -0
- package/dist/make/tenancy.d.ts +101 -0
- package/dist/make/tenancy.d.ts.map +1 -0
- package/dist/make/tenancy.js +4409 -0
- package/dist/make/tenancy.js.map +1 -0
- package/dist/make.d.ts +8 -44
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +71 -3659
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +44 -3
- package/dist/mcp.js.map +1 -1
- package/dist/provider-add.d.ts +19 -0
- package/dist/provider-add.d.ts.map +1 -0
- package/dist/provider-add.js +691 -0
- package/dist/provider-add.js.map +1 -0
- package/dist/registry-edits.d.ts +9 -0
- package/dist/registry-edits.d.ts.map +1 -1
- package/dist/registry-edits.js +32 -0
- package/dist/registry-edits.js.map +1 -1
- package/dist/task.d.ts +2 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +2 -0
- package/dist/task.js.map +1 -1
- package/dist/templates/agents.js +2 -2
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +44 -37
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +5 -0
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +5 -0
- package/dist/templates/shared.js.map +1 -1
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +1 -1
- package/src/choices.ts +28 -0
- package/src/db.ts +2 -2
- package/src/index.ts +170 -13
- package/src/inspect.ts +68 -2
- package/src/make/inbox.ts +2015 -0
- package/src/make/payments.ts +2182 -0
- package/src/make/shared.ts +3550 -0
- package/src/make/tenancy.ts +4809 -0
- package/src/make.ts +380 -5289
- package/src/mcp.ts +65 -2
- package/src/provider-add.ts +926 -0
- package/src/registry-edits.ts +47 -0
- package/src/task.ts +8 -10
- package/src/templates/agents.ts +2 -2
- package/src/templates/server.ts +44 -37
- package/src/templates/shared.ts +5 -0
- package/src/test-helpers/generated-app.ts +168 -0
package/src/registry-edits.ts
CHANGED
|
@@ -94,6 +94,53 @@ export function insertAfterImports(source: string, line: string): string {
|
|
|
94
94
|
return lines.join("\n");
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Add a name to an `import { ... } from "<module>";` value import, creating
|
|
99
|
+
* the import when the module is not imported yet. Names are inserted in
|
|
100
|
+
* sorted position and single-line versus multiline formatting is preserved.
|
|
101
|
+
*
|
|
102
|
+
* Mirrors the `addNamedTypeImport` helper for value imports; type-only
|
|
103
|
+
* imports (`import type { ... }`) from the same module are left untouched.
|
|
104
|
+
*/
|
|
105
|
+
export function addNamedValueImport(
|
|
106
|
+
source: string,
|
|
107
|
+
name: string,
|
|
108
|
+
moduleName: string,
|
|
109
|
+
): string {
|
|
110
|
+
const escapedModule = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
111
|
+
const importPattern = new RegExp(
|
|
112
|
+
`import \\{([^}]*)\\} from "${escapedModule}";`,
|
|
113
|
+
);
|
|
114
|
+
const match = importPattern.exec(source);
|
|
115
|
+
if (!match) {
|
|
116
|
+
return insertAfterImports(
|
|
117
|
+
source,
|
|
118
|
+
`import { ${name} } from "${moduleName}";`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const names = match[1]
|
|
123
|
+
.split(",")
|
|
124
|
+
.map((entry) => entry.trim())
|
|
125
|
+
.filter(Boolean);
|
|
126
|
+
if (names.includes(name)) return source;
|
|
127
|
+
|
|
128
|
+
const insertIndex = names.findIndex(
|
|
129
|
+
(existing) => existing.localeCompare(name) > 0,
|
|
130
|
+
);
|
|
131
|
+
if (insertIndex === -1) names.push(name);
|
|
132
|
+
else names.splice(insertIndex, 0, name);
|
|
133
|
+
|
|
134
|
+
const indent = match[1].match(/\n([\t ]+)/)?.[1] ?? "\t";
|
|
135
|
+
const replacement = match[1].includes("\n")
|
|
136
|
+
? `import {\n${indent}${names.join(`,\n${indent}`)},\n} from "${moduleName}";`
|
|
137
|
+
: `import { ${names.join(", ")} } from "${moduleName}";`;
|
|
138
|
+
|
|
139
|
+
return `${source.slice(0, match.index)}${replacement}${source.slice(
|
|
140
|
+
match.index + match[0].length,
|
|
141
|
+
)}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
97
144
|
/**
|
|
98
145
|
* Collect every identifier referenced inside an array (or object) expression.
|
|
99
146
|
*/
|
package/src/task.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import type { TaskDef } from "@beignet/core/tasks";
|
|
3
|
+
import type { TaskDef, TaskRunContextArgs } from "@beignet/core/tasks";
|
|
4
4
|
import { createJiti } from "jiti";
|
|
5
5
|
import { loadBeignetConfig, normalizePath } from "./config.js";
|
|
6
6
|
|
|
@@ -11,6 +11,7 @@ export type RunAppTaskOptions = {
|
|
|
11
11
|
name: string;
|
|
12
12
|
cwd?: string;
|
|
13
13
|
input?: string | Record<string, unknown>;
|
|
14
|
+
tenant?: string;
|
|
14
15
|
modulePath?: string;
|
|
15
16
|
};
|
|
16
17
|
|
|
@@ -23,20 +24,15 @@ export type RunAppTaskResult = {
|
|
|
23
24
|
cwd: string;
|
|
24
25
|
modulePath: string;
|
|
25
26
|
input: unknown;
|
|
27
|
+
tenant?: string;
|
|
26
28
|
output: unknown;
|
|
27
29
|
durationMs: number;
|
|
28
30
|
};
|
|
29
31
|
|
|
30
|
-
type TaskContextArgs = {
|
|
31
|
-
task: TaskDef;
|
|
32
|
-
taskName: string;
|
|
33
|
-
input: unknown;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
32
|
type TaskModule = {
|
|
37
33
|
tasks?: readonly unknown[];
|
|
38
|
-
createTaskContext?: (args:
|
|
39
|
-
stopTaskContext?: (ctx: unknown, args:
|
|
34
|
+
createTaskContext?: (args: TaskRunContextArgs) => unknown;
|
|
35
|
+
stopTaskContext?: (ctx: unknown, args: TaskRunContextArgs) => unknown;
|
|
40
36
|
};
|
|
41
37
|
|
|
42
38
|
/**
|
|
@@ -56,10 +52,11 @@ export async function runAppTask(
|
|
|
56
52
|
// `beignet lint`, work without a built @beignet/core.
|
|
57
53
|
const { parseTaskInput } = await import("@beignet/core/tasks");
|
|
58
54
|
const parsedInput = await parseTaskInput(task, rawInput);
|
|
59
|
-
const contextArgs = {
|
|
55
|
+
const contextArgs: TaskRunContextArgs = {
|
|
60
56
|
task,
|
|
61
57
|
taskName: task.name,
|
|
62
58
|
input: parsedInput,
|
|
59
|
+
tenant: options.tenant,
|
|
63
60
|
};
|
|
64
61
|
const ctx = taskModule.createTaskContext
|
|
65
62
|
? await taskModule.createTaskContext(contextArgs)
|
|
@@ -78,6 +75,7 @@ export async function runAppTask(
|
|
|
78
75
|
cwd,
|
|
79
76
|
modulePath,
|
|
80
77
|
input: parsedInput,
|
|
78
|
+
...(options.tenant !== undefined ? { tenant: options.tenant } : {}),
|
|
81
79
|
output,
|
|
82
80
|
durationMs: Math.round(performance.now() - startedAt),
|
|
83
81
|
};
|
package/src/templates/agents.ts
CHANGED
|
@@ -118,9 +118,9 @@ skill-loading block.
|
|
|
118
118
|
## MCP server
|
|
119
119
|
|
|
120
120
|
\`.mcp.json\` registers the app-local \`@beignet/cli\` bin at
|
|
121
|
-
\`./node_modules/.bin/beignet mcp\`, which exposes routes/doctor/lint/make as
|
|
121
|
+
\`./node_modules/.bin/beignet mcp\`, which exposes routes/doctor/lint/make/provider_add as
|
|
122
122
|
structured tools named exactly: \`routes\`, \`doctor\`, \`doctor_fix\`,
|
|
123
|
-
\`lint\`, \`make\`. Clients that do not read \`.mcp.json\` can use the same
|
|
123
|
+
\`lint\`, \`make\`, \`provider_add\`. Clients that do not read \`.mcp.json\` can use the same
|
|
124
124
|
command from the app root; use \`${cli} mcp\` only for terminal debugging.
|
|
125
125
|
`;
|
|
126
126
|
}
|
package/src/templates/server.ts
CHANGED
|
@@ -296,16 +296,18 @@ export function server(ctx: TemplateContext): string {
|
|
|
296
296
|
\t\t},`;
|
|
297
297
|
|
|
298
298
|
return `import "@beignet/core/server-only";
|
|
299
|
-
import { createNextServer } from "@beignet/next";
|
|
299
|
+
import { createNextServer, createNextServerLoader } from "@beignet/next";
|
|
300
300
|
import { createErrorReportingHooks, createIdempotencyHooks } from "@beignet/core/server";
|
|
301
301
|
import type { AppContext } from "@/app-context";
|
|
302
302
|
import { appPorts } from "@/infra/app-ports";
|
|
303
303
|
import { env } from "@/lib/env";
|
|
304
304
|
import { appContext } from "./context";
|
|
305
|
-
import { providers } from "./providers";
|
|
306
305
|
import { routes } from "./routes";
|
|
307
306
|
|
|
308
|
-
export const
|
|
307
|
+
export const getServer = createNextServerLoader(async () => {
|
|
308
|
+
const { providers } = await import("./providers");
|
|
309
|
+
|
|
310
|
+
return createNextServer({
|
|
309
311
|
ports: appPorts,
|
|
310
312
|
providers,
|
|
311
313
|
providerConfig: {
|
|
@@ -333,6 +335,7 @@ ${providerConfigEntries}
|
|
|
333
335
|
},
|
|
334
336
|
};
|
|
335
337
|
},
|
|
338
|
+
});
|
|
336
339
|
});
|
|
337
340
|
`;
|
|
338
341
|
}
|
|
@@ -557,15 +560,11 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
557
560
|
return typeof value === "object" && value !== null;
|
|
558
561
|
}
|
|
559
562
|
`,
|
|
560
|
-
apiCatchAllRoute: `import {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
export const HEAD =
|
|
564
|
-
|
|
565
|
-
export const PATCH = server.api;
|
|
566
|
-
export const POST = server.api;
|
|
567
|
-
export const PUT = server.api;
|
|
568
|
-
export const DELETE = server.api;
|
|
563
|
+
apiCatchAllRoute: `import { createApiRoute } from "@beignet/next";
|
|
564
|
+
import { getServer } from "@/server";
|
|
565
|
+
|
|
566
|
+
export const { DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT } =
|
|
567
|
+
createApiRoute(getServer);
|
|
569
568
|
`,
|
|
570
569
|
healthRoute: `import { env } from "@/lib/env";
|
|
571
570
|
|
|
@@ -584,12 +583,12 @@ export function GET() {
|
|
|
584
583
|
);
|
|
585
584
|
}
|
|
586
585
|
`,
|
|
587
|
-
readyRoute: `import {
|
|
586
|
+
readyRoute: `import { createHealthRoute } from "@beignet/next";
|
|
588
587
|
import { env } from "@/lib/env";
|
|
589
|
-
import {
|
|
588
|
+
import { getServer } from "@/server";
|
|
590
589
|
|
|
591
|
-
const
|
|
592
|
-
|
|
590
|
+
export const { GET } = createHealthRoute(
|
|
591
|
+
getServer,
|
|
593
592
|
{
|
|
594
593
|
checks: {
|
|
595
594
|
database: (ports) => ports.db.checkHealth(),
|
|
@@ -598,42 +597,50 @@ const readiness = createHealthHandler(
|
|
|
598
597
|
},
|
|
599
598
|
env.NODE_ENV,
|
|
600
599
|
);
|
|
601
|
-
|
|
602
|
-
export async function GET(request: Request) {
|
|
603
|
-
const response = await readiness(request);
|
|
604
|
-
|
|
605
|
-
return Response.json(response.body, {
|
|
606
|
-
status: response.status,
|
|
607
|
-
headers: {
|
|
608
|
-
...response.headers,
|
|
609
|
-
"cache-control": "no-store",
|
|
610
|
-
},
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
600
|
`,
|
|
614
601
|
devtoolsRoute: `import { createDevtoolsRoute } from "@beignet/devtools";
|
|
615
602
|
import { env } from "@/lib/env";
|
|
616
|
-
import {
|
|
603
|
+
import { getServer } from "@/server";
|
|
617
604
|
|
|
618
|
-
export const { GET, POST } = createDevtoolsRoute(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
605
|
+
export const { GET, POST } = createDevtoolsRoute(
|
|
606
|
+
async () => (await getServer()).ports.devtools,
|
|
607
|
+
{
|
|
608
|
+
basePath: "/api/devtools",
|
|
609
|
+
enabled: env.DEVTOOLS_ENABLED,
|
|
610
|
+
},
|
|
611
|
+
);
|
|
622
612
|
`,
|
|
623
613
|
openApiRoute: `import { createOpenAPIHandler } from "@beignet/next";
|
|
624
614
|
import { env } from "@/lib/env";
|
|
625
|
-
import {
|
|
615
|
+
import { contracts } from "@/server/routes";
|
|
626
616
|
|
|
627
|
-
export const GET = createOpenAPIHandler(
|
|
617
|
+
export const GET = createOpenAPIHandler(contracts, {
|
|
628
618
|
title: "Beignet starter API",
|
|
629
619
|
version: "0.1.0",
|
|
630
620
|
servers: [{ url: env.APP_URL }],
|
|
631
621
|
});
|
|
632
622
|
`,
|
|
633
623
|
authRoute: `import { toNextJsHandler } from "better-auth/next-js";
|
|
634
|
-
import { auth } from "@/lib/better-auth";
|
|
635
624
|
|
|
636
|
-
|
|
625
|
+
type AuthRouteMethod = "GET" | "POST";
|
|
626
|
+
|
|
627
|
+
async function handleAuthRoute(
|
|
628
|
+
method: AuthRouteMethod,
|
|
629
|
+
request: Request,
|
|
630
|
+
): Promise<Response> {
|
|
631
|
+
const { auth } = await import("@/lib/better-auth");
|
|
632
|
+
const handlers = toNextJsHandler(auth);
|
|
633
|
+
|
|
634
|
+
return handlers[method](request);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export function GET(request: Request): Promise<Response> {
|
|
638
|
+
return handleAuthRoute("GET", request);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export function POST(request: Request): Promise<Response> {
|
|
642
|
+
return handleAuthRoute("POST", request);
|
|
643
|
+
}
|
|
637
644
|
`,
|
|
638
645
|
// API-only scaffold frontend: a minimal layout and landing page that point
|
|
639
646
|
// at the contract-backed API surface, plus a typed client without React
|
package/src/templates/shared.ts
CHANGED
|
@@ -54,7 +54,12 @@ export const externalVersions = {
|
|
|
54
54
|
pglite: "^0.5.2",
|
|
55
55
|
pino: "^9.7.0",
|
|
56
56
|
resend: "^6.14.0",
|
|
57
|
+
sentryNode: "^10.58.0",
|
|
58
|
+
nodemailer: "^8.0.5",
|
|
59
|
+
openFeatureServerSdk: "^1.22.0",
|
|
57
60
|
typesPg: "^8.11.0",
|
|
61
|
+
ioredis: "^5.0.0",
|
|
62
|
+
awsSdkS3: "^3.955.0",
|
|
58
63
|
upstashRateLimit: "^2.0.0",
|
|
59
64
|
upstashRedis: "^1.0.0",
|
|
60
65
|
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { expect } from "bun:test";
|
|
2
|
+
import { access, mkdir, symlink } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Test helpers for typechecking and running generated apps inside the CLI
|
|
7
|
+
* test suite. Shared by make.test.ts, make-tenancy.test.ts, and
|
|
8
|
+
* make-inbox.test.ts; excluded from the published build.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const repoRoot = path.resolve(import.meta.dir, "../../../..");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Symlink the workspace and vendored dependencies a generated app needs for a
|
|
15
|
+
* real `tsc --noEmit` and `bun test` run, without a package-manager install.
|
|
16
|
+
* Pass `shell: true` for full-stack apps so the shadcn shell dependencies
|
|
17
|
+
* (icons, radix, theming) resolve too.
|
|
18
|
+
*/
|
|
19
|
+
export async function linkSmokeDependencies(
|
|
20
|
+
targetDir: string,
|
|
21
|
+
options: { shell?: boolean } = {},
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
const nodeModules = path.join(targetDir, "node_modules");
|
|
24
|
+
const scopeDir = path.join(nodeModules, "@beignet");
|
|
25
|
+
await mkdir(scopeDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
const beignetPackages = [
|
|
28
|
+
"core",
|
|
29
|
+
"devtools",
|
|
30
|
+
"next",
|
|
31
|
+
"provider-auth-better-auth",
|
|
32
|
+
"provider-db-drizzle",
|
|
33
|
+
"provider-event-bus-memory",
|
|
34
|
+
"provider-logger-pino",
|
|
35
|
+
"provider-mail-resend",
|
|
36
|
+
"provider-storage-local",
|
|
37
|
+
"react-hook-form",
|
|
38
|
+
"react-query",
|
|
39
|
+
"web",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
for (const packageName of beignetPackages) {
|
|
43
|
+
await linkDependency(
|
|
44
|
+
path.join(repoRoot, "packages", packageName),
|
|
45
|
+
path.join(scopeDir, packageName),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const externalSource = path.join(
|
|
50
|
+
repoRoot,
|
|
51
|
+
"apps/example-linear-clone/node_modules",
|
|
52
|
+
);
|
|
53
|
+
const externalPackages = [
|
|
54
|
+
"@electric-sql/pglite",
|
|
55
|
+
"@hookform/resolvers",
|
|
56
|
+
"@libsql/client",
|
|
57
|
+
"@tanstack/react-query",
|
|
58
|
+
"better-auth",
|
|
59
|
+
"@types/bun",
|
|
60
|
+
"@types/node",
|
|
61
|
+
"@types/pg",
|
|
62
|
+
"@types/react",
|
|
63
|
+
"@types/react-dom",
|
|
64
|
+
"drizzle-orm",
|
|
65
|
+
"mysql2",
|
|
66
|
+
"next",
|
|
67
|
+
"pg",
|
|
68
|
+
"pino",
|
|
69
|
+
"react",
|
|
70
|
+
"react-dom",
|
|
71
|
+
"react-hook-form",
|
|
72
|
+
"resend",
|
|
73
|
+
"typescript",
|
|
74
|
+
"zod",
|
|
75
|
+
];
|
|
76
|
+
if (options.shell) {
|
|
77
|
+
// Full-stack apps also import the shadcn shell dependencies.
|
|
78
|
+
externalPackages.push(
|
|
79
|
+
"class-variance-authority",
|
|
80
|
+
"clsx",
|
|
81
|
+
"lucide-react",
|
|
82
|
+
"next-themes",
|
|
83
|
+
"radix-ui",
|
|
84
|
+
"tailwind-merge",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const externalPackageSources: Record<string, string> = {
|
|
88
|
+
"@electric-sql/pglite": path.join(
|
|
89
|
+
repoRoot,
|
|
90
|
+
"packages/provider-db-drizzle/node_modules/@electric-sql/pglite",
|
|
91
|
+
),
|
|
92
|
+
"@libsql/client": path.join(
|
|
93
|
+
repoRoot,
|
|
94
|
+
"packages/provider-db-drizzle/node_modules/@libsql/client",
|
|
95
|
+
),
|
|
96
|
+
"@types/pg": path.join(
|
|
97
|
+
repoRoot,
|
|
98
|
+
"packages/provider-db-drizzle/node_modules/@types/pg",
|
|
99
|
+
),
|
|
100
|
+
"drizzle-orm": path.join(
|
|
101
|
+
repoRoot,
|
|
102
|
+
"packages/provider-db-drizzle/node_modules/drizzle-orm",
|
|
103
|
+
),
|
|
104
|
+
mysql2: path.join(
|
|
105
|
+
repoRoot,
|
|
106
|
+
"packages/provider-db-drizzle/node_modules/mysql2",
|
|
107
|
+
),
|
|
108
|
+
pg: path.join(repoRoot, "packages/provider-db-drizzle/node_modules/pg"),
|
|
109
|
+
"better-auth": path.join(
|
|
110
|
+
repoRoot,
|
|
111
|
+
"packages/provider-auth-better-auth/node_modules/better-auth",
|
|
112
|
+
),
|
|
113
|
+
resend: path.join(
|
|
114
|
+
repoRoot,
|
|
115
|
+
"packages/provider-mail-resend/node_modules/resend",
|
|
116
|
+
),
|
|
117
|
+
"next-themes": path.join(
|
|
118
|
+
repoRoot,
|
|
119
|
+
"apps/example-ehr/node_modules/next-themes",
|
|
120
|
+
),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
for (const packageName of externalPackages) {
|
|
124
|
+
await linkDependency(
|
|
125
|
+
externalPackageSources[packageName] ??
|
|
126
|
+
path.join(externalSource, packageName),
|
|
127
|
+
path.join(nodeModules, packageName),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await linkDependency(
|
|
132
|
+
path.join(
|
|
133
|
+
repoRoot,
|
|
134
|
+
"node_modules/.bun/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec",
|
|
135
|
+
),
|
|
136
|
+
path.join(nodeModules, "@standard-schema/spec"),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function linkDependency(
|
|
141
|
+
source: string,
|
|
142
|
+
destination: string,
|
|
143
|
+
): Promise<void> {
|
|
144
|
+
await access(source);
|
|
145
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
146
|
+
await symlink(source, destination);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Run a command inside a generated app and fail the test with the combined
|
|
151
|
+
* output when it exits non-zero.
|
|
152
|
+
*/
|
|
153
|
+
export async function expectAppCommand(
|
|
154
|
+
cwd: string,
|
|
155
|
+
command: readonly string[],
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
const proc = Bun.spawnSync(command, {
|
|
158
|
+
cwd,
|
|
159
|
+
env: {
|
|
160
|
+
...process.env,
|
|
161
|
+
NEXT_TELEMETRY_DISABLED: "1",
|
|
162
|
+
},
|
|
163
|
+
stderr: "pipe",
|
|
164
|
+
stdout: "pipe",
|
|
165
|
+
});
|
|
166
|
+
const output = `${proc.stdout.toString()}${proc.stderr.toString()}`;
|
|
167
|
+
expect(proc.exitCode, output).toBe(0);
|
|
168
|
+
}
|