@flaredev/cli 0.5.1 → 0.6.1
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/README.md +59 -47
- package/dist/index.js +368 -146
- package/package.json +2 -2
- package/templates/app/app/dashboard/actions.ts +1 -1
- package/templates/app/app/dashboard/error.tsx +37 -0
- package/templates/app/app/dashboard/import-actions.ts +1 -1
- package/templates/app/app/dashboard/loading.tsx +18 -0
- package/templates/app/app/dashboard/not-found.tsx +30 -0
- package/templates/app/app/error.tsx +55 -0
- package/templates/app/app/loading.tsx +22 -0
- package/templates/app/app/not-found.tsx +48 -0
- package/templates/app/components/dashboard/fields/file-cell.tsx +68 -0
- package/templates/app/components/dashboard/record-detail.tsx +4 -0
- package/templates/app/components/dashboard/resource-table.tsx +6 -1
- package/templates/app/components/dashboard/skeletons.tsx +146 -0
- package/templates/app/lib/api.ts +1 -1
- package/templates/app/lib/cache.ts +83 -83
- package/templates/app/lib/dashboard.ts +2 -2
- package/templates/app/lib/resource/drizzle-rows.ts +134 -0
- package/templates/app/lib/resource/handlers.ts +157 -0
- package/templates/app/lib/resource/index.ts +14 -0
- package/templates/app/lib/resource/query.ts +149 -0
- package/templates/app/lib/resource/rows.ts +66 -0
- package/templates/app/lib/resource/store.ts +342 -0
- package/templates/next/app/dashboard/actions.ts +1 -1
- package/templates/next/lib/dashboard.ts +1 -1
- package/templates/next/lib/resource/index.ts +14 -0
- package/templates/next/lib/resource/prisma-rows.ts +169 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
2
|
+
import pc25 from "picocolors";
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
5
|
import { cac } from "cac";
|
|
6
|
-
import
|
|
6
|
+
import pc24 from "picocolors";
|
|
7
7
|
import * as prompts3 from "@clack/prompts";
|
|
8
8
|
import { FLARE_VERSION as FLARE_VERSION3 } from "@flaredev/core";
|
|
9
9
|
|
|
@@ -201,6 +201,9 @@ var NEXT_DEV_DEPENDENCIES = {
|
|
|
201
201
|
prisma: "7.10.0",
|
|
202
202
|
// Runs seeds: the generated Prisma client is TypeScript a bundler compiles.
|
|
203
203
|
tsx: "^4.23.13",
|
|
204
|
+
// `flare deploy` and `npm run deploy`. Local rather than global, so the deploy works
|
|
205
|
+
// on a fresh clone and every app pins its own version.
|
|
206
|
+
vercel: "^60.1.3",
|
|
204
207
|
"@types/node": "^22.20.3",
|
|
205
208
|
"@types/react": "^19.3.0",
|
|
206
209
|
"@types/react-dom": "^19.3.0",
|
|
@@ -216,7 +219,9 @@ var CLOUDFLARE_ONLY = [
|
|
|
216
219
|
"migrations",
|
|
217
220
|
"drizzle.config.ts",
|
|
218
221
|
".dev.vars.example",
|
|
219
|
-
"worker-configuration.d.ts"
|
|
222
|
+
"worker-configuration.d.ts",
|
|
223
|
+
// The engine's Drizzle adapter; the Next.js overlay brings prisma-rows.ts instead.
|
|
224
|
+
"lib/resource/drizzle-rows.ts"
|
|
220
225
|
];
|
|
221
226
|
|
|
222
227
|
// src/stack.ts
|
|
@@ -285,6 +290,20 @@ function detectPackageManager(userAgent = process.env.npm_config_user_agent ?? "
|
|
|
285
290
|
const name = userAgent.split("/")[0];
|
|
286
291
|
return isPackageManager(name) ? name : "pnpm";
|
|
287
292
|
}
|
|
293
|
+
function isInstalled(packageManager) {
|
|
294
|
+
const probe = [packageManager, "--version"].join(" ");
|
|
295
|
+
const result = process.platform === "win32" ? spawnSync(probe, { stdio: "ignore", shell: true, env: childEnv() }) : spawnSync(packageManager, ["--version"], { stdio: "ignore", env: childEnv() });
|
|
296
|
+
return result.status === 0;
|
|
297
|
+
}
|
|
298
|
+
function choosePackageManager(explicit) {
|
|
299
|
+
if (explicit !== void 0) {
|
|
300
|
+
if (!isPackageManager(explicit)) throw new Error(`Unknown package manager "${explicit}". Use pnpm, npm, yarn, or bun.`);
|
|
301
|
+
return { packageManager: explicit, preferred: false };
|
|
302
|
+
}
|
|
303
|
+
const detected = detectPackageManager();
|
|
304
|
+
if (detected !== "pnpm" && isInstalled("pnpm")) return { packageManager: "pnpm", preferred: true };
|
|
305
|
+
return { packageManager: detected, preferred: false };
|
|
306
|
+
}
|
|
288
307
|
function run(command, args, cwd) {
|
|
289
308
|
const env = childEnv();
|
|
290
309
|
const result = process.platform === "win32" ? spawnSync([command, ...args].join(" "), { cwd, stdio: "inherit", shell: true, env }) : spawnSync(command, args, { cwd, stdio: "inherit", env });
|
|
@@ -304,6 +323,7 @@ function installArgs(pm) {
|
|
|
304
323
|
}
|
|
305
324
|
|
|
306
325
|
// src/commands/create.ts
|
|
326
|
+
var ALLOW_BUILDS = ["'@prisma/engines': true", "esbuild: true", "prisma: true", "sharp: false", "workerd: true"].map((line) => ` ${line}`).join("\n");
|
|
307
327
|
function toAppName(dir) {
|
|
308
328
|
const name = basename(resolve2(dir)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
309
329
|
if (!name) throw new Error(`Cannot derive an app name from "${dir}". Use letters, digits, and dashes.`);
|
|
@@ -325,10 +345,7 @@ function createApp(target, options2 = {}) {
|
|
|
325
345
|
if (existsSync3(dir) && readdirSync2(dir).length > 0) {
|
|
326
346
|
throw new Error(`Directory ${dir} already exists and is not empty.`);
|
|
327
347
|
}
|
|
328
|
-
|
|
329
|
-
throw new Error(`Unknown package manager "${options2.pm}". Use pnpm, npm, yarn, or bun.`);
|
|
330
|
-
}
|
|
331
|
-
const packageManager = options2.pm ?? detectPackageManager();
|
|
348
|
+
const { packageManager, preferred: prefersPnpm } = choosePackageManager(options2.pm);
|
|
332
349
|
if (options2.stack !== void 0 && !isStack(options2.stack)) {
|
|
333
350
|
throw new Error(`Unknown stack "${options2.stack}". Use ${STACKS.join(" or ")}.`);
|
|
334
351
|
}
|
|
@@ -395,13 +412,15 @@ MAIL_FROM=
|
|
|
395
412
|
${devVarsEntries(authProviders)}`
|
|
396
413
|
);
|
|
397
414
|
if (packageManager === "pnpm" && !inWorkspace) {
|
|
398
|
-
|
|
399
|
-
|
|
415
|
+
writeFileSync3(join3(dir, "pnpm-workspace.yaml"), `allowBuilds:
|
|
416
|
+
${ALLOW_BUILDS}
|
|
417
|
+
`);
|
|
400
418
|
}
|
|
401
419
|
const pinned = flarePackageSpec("core", dir, packageManager).startsWith("^");
|
|
402
420
|
if (pinned) copyLockfile(packageManager, dir);
|
|
403
421
|
const log2 = options2.log ?? ((message) => console.log(message));
|
|
404
422
|
log2(`${pc.green("\u2714")} Wrote ${files} files to ${relative(process.cwd(), dir) || "."}`);
|
|
423
|
+
if (prefersPnpm) log2(pc.dim(`Installing with pnpm \u2014 far quicker for an app this size. Use ${pc.bold("--pm npm")} to change that.`));
|
|
405
424
|
return { dir, name, packageManager, inWorkspace };
|
|
406
425
|
}
|
|
407
426
|
function copyLockfile(packageManager, dir) {
|
|
@@ -1798,12 +1817,14 @@ function resolveBin(appRoot, pkgName, binName2) {
|
|
|
1798
1817
|
if (!relativeBin) throw new Error(`${pkgName} does not provide a "${binName2}" executable.`);
|
|
1799
1818
|
return join8(dirname4(manifestPath), relativeBin);
|
|
1800
1819
|
}
|
|
1820
|
+
var NEXT_COMMANDS = {
|
|
1821
|
+
dev: { pkg: "next", bin: "next", args: ["dev"] },
|
|
1822
|
+
build: { pkg: "next", bin: "next", args: ["build"] },
|
|
1823
|
+
start: { pkg: "next", bin: "next", args: ["start"] },
|
|
1824
|
+
deploy: { pkg: "vercel", bin: "vercel", args: ["deploy", "--prod"] }
|
|
1825
|
+
};
|
|
1801
1826
|
function delegatedArgv(appRoot, command, forwarded) {
|
|
1802
|
-
|
|
1803
|
-
const script = { dev: "npm run dev", build: "npm run build", start: "npm start", deploy: "npm run deploy" }[command];
|
|
1804
|
-
throw new Error(`\`flare ${command}\` is for the Cloudflare stack. This app targets Next.js \u2014 run \`${script}\` instead.`);
|
|
1805
|
-
}
|
|
1806
|
-
const spec = DELEGATED_COMMANDS[command];
|
|
1827
|
+
const spec = readStack(appRoot) === "next" ? NEXT_COMMANDS[command] : DELEGATED_COMMANDS[command];
|
|
1807
1828
|
return [resolveBin(appRoot, spec.pkg, spec.bin), ...spec.args, ...forwarded];
|
|
1808
1829
|
}
|
|
1809
1830
|
function runNode(argv, cwd) {
|
|
@@ -1858,15 +1879,22 @@ function portArg(args) {
|
|
|
1858
1879
|
}
|
|
1859
1880
|
async function runDelegated(command, forwarded, cwd = process.cwd()) {
|
|
1860
1881
|
const appRoot = findAppRoot(cwd);
|
|
1882
|
+
const next = readStack(appRoot) === "next";
|
|
1861
1883
|
const tunnel = (command === "dev" || command === "start") && forwarded.includes("--tunnel");
|
|
1862
1884
|
if (tunnel) forwarded = forwarded.filter((arg) => arg !== "--tunnel");
|
|
1863
1885
|
const wantsHelp = forwarded.includes("--help") || forwarded.includes("-h");
|
|
1864
|
-
if (command === "
|
|
1886
|
+
if (next && command === "build" && !wantsHelp) {
|
|
1887
|
+
const code = await runNode([resolveBin(appRoot, "prisma", "prisma"), "generate"], appRoot);
|
|
1888
|
+
if (code !== 0) return code;
|
|
1889
|
+
}
|
|
1890
|
+
const built = next ? ".next" : "dist/server/wrangler.json";
|
|
1891
|
+
if (command === "start" && !wantsHelp && !existsSync9(join8(appRoot, built))) {
|
|
1865
1892
|
console.log("No production build found; running `flare build` first.\n");
|
|
1866
|
-
const code = await
|
|
1893
|
+
const code = await runDelegated("build", [], appRoot);
|
|
1867
1894
|
if (code !== 0) return code;
|
|
1868
1895
|
}
|
|
1869
1896
|
if (command === "deploy") {
|
|
1897
|
+
if (next) return runNode(delegatedArgv(appRoot, "deploy", forwarded), appRoot);
|
|
1870
1898
|
return runDeploy(forwarded, {
|
|
1871
1899
|
appRoot,
|
|
1872
1900
|
wranglerBin: resolveBin(appRoot, "wrangler", "wrangler"),
|
|
@@ -1960,7 +1988,7 @@ function previousSnapshot(dir, migrationFile) {
|
|
|
1960
1988
|
|
|
1961
1989
|
// src/generator/plan.ts
|
|
1962
1990
|
import { copyFileSync as copyFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync4, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
|
|
1963
|
-
import { dirname as dirname5, join as join11 } from "path";
|
|
1991
|
+
import { dirname as dirname5, join as join11, sep } from "path";
|
|
1964
1992
|
import pc5 from "picocolors";
|
|
1965
1993
|
|
|
1966
1994
|
// src/generator/policy.ts
|
|
@@ -2285,14 +2313,19 @@ ${lines.join("\n")}
|
|
|
2285
2313
|
var handlerImports = (entry, stack = "cloudflare") => {
|
|
2286
2314
|
const local = resourceLocal(entry.stem);
|
|
2287
2315
|
const source = stack === "next" ? {
|
|
2288
|
-
imports: [`import { prismaRows } from "
|
|
2316
|
+
imports: [`import { createResourceHandlers, prismaRows } from "@/lib/resource";`, `import { prisma } from "@/lib/db";`],
|
|
2289
2317
|
wiring: [` rows: prismaRows(prisma.${camelCase4(entry.resource.name)}, prisma),`]
|
|
2290
2318
|
} : {
|
|
2291
|
-
imports: [
|
|
2292
|
-
|
|
2319
|
+
imports: [
|
|
2320
|
+
`import { createResourceHandlers, drizzleRows } from "@/lib/resource";`,
|
|
2321
|
+
`import { getDb } from "@/db";`,
|
|
2322
|
+
`import { ${tableExport(entry.resource)} } from "@/db/schema";`
|
|
2323
|
+
],
|
|
2324
|
+
// Named rather than implied: the adapter is the one thing about this route
|
|
2325
|
+
// that differs between stacks, so it is written out.
|
|
2326
|
+
wiring: [` rows: drizzleRows(${tableExport(entry.resource)}, getDb),`]
|
|
2293
2327
|
};
|
|
2294
2328
|
return [
|
|
2295
|
-
`import { createResourceHandlers } from "@flaredev/core/server";`,
|
|
2296
2329
|
...source.imports,
|
|
2297
2330
|
`import { authorize, currentUser } from "@/lib/api";`,
|
|
2298
2331
|
`import { revalidateResource } from "@/lib/cache";`,
|
|
@@ -2326,6 +2359,49 @@ function renderItemRoute(entry, stack = "cloudflare") {
|
|
|
2326
2359
|
var adminImports = (entry) => [
|
|
2327
2360
|
`import ${resourceLocal(entry.stem)} from "@/resources/${entry.stem}.resource";`
|
|
2328
2361
|
];
|
|
2362
|
+
function renderAdminListLoading(entry) {
|
|
2363
|
+
const columns = Math.min(6, storedFields2(entry.resource).length + 1);
|
|
2364
|
+
return [
|
|
2365
|
+
`import { ResourceListSkeleton } from "@/components/dashboard/skeletons";`,
|
|
2366
|
+
"",
|
|
2367
|
+
`export default function Loading() {`,
|
|
2368
|
+
` return <ResourceListSkeleton columns={${columns}} />;`,
|
|
2369
|
+
"}",
|
|
2370
|
+
""
|
|
2371
|
+
].join("\n");
|
|
2372
|
+
}
|
|
2373
|
+
function renderAdminFormLoading(entry) {
|
|
2374
|
+
const fields = Math.min(8, Math.max(1, storedFields2(entry.resource).length));
|
|
2375
|
+
return [
|
|
2376
|
+
`import { FormSkeleton, PageHeaderSkeleton } from "@/components/dashboard/skeletons";`,
|
|
2377
|
+
"",
|
|
2378
|
+
`export default function Loading() {`,
|
|
2379
|
+
" return (",
|
|
2380
|
+
" <>",
|
|
2381
|
+
" <PageHeaderSkeleton actions={0} />",
|
|
2382
|
+
` <FormSkeleton fields={${fields}} />`,
|
|
2383
|
+
" </>",
|
|
2384
|
+
" );",
|
|
2385
|
+
"}",
|
|
2386
|
+
""
|
|
2387
|
+
].join("\n");
|
|
2388
|
+
}
|
|
2389
|
+
function renderAdminDetailLoading(entry) {
|
|
2390
|
+
const rows = Math.min(10, Math.max(1, storedFields2(entry.resource).length));
|
|
2391
|
+
return [
|
|
2392
|
+
`import { DetailSkeleton, PageHeaderSkeleton } from "@/components/dashboard/skeletons";`,
|
|
2393
|
+
"",
|
|
2394
|
+
`export default function Loading() {`,
|
|
2395
|
+
" return (",
|
|
2396
|
+
" <>",
|
|
2397
|
+
" <PageHeaderSkeleton />",
|
|
2398
|
+
` <DetailSkeleton rows={${rows}} />`,
|
|
2399
|
+
" </>",
|
|
2400
|
+
" );",
|
|
2401
|
+
"}",
|
|
2402
|
+
""
|
|
2403
|
+
].join("\n");
|
|
2404
|
+
}
|
|
2329
2405
|
function renderAdminListPage(entry) {
|
|
2330
2406
|
const local = resourceLocal(entry.stem);
|
|
2331
2407
|
return [
|
|
@@ -2468,7 +2544,11 @@ function resourceFiles(entry, all, stack = "cloudflare") {
|
|
|
2468
2544
|
{ path: `app/dashboard/${resource.slug}/page.tsx`, content: renderAdminListPage(entry) },
|
|
2469
2545
|
{ path: `app/dashboard/${resource.slug}/[id]/page.tsx`, content: renderAdminDetailPage(entry) },
|
|
2470
2546
|
{ path: `app/dashboard/${resource.slug}/new/page.tsx`, content: renderAdminNewPage(entry) },
|
|
2471
|
-
{ path: `app/dashboard/${resource.slug}/[id]/edit/page.tsx`, content: renderAdminEditPage(entry) }
|
|
2547
|
+
{ path: `app/dashboard/${resource.slug}/[id]/edit/page.tsx`, content: renderAdminEditPage(entry) },
|
|
2548
|
+
{ path: `app/dashboard/${resource.slug}/loading.tsx`, content: renderAdminListLoading(entry) },
|
|
2549
|
+
{ path: `app/dashboard/${resource.slug}/new/loading.tsx`, content: renderAdminFormLoading(entry) },
|
|
2550
|
+
{ path: `app/dashboard/${resource.slug}/[id]/loading.tsx`, content: renderAdminDetailLoading(entry) },
|
|
2551
|
+
{ path: `app/dashboard/${resource.slug}/[id]/edit/loading.tsx`, content: renderAdminFormLoading(entry) }
|
|
2472
2552
|
];
|
|
2473
2553
|
}
|
|
2474
2554
|
|
|
@@ -2550,7 +2630,9 @@ function findOrphans(appRoot, all) {
|
|
|
2550
2630
|
for (const file of [`${dir}/route.ts`, `${dir}/[id]/route.ts`]) if (existsSync12(join11(appRoot, file))) candidates.push(file);
|
|
2551
2631
|
}
|
|
2552
2632
|
for (const dir of list2("app/dashboard", () => true)) {
|
|
2553
|
-
|
|
2633
|
+
const pages = [`${dir}/page.tsx`, `${dir}/new/page.tsx`, `${dir}/[id]/page.tsx`, `${dir}/[id]/edit/page.tsx`];
|
|
2634
|
+
const skeletons = [`${dir}/loading.tsx`, `${dir}/new/loading.tsx`, `${dir}/[id]/loading.tsx`, `${dir}/[id]/edit/loading.tsx`];
|
|
2635
|
+
for (const file of [...pages, ...skeletons]) {
|
|
2554
2636
|
if (existsSync12(join11(appRoot, file))) candidates.push(file);
|
|
2555
2637
|
}
|
|
2556
2638
|
}
|
|
@@ -2562,11 +2644,19 @@ function findOrphans(appRoot, all) {
|
|
|
2562
2644
|
return orphans;
|
|
2563
2645
|
}
|
|
2564
2646
|
function ensureSupportFiles(appRoot, log2) {
|
|
2565
|
-
|
|
2647
|
+
const stack = readStack(appRoot);
|
|
2648
|
+
const engine = ["rows.ts", "query.ts", "store.ts", "handlers.ts", "index.ts", stack === "next" ? "prisma-rows.ts" : "drizzle-rows.ts"].map(
|
|
2649
|
+
(name) => `lib/resource/${name}`
|
|
2650
|
+
);
|
|
2651
|
+
for (const path of ["lib/api.ts", "lib/cache.ts", ...engine]) {
|
|
2566
2652
|
const target = join11(appRoot, path);
|
|
2567
2653
|
if (existsSync12(target)) continue;
|
|
2654
|
+
const source = [join11(templatesDir, "next", path), join11(templatesDir, "app", path)].find(
|
|
2655
|
+
(candidate) => (stack === "next" || !candidate.includes(`${sep}next${sep}`)) && existsSync12(candidate)
|
|
2656
|
+
);
|
|
2657
|
+
if (!source) continue;
|
|
2568
2658
|
mkdirSync4(dirname5(target), { recursive: true });
|
|
2569
|
-
copyFileSync2(
|
|
2659
|
+
copyFileSync2(source, target);
|
|
2570
2660
|
log2(`${pc5.green("create".padEnd(9))} ${path}`);
|
|
2571
2661
|
}
|
|
2572
2662
|
}
|
|
@@ -2610,30 +2700,30 @@ function addStripeDependency(appRoot) {
|
|
|
2610
2700
|
return true;
|
|
2611
2701
|
}
|
|
2612
2702
|
function writeBillingResource(appRoot, name, force, log2) {
|
|
2613
|
-
const
|
|
2614
|
-
const path = join12(appRoot,
|
|
2703
|
+
const relative3 = descriptorPath(name);
|
|
2704
|
+
const path = join12(appRoot, relative3);
|
|
2615
2705
|
if (!existsSync13(path)) {
|
|
2616
2706
|
mkdirSync5(dirname6(path), { recursive: true });
|
|
2617
2707
|
writeFileSync7(path, renderBillingDescriptor(name));
|
|
2618
|
-
log2(`${pc6.green("create".padEnd(9))} ${
|
|
2708
|
+
log2(`${pc6.green("create".padEnd(9))} ${relative3}`);
|
|
2619
2709
|
return;
|
|
2620
2710
|
}
|
|
2621
2711
|
const parts = splitMarkers(readFileSync9(path, "utf8"));
|
|
2622
|
-
if (!parts) throw new Error(`${
|
|
2712
|
+
if (!parts) throw new Error(`${relative3} has no generated fields block, so the billing fields can't be merged. Add them by hand: ${BILLING_RESOURCES[name]}`);
|
|
2623
2713
|
const merged = mergeBillingFields(name, parts.block);
|
|
2624
2714
|
if (!merged) {
|
|
2625
|
-
log2(`${pc6.dim("identical".padEnd(9))} ${
|
|
2715
|
+
log2(`${pc6.dim("identical".padEnd(9))} ${relative3} (has every billing field)`);
|
|
2626
2716
|
return;
|
|
2627
2717
|
}
|
|
2628
2718
|
try {
|
|
2629
2719
|
writeGenerated(path, merged, { force });
|
|
2630
2720
|
} catch (error) {
|
|
2631
2721
|
if (error instanceof DriftError) {
|
|
2632
|
-
throw new Error(`${
|
|
2722
|
+
throw new Error(`${relative3}: its fields block was edited by hand, so the billing fields weren't merged. Pass --force, or add them yourself: ${BILLING_RESOURCES[name]}`);
|
|
2633
2723
|
}
|
|
2634
2724
|
throw error;
|
|
2635
2725
|
}
|
|
2636
|
-
log2(`${pc6.yellow("update".padEnd(9))} ${
|
|
2726
|
+
log2(`${pc6.yellow("update".padEnd(9))} ${relative3} (added billing fields, kept yours)`);
|
|
2637
2727
|
}
|
|
2638
2728
|
async function genBilling(options2 = {}) {
|
|
2639
2729
|
const log2 = options2.log ?? ((message) => console.log(message));
|
|
@@ -2641,14 +2731,14 @@ async function genBilling(options2 = {}) {
|
|
|
2641
2731
|
const appRoot = findAppRoot(options2.cwd ?? process.cwd());
|
|
2642
2732
|
for (const name of ["Plan", "Customer", "Purchase"]) writeBillingResource(appRoot, name, options2.force, log2);
|
|
2643
2733
|
for (const [name, roles] of Object.entries(BILLING_POLICIES)) {
|
|
2644
|
-
const
|
|
2645
|
-
if (existsSync13(join12(appRoot,
|
|
2646
|
-
log2(`${pc6.dim("kept".padEnd(9))} ${
|
|
2734
|
+
const relative3 = policyPath(name);
|
|
2735
|
+
if (existsSync13(join12(appRoot, relative3))) {
|
|
2736
|
+
log2(`${pc6.dim("kept".padEnd(9))} ${relative3} (already exists)`);
|
|
2647
2737
|
continue;
|
|
2648
2738
|
}
|
|
2649
2739
|
mkdirSync5(join12(appRoot, "policies"), { recursive: true });
|
|
2650
|
-
writeFileSync7(join12(appRoot,
|
|
2651
|
-
log2(`${pc6.green("create".padEnd(9))} ${
|
|
2740
|
+
writeFileSync7(join12(appRoot, relative3), renderPolicy(name, roles));
|
|
2741
|
+
log2(`${pc6.green("create".padEnd(9))} ${relative3}`);
|
|
2652
2742
|
}
|
|
2653
2743
|
const files = [];
|
|
2654
2744
|
const appName = JSON.parse(readFileSync9(join12(appRoot, "package.json"), "utf8")).name ?? "app";
|
|
@@ -3233,15 +3323,15 @@ function securityFiles(appName) {
|
|
|
3233
3323
|
];
|
|
3234
3324
|
}
|
|
3235
3325
|
var PASS_THROUGH_HEADER = /^\/\/ Request protection for worker\/index\.ts\.[\s\S]*?\n(?=\/\/ generated:start)/;
|
|
3236
|
-
function writeOnce(appRoot,
|
|
3237
|
-
const path = join13(appRoot,
|
|
3326
|
+
function writeOnce(appRoot, relative3, content, log2) {
|
|
3327
|
+
const path = join13(appRoot, relative3);
|
|
3238
3328
|
if (existsSync14(path)) {
|
|
3239
|
-
log2(`${pc7.dim("kept".padEnd(9))} ${
|
|
3329
|
+
log2(`${pc7.dim("kept".padEnd(9))} ${relative3} (already exists)`);
|
|
3240
3330
|
return;
|
|
3241
3331
|
}
|
|
3242
3332
|
mkdirSync6(dirname7(path), { recursive: true });
|
|
3243
3333
|
writeFileSync8(path, content);
|
|
3244
|
-
log2(`${pc7.green("create".padEnd(9))} ${
|
|
3334
|
+
log2(`${pc7.green("create".padEnd(9))} ${relative3}`);
|
|
3245
3335
|
}
|
|
3246
3336
|
async function genSecurity(options2 = {}) {
|
|
3247
3337
|
const log2 = options2.log ?? ((message) => console.log(message));
|
|
@@ -3513,16 +3603,16 @@ async function genEndpoint(resourceName2, rawName, options2 = {}) {
|
|
|
3513
3603
|
const action = options2.action ?? (method === "GET" ? "read" : "update");
|
|
3514
3604
|
if (!ACTIONS.includes(action)) throw new Error(`Unknown action "${action}". Use one of ${ACTIONS.join(", ")}.`);
|
|
3515
3605
|
const { resource, stem } = entry;
|
|
3516
|
-
const
|
|
3517
|
-
const path = join16(appRoot,
|
|
3518
|
-
if (existsSync17(path)) throw new Error(`${
|
|
3606
|
+
const relative3 = options2.record ? `app/api/${resource.slug}/[id]/${name}/route.ts` : `app/api/${resource.slug}/${name}/route.ts`;
|
|
3607
|
+
const path = join16(appRoot, relative3);
|
|
3608
|
+
if (existsSync17(path)) throw new Error(`${relative3} already exists.`);
|
|
3519
3609
|
mkdirSync8(join16(path, ".."), { recursive: true });
|
|
3520
3610
|
const route = options2.record ? `/api/${resource.slug}/[id]/${name}` : `/api/${resource.slug}/${name}`;
|
|
3521
3611
|
writeFileSync11(path, renderEndpoint({ resource: resource.name, stem, method, action, record: Boolean(options2.record), name, route }));
|
|
3522
|
-
log2(`${pc11.green("create".padEnd(9))} ${
|
|
3612
|
+
log2(`${pc11.green("create".padEnd(9))} ${relative3}`);
|
|
3523
3613
|
log2(`
|
|
3524
3614
|
It's an ordinary route handler \u2014 edit it freely; regeneration leaves it alone.`);
|
|
3525
|
-
return
|
|
3615
|
+
return relative3;
|
|
3526
3616
|
}
|
|
3527
3617
|
function renderEndpoint({ resource, stem, method, action, record, route }) {
|
|
3528
3618
|
const body = method === "GET" || method === "DELETE" ? "" : " const body = (await request.json()) as Record<string, unknown>;\n";
|
|
@@ -3612,8 +3702,8 @@ async function genPolicy(rawName, options2) {
|
|
|
3612
3702
|
const known = resources.map(({ resource }) => resource.name).join(", ") || "none";
|
|
3613
3703
|
throw new Error(`No resource "${name}". Generate it first. Known resources: ${known}.`);
|
|
3614
3704
|
}
|
|
3615
|
-
const
|
|
3616
|
-
const path = join18(appRoot,
|
|
3705
|
+
const relative3 = policyPath(name);
|
|
3706
|
+
const path = join18(appRoot, relative3);
|
|
3617
3707
|
const exists = existsSync19(path);
|
|
3618
3708
|
let roles;
|
|
3619
3709
|
let deleteRoles;
|
|
@@ -3627,14 +3717,14 @@ async function genPolicy(rawName, options2) {
|
|
|
3627
3717
|
if (!exists) {
|
|
3628
3718
|
mkdirSync10(dirname9(path), { recursive: true });
|
|
3629
3719
|
writeFileSync13(path, renderPolicy(name, { read: roles, create: roles, update: roles, delete: deleteRoles }));
|
|
3630
|
-
log2(`${pc13.green("create".padEnd(9))} ${
|
|
3720
|
+
log2(`${pc13.green("create".padEnd(9))} ${relative3}`);
|
|
3631
3721
|
} else {
|
|
3632
3722
|
try {
|
|
3633
3723
|
const outcome = writeGenerated(path, block, { force: options2.force });
|
|
3634
|
-
log2(`${(outcome === "identical" ? pc13.dim : pc13.yellow)(outcome.padEnd(9))} ${
|
|
3724
|
+
log2(`${(outcome === "identical" ? pc13.dim : pc13.yellow)(outcome.padEnd(9))} ${relative3}`);
|
|
3635
3725
|
} catch (error) {
|
|
3636
3726
|
if (error instanceof DriftError) {
|
|
3637
|
-
throw new Error(`The roles in ${
|
|
3727
|
+
throw new Error(`The roles in ${relative3} were edited by hand, so --roles won't overwrite them. Edit the file directly, or pass --force.`);
|
|
3638
3728
|
}
|
|
3639
3729
|
throw error;
|
|
3640
3730
|
}
|
|
@@ -3644,7 +3734,7 @@ async function genPolicy(rawName, options2) {
|
|
|
3644
3734
|
logResults(applyPlan(appRoot, planFiles(resources, policies, readStack(appRoot))), log2);
|
|
3645
3735
|
log2(`
|
|
3646
3736
|
${pc13.dim("Roles come from `flare role:add`; the API and the admin both enforce this policy.")}`);
|
|
3647
|
-
return { name, path:
|
|
3737
|
+
return { name, path: relative3 };
|
|
3648
3738
|
}
|
|
3649
3739
|
|
|
3650
3740
|
// src/commands/migrate.ts
|
|
@@ -3993,12 +4083,118 @@ ${result.output.trim()}`);
|
|
|
3993
4083
|
return 0;
|
|
3994
4084
|
}
|
|
3995
4085
|
|
|
4086
|
+
// src/commands/eject.ts
|
|
4087
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync14, readdirSync as readdirSync8, writeFileSync as writeFileSync15 } from "fs";
|
|
4088
|
+
import { dirname as dirname11, join as join21, relative as relative2 } from "path";
|
|
4089
|
+
import pc17 from "picocolors";
|
|
4090
|
+
var TRACKED = ["lib/resource"];
|
|
4091
|
+
var prunedOn = (path, stack) => stack === "next" && CLOUDFLARE_ONLY.some((only) => path === only || path.startsWith(`${only}/`));
|
|
4092
|
+
function upstreamFile(path, stack) {
|
|
4093
|
+
if (prunedOn(path, stack)) return void 0;
|
|
4094
|
+
const candidates = stack === "next" ? [join21(templatesDir, "next", path), join21(templatesDir, "app", path)] : [join21(templatesDir, "app", path)];
|
|
4095
|
+
return candidates.find((candidate) => existsSync22(candidate));
|
|
4096
|
+
}
|
|
4097
|
+
function trackedPaths(appRoot, stack) {
|
|
4098
|
+
const paths = /* @__PURE__ */ new Set();
|
|
4099
|
+
const walk = (base, dir) => {
|
|
4100
|
+
if (!existsSync22(join21(base, dir))) return;
|
|
4101
|
+
for (const entry of readdirSync8(join21(base, dir), { withFileTypes: true })) {
|
|
4102
|
+
const next = `${dir}/${entry.name}`;
|
|
4103
|
+
if (entry.isDirectory()) walk(base, next);
|
|
4104
|
+
else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) paths.add(next);
|
|
4105
|
+
}
|
|
4106
|
+
};
|
|
4107
|
+
for (const root of TRACKED) {
|
|
4108
|
+
walk(appRoot, root);
|
|
4109
|
+
walk(join21(templatesDir, "app"), root);
|
|
4110
|
+
if (stack === "next") walk(join21(templatesDir, "next"), root);
|
|
4111
|
+
}
|
|
4112
|
+
return [...paths].filter((path) => upstreamFile(path, stack) !== void 0).sort();
|
|
4113
|
+
}
|
|
4114
|
+
function compareTracked(appRoot, stack, filter) {
|
|
4115
|
+
return trackedPaths(appRoot, stack).filter((path) => !filter || path.includes(filter)).map((path) => {
|
|
4116
|
+
const source = upstreamFile(path, stack);
|
|
4117
|
+
const theirs = readFileSync14(source, "utf8");
|
|
4118
|
+
const target = join21(appRoot, path);
|
|
4119
|
+
if (!existsSync22(target)) return { path, state: "missing", theirs };
|
|
4120
|
+
const mine = readFileSync14(target, "utf8");
|
|
4121
|
+
return { path, state: mine === theirs ? "same" : "changed", mine, theirs };
|
|
4122
|
+
});
|
|
4123
|
+
}
|
|
4124
|
+
function summarise(mine, theirs) {
|
|
4125
|
+
const a = mine.split("\n");
|
|
4126
|
+
const b = theirs.split("\n");
|
|
4127
|
+
const lines = [];
|
|
4128
|
+
const max = Math.max(a.length, b.length);
|
|
4129
|
+
let shown = 0;
|
|
4130
|
+
for (let index = 0; index < max && shown < 12; index += 1) {
|
|
4131
|
+
if (a[index] === b[index]) continue;
|
|
4132
|
+
if (a[index] !== void 0) lines.push(pc17.red(` - ${a[index].slice(0, 100)}`));
|
|
4133
|
+
if (b[index] !== void 0) lines.push(pc17.green(` + ${b[index].slice(0, 100)}`));
|
|
4134
|
+
shown += 1;
|
|
4135
|
+
}
|
|
4136
|
+
return lines;
|
|
4137
|
+
}
|
|
4138
|
+
function diffTracked(options2 = {}) {
|
|
4139
|
+
const log2 = options2.log ?? ((message) => console.log(message));
|
|
4140
|
+
const appRoot = findAppRoot(options2.cwd ?? process.cwd());
|
|
4141
|
+
const stack = readStack(appRoot);
|
|
4142
|
+
const comparisons = compareTracked(appRoot, stack, options2.filter);
|
|
4143
|
+
const changed = comparisons.filter((entry) => entry.state !== "same");
|
|
4144
|
+
if (changed.length === 0) {
|
|
4145
|
+
log2(pc17.green(`Up to date: ${comparisons.length} file(s) match this version of Flare.`));
|
|
4146
|
+
return comparisons;
|
|
4147
|
+
}
|
|
4148
|
+
log2(pc17.bold(`${changed.length} of ${comparisons.length} file(s) differ from this version of Flare:
|
|
4149
|
+
`));
|
|
4150
|
+
for (const entry of changed) {
|
|
4151
|
+
if (entry.state === "missing") {
|
|
4152
|
+
log2(`${pc17.yellow("missing")} ${entry.path} ${pc17.dim("(Flare ships this; your app doesn't have it)")}`);
|
|
4153
|
+
continue;
|
|
4154
|
+
}
|
|
4155
|
+
log2(`${pc17.cyan("changed")} ${entry.path}`);
|
|
4156
|
+
for (const line of summarise(entry.mine, entry.theirs)) log2(line);
|
|
4157
|
+
log2("");
|
|
4158
|
+
}
|
|
4159
|
+
log2(pc17.dim(`Yours is "-", Flare's is "+". Apply with ${pc17.bold("flare update")}, or keep yours and do nothing.`));
|
|
4160
|
+
return comparisons;
|
|
4161
|
+
}
|
|
4162
|
+
function updateTracked(options2 = {}) {
|
|
4163
|
+
const log2 = options2.log ?? ((message) => console.log(message));
|
|
4164
|
+
const appRoot = findAppRoot(options2.cwd ?? process.cwd());
|
|
4165
|
+
const stack = readStack(appRoot);
|
|
4166
|
+
const comparisons = compareTracked(appRoot, stack, options2.filter);
|
|
4167
|
+
const changed = comparisons.filter((entry) => entry.state !== "same");
|
|
4168
|
+
if (changed.length === 0) {
|
|
4169
|
+
log2(pc17.green("Nothing to update."));
|
|
4170
|
+
return 0;
|
|
4171
|
+
}
|
|
4172
|
+
if (!options2.yes) {
|
|
4173
|
+
log2(pc17.bold(`${changed.length} file(s) would be overwritten with Flare's version:
|
|
4174
|
+
`));
|
|
4175
|
+
for (const entry of changed) log2(` ${entry.path}`);
|
|
4176
|
+
log2(pc17.yellow(`
|
|
4177
|
+
This discards your edits to those files. Re-run with ${pc17.bold("--yes")} to proceed.`));
|
|
4178
|
+
log2(pc17.dim(`See what would change first with ${pc17.bold("flare diff")}.`));
|
|
4179
|
+
return 1;
|
|
4180
|
+
}
|
|
4181
|
+
for (const entry of changed) {
|
|
4182
|
+
const target = join21(appRoot, entry.path);
|
|
4183
|
+
mkdirSync11(dirname11(target), { recursive: true });
|
|
4184
|
+
writeFileSync15(target, entry.theirs);
|
|
4185
|
+
log2(`${pc17.green("update")} ${relative2(process.cwd(), target) || entry.path}`);
|
|
4186
|
+
}
|
|
4187
|
+
log2(pc17.green(`
|
|
4188
|
+
Updated ${changed.length} file(s).`));
|
|
4189
|
+
return 0;
|
|
4190
|
+
}
|
|
4191
|
+
|
|
3996
4192
|
// src/commands/seed.ts
|
|
3997
|
-
import { existsSync as
|
|
3998
|
-
import { join as
|
|
4193
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readdirSync as readdirSync9, rmSync as rmSync5, writeFileSync as writeFileSync17 } from "fs";
|
|
4194
|
+
import { join as join23 } from "path";
|
|
3999
4195
|
import { camelCase as camelCase6, createFake, kebabCase as kebabCase4, storedFields as storedFields3 } from "@flaredev/core";
|
|
4000
4196
|
import { createJiti as createJiti4 } from "jiti";
|
|
4001
|
-
import
|
|
4197
|
+
import pc18 from "picocolors";
|
|
4002
4198
|
|
|
4003
4199
|
// src/seed/bulk.ts
|
|
4004
4200
|
var MAX_STATEMENT_BYTES = 8e4;
|
|
@@ -4092,17 +4288,20 @@ function createInsertMany(sink, drizzle, onProgress) {
|
|
|
4092
4288
|
}
|
|
4093
4289
|
|
|
4094
4290
|
// src/seed/local-d1.ts
|
|
4291
|
+
import { readFileSync as readFileSync15, rmSync as rmSync4, writeFileSync as writeFileSync16 } from "fs";
|
|
4095
4292
|
import { createRequire } from "module";
|
|
4096
|
-
import { join as
|
|
4293
|
+
import { join as join22 } from "path";
|
|
4097
4294
|
import { pathToFileURL } from "url";
|
|
4295
|
+
import { parse as parseJsonc3 } from "jsonc-parser";
|
|
4098
4296
|
var LOCAL_STATE = ".wrangler/state/v3";
|
|
4099
4297
|
async function openLocalD1(appRoot, binding) {
|
|
4100
4298
|
process.env.WRANGLER_LOG ??= "error";
|
|
4101
|
-
const appRequire = createRequire(
|
|
4299
|
+
const appRequire = createRequire(join22(appRoot, "package.json"));
|
|
4102
4300
|
const wrangler = await import(pathToFileURL(appRequire.resolve("wrangler")).href);
|
|
4301
|
+
const configPath = seedConfig(appRoot);
|
|
4103
4302
|
const { env, dispose } = await wrangler.getPlatformProxy({
|
|
4104
|
-
configPath
|
|
4105
|
-
persist: { path:
|
|
4303
|
+
configPath,
|
|
4304
|
+
persist: { path: join22(appRoot, LOCAL_STATE) }
|
|
4106
4305
|
});
|
|
4107
4306
|
const bindings = env;
|
|
4108
4307
|
const name = binding ?? "DB";
|
|
@@ -4111,14 +4310,30 @@ async function openLocalD1(appRoot, binding) {
|
|
|
4111
4310
|
const available = Object.keys(bindings).filter((key) => typeof bindings[key]?.prepare === "function");
|
|
4112
4311
|
throw new Error(`No "${name}" database binding in wrangler.jsonc.${available.length ? ` Available: ${available.join(", ")}.` : ""}`);
|
|
4113
4312
|
}
|
|
4114
|
-
return {
|
|
4313
|
+
return {
|
|
4314
|
+
db,
|
|
4315
|
+
env: bindings,
|
|
4316
|
+
dispose: async () => {
|
|
4317
|
+
await dispose();
|
|
4318
|
+
rmSync4(configPath, { force: true });
|
|
4319
|
+
}
|
|
4320
|
+
};
|
|
4321
|
+
}
|
|
4322
|
+
function seedConfig(appRoot) {
|
|
4323
|
+
const source = join22(appRoot, "wrangler.jsonc");
|
|
4324
|
+
const config = parseJsonc3(readFileSync15(source, "utf8")) ?? {};
|
|
4325
|
+
delete config.durable_objects;
|
|
4326
|
+
delete config.migrations;
|
|
4327
|
+
const path = join22(appRoot, ".flare-seed.wrangler.json");
|
|
4328
|
+
writeFileSync16(path, JSON.stringify(config, null, 2));
|
|
4329
|
+
return path;
|
|
4115
4330
|
}
|
|
4116
4331
|
|
|
4117
4332
|
// src/commands/seed.ts
|
|
4118
4333
|
var SEEDS_DIR = "seeds";
|
|
4119
4334
|
function seedFiles(appRoot, names) {
|
|
4120
|
-
const dir =
|
|
4121
|
-
const all =
|
|
4335
|
+
const dir = join23(appRoot, SEEDS_DIR);
|
|
4336
|
+
const all = existsSync23(dir) ? readdirSync9(dir).filter((file) => file.endsWith(".seed.ts")).sort() : [];
|
|
4122
4337
|
if (!names?.length) return all;
|
|
4123
4338
|
return names.map((name) => {
|
|
4124
4339
|
const file = `${name.replace(/\.seed\.ts$/, "")}.seed.ts`;
|
|
@@ -4133,28 +4348,28 @@ async function runSeeds(options2 = {}) {
|
|
|
4133
4348
|
const appRoot = findAppRoot(options2.cwd ?? process.cwd());
|
|
4134
4349
|
const files = seedFiles(appRoot, options2.names);
|
|
4135
4350
|
if (files.length === 0) {
|
|
4136
|
-
log2(`No seeds found. Create one with ${
|
|
4351
|
+
log2(`No seeds found. Create one with ${pc18.bold("flare seed:make <name>")}.`);
|
|
4137
4352
|
return;
|
|
4138
4353
|
}
|
|
4139
4354
|
if (readStack(appRoot) === "next") {
|
|
4140
4355
|
await runPrismaSeeds(appRoot, files, log2);
|
|
4141
4356
|
return;
|
|
4142
4357
|
}
|
|
4143
|
-
const jiti = createJiti4(
|
|
4358
|
+
const jiti = createJiti4(join23(appRoot, "package.json"), { alias: { "@": appRoot }, moduleCache: false, fsCache: false });
|
|
4144
4359
|
const { drizzle } = await jiti.import("drizzle-orm/d1");
|
|
4145
4360
|
const drizzleHelpers = await jiti.import("drizzle-orm");
|
|
4146
|
-
const schema = await jiti.import(
|
|
4361
|
+
const schema = await jiti.import(join23(appRoot, "db/schema.ts"));
|
|
4147
4362
|
const local = await openLocalD1(appRoot);
|
|
4148
4363
|
try {
|
|
4149
4364
|
const db = drizzle(local.db, { schema });
|
|
4150
4365
|
const insertMany = createInsertMany(local.db, drizzleHelpers);
|
|
4151
4366
|
for (const file of files) {
|
|
4152
|
-
const mod = await jiti.import(
|
|
4367
|
+
const mod = await jiti.import(join23(appRoot, SEEDS_DIR, file));
|
|
4153
4368
|
if (typeof mod.default !== "function") throw new Error(`${SEEDS_DIR}/${file} must \`export default defineSeed(async ({ db }) => { ... })\`.`);
|
|
4154
|
-
log2(`${
|
|
4369
|
+
log2(`${pc18.cyan("seed")} ${file}`);
|
|
4155
4370
|
await mod.default({ db, env: local.env, insertMany, fake: createFake(), log: (message) => log2(` ${message}`) });
|
|
4156
4371
|
}
|
|
4157
|
-
log2(
|
|
4372
|
+
log2(pc18.green(`
|
|
4158
4373
|
Ran ${files.length} seed(s) against the local database.`));
|
|
4159
4374
|
} finally {
|
|
4160
4375
|
await local.dispose();
|
|
@@ -4265,9 +4480,9 @@ async function makeSeed(rawName, options2 = {}) {
|
|
|
4265
4480
|
const appRoot = findAppRoot(options2.cwd ?? process.cwd());
|
|
4266
4481
|
const name = kebabCase4(rawName);
|
|
4267
4482
|
if (!name) throw new Error(`Invalid seed name "${rawName}".`);
|
|
4268
|
-
const
|
|
4269
|
-
const path =
|
|
4270
|
-
if (
|
|
4483
|
+
const relative3 = `${SEEDS_DIR}/${name}.seed.ts`;
|
|
4484
|
+
const path = join23(appRoot, relative3);
|
|
4485
|
+
if (existsSync23(path)) throw new Error(`${relative3} already exists.`);
|
|
4271
4486
|
const resources = (await loadResources(appRoot)).map(({ resource: resource2 }) => resource2);
|
|
4272
4487
|
let resource;
|
|
4273
4488
|
if (options2.resource) {
|
|
@@ -4276,15 +4491,15 @@ async function makeSeed(rawName, options2 = {}) {
|
|
|
4276
4491
|
} else {
|
|
4277
4492
|
resource = resources.find((r) => [kebabCase4(r.name), r.slug].includes(name));
|
|
4278
4493
|
}
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
log2(`${
|
|
4494
|
+
mkdirSync12(join23(appRoot, SEEDS_DIR), { recursive: true });
|
|
4495
|
+
writeFileSync17(path, renderSeed(resource, readStack(appRoot)));
|
|
4496
|
+
log2(`${pc18.green("create".padEnd(9))} ${relative3}`);
|
|
4282
4497
|
log2(`
|
|
4283
|
-
Run it with ${
|
|
4284
|
-
return
|
|
4498
|
+
Run it with ${pc18.bold(`flare seed ${name}`)}.`);
|
|
4499
|
+
return relative3;
|
|
4285
4500
|
}
|
|
4286
4501
|
async function runPrismaSeeds(appRoot, files, log2) {
|
|
4287
|
-
const runner =
|
|
4502
|
+
const runner = join23(appRoot, ".flare-seed-runner.mts");
|
|
4288
4503
|
const script = [
|
|
4289
4504
|
`import "dotenv/config";`,
|
|
4290
4505
|
`import { createFake } from "@flaredev/core";`,
|
|
@@ -4312,23 +4527,23 @@ async function runPrismaSeeds(appRoot, files, log2) {
|
|
|
4312
4527
|
`await (prisma as unknown as { $disconnect(): Promise<void> }).$disconnect();`,
|
|
4313
4528
|
""
|
|
4314
4529
|
].join("\n");
|
|
4315
|
-
|
|
4530
|
+
writeFileSync17(runner, script);
|
|
4316
4531
|
try {
|
|
4317
4532
|
const tsx = resolveBin(appRoot, "tsx", "tsx");
|
|
4318
4533
|
const code = await runNode([tsx, runner], appRoot);
|
|
4319
4534
|
if (code !== 0) throw new Error("A seed failed; nothing further was run.");
|
|
4320
|
-
log2(
|
|
4535
|
+
log2(pc18.green(`
|
|
4321
4536
|
Ran ${files.length} seed(s).`));
|
|
4322
4537
|
} finally {
|
|
4323
|
-
|
|
4538
|
+
rmSync5(runner, { force: true });
|
|
4324
4539
|
}
|
|
4325
4540
|
}
|
|
4326
4541
|
|
|
4327
4542
|
// src/commands/db-push.ts
|
|
4328
|
-
import { mkdtempSync as mkdtempSync2, rmSync as
|
|
4543
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync6, writeFileSync as writeFileSync18 } from "fs";
|
|
4329
4544
|
import { tmpdir as tmpdir2 } from "os";
|
|
4330
|
-
import { join as
|
|
4331
|
-
import
|
|
4545
|
+
import { join as join24 } from "path";
|
|
4546
|
+
import pc19 from "picocolors";
|
|
4332
4547
|
|
|
4333
4548
|
// src/terminal.ts
|
|
4334
4549
|
var isTTY = () => Boolean(process.stdout.isTTY);
|
|
@@ -4392,7 +4607,7 @@ async function dbPush(tables, options2 = {}) {
|
|
|
4392
4607
|
const wrangler = resolveBin(appRoot, "wrangler", "wrangler");
|
|
4393
4608
|
const started = Date.now();
|
|
4394
4609
|
const local = await openLocalD1(appRoot, options2.database);
|
|
4395
|
-
const dir = mkdtempSync2(
|
|
4610
|
+
const dir = mkdtempSync2(join24(tmpdir2(), "flare-push-"));
|
|
4396
4611
|
try {
|
|
4397
4612
|
const statements = [];
|
|
4398
4613
|
const counts = [];
|
|
@@ -4411,33 +4626,33 @@ async function dbPush(tables, options2 = {}) {
|
|
|
4411
4626
|
offset += PAGE;
|
|
4412
4627
|
}
|
|
4413
4628
|
counts.push({ table, rows });
|
|
4414
|
-
log2(`${
|
|
4629
|
+
log2(`${pc19.cyan("read")} ${table} ${pc19.dim(`(${formatCount(rows)} rows)`)}`);
|
|
4415
4630
|
}
|
|
4416
4631
|
const total = counts.reduce((sum, entry) => sum + entry.rows, 0);
|
|
4417
4632
|
if (total === 0) {
|
|
4418
4633
|
log2("Nothing to push: those tables are empty locally.");
|
|
4419
4634
|
return { tables: counts, ms: Date.now() - started };
|
|
4420
4635
|
}
|
|
4421
|
-
const file =
|
|
4422
|
-
|
|
4636
|
+
const file = join24(dir, "push.sql");
|
|
4637
|
+
writeFileSync18(file, statements.join("\n"));
|
|
4423
4638
|
const target = ["--remote", ...options2.env ? ["--env", options2.env] : []];
|
|
4424
4639
|
const result = await runWrangler(wrangler, ["d1", "execute", db.binding, ...target, "--yes", "--file", file], appRoot, { capture: true });
|
|
4425
4640
|
if (result.code !== 0) throw new Error(`Pushing to ${db.database_name} failed:
|
|
4426
4641
|
${result.output.trim()}`);
|
|
4427
4642
|
const ms = Date.now() - started;
|
|
4428
|
-
log2(`${
|
|
4643
|
+
log2(`${pc19.green("\u2714")} Pushed ${pc19.bold(formatCount(total))} rows to ${pc19.bold(db.database_name)} ${pc19.dim(`(${formatDuration(ms)})`)}`);
|
|
4429
4644
|
return { tables: counts, ms };
|
|
4430
4645
|
} finally {
|
|
4431
|
-
|
|
4646
|
+
rmSync6(dir, { recursive: true, force: true });
|
|
4432
4647
|
await local.dispose();
|
|
4433
4648
|
}
|
|
4434
4649
|
}
|
|
4435
4650
|
|
|
4436
4651
|
// src/commands/seed-resource.ts
|
|
4437
|
-
import { mkdtempSync as mkdtempSync3, rmSync as
|
|
4652
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync19 } from "fs";
|
|
4438
4653
|
import { tmpdir as tmpdir3 } from "os";
|
|
4439
|
-
import { join as
|
|
4440
|
-
import
|
|
4654
|
+
import { join as join25 } from "path";
|
|
4655
|
+
import pc20 from "picocolors";
|
|
4441
4656
|
|
|
4442
4657
|
// src/seed/rows.ts
|
|
4443
4658
|
import { columnName as columnName3, createFake as createFake2, storedFields as storedFields4 } from "@flaredev/core";
|
|
@@ -4621,13 +4836,13 @@ async function seedResourceCommand(name, options2 = {}) {
|
|
|
4621
4836
|
...options2,
|
|
4622
4837
|
onProgress: (written, total) => {
|
|
4623
4838
|
const elapsed = Date.now() - started;
|
|
4624
|
-
progress.update(` ${
|
|
4839
|
+
progress.update(` ${pc20.cyan(formatCount(written))} of ${formatCount(total)} rows ${pc20.dim(formatRate(written, elapsed))}`);
|
|
4625
4840
|
}
|
|
4626
4841
|
});
|
|
4627
4842
|
progress.done();
|
|
4628
4843
|
const { rows, ms, where } = result;
|
|
4629
4844
|
console.log(
|
|
4630
|
-
`${
|
|
4845
|
+
`${pc20.green("\u2714")} Seeded ${pc20.bold(formatCount(rows))} ${result.resource.pluralLabel.toLowerCase()} into the ${where} database ` + pc20.dim(`(${formatDuration(ms)}, ${formatRate(rows, ms)})`)
|
|
4631
4846
|
);
|
|
4632
4847
|
return result;
|
|
4633
4848
|
} catch (error) {
|
|
@@ -4675,8 +4890,8 @@ ${result.output.trim()}`);
|
|
|
4675
4890
|
const [counted] = await query(`SELECT count(*) AS total FROM "${resource.table}"`);
|
|
4676
4891
|
startIndex = counted?.total ?? 0;
|
|
4677
4892
|
}
|
|
4678
|
-
const dir = mkdtempSync3(
|
|
4679
|
-
const file =
|
|
4893
|
+
const dir = mkdtempSync3(join25(tmpdir3(), "flare-seed-"));
|
|
4894
|
+
const file = join25(dir, `${resource.table}.sql`);
|
|
4680
4895
|
try {
|
|
4681
4896
|
const statements = [];
|
|
4682
4897
|
if (options2.truncate) statements.push(`DELETE FROM "${resource.table}";`);
|
|
@@ -4686,13 +4901,13 @@ ${result.output.trim()}`);
|
|
|
4686
4901
|
written += statement.rows;
|
|
4687
4902
|
options2.onProgress?.(written, total);
|
|
4688
4903
|
}
|
|
4689
|
-
|
|
4690
|
-
log2(`${
|
|
4904
|
+
writeFileSync19(file, statements.join("\n"));
|
|
4905
|
+
log2(`${pc20.cyan("\u25CF")} Uploading ${total.toLocaleString()} rows to ${pc20.bold(db.database_name)}`);
|
|
4691
4906
|
const result = await runWrangler(wrangler, ["d1", "execute", db.binding, ...target, "--yes", "--file", file], appRoot, { capture: true });
|
|
4692
4907
|
if (result.code !== 0) throw new Error(`Seeding the remote database failed:
|
|
4693
4908
|
${result.output.trim()}`);
|
|
4694
4909
|
} finally {
|
|
4695
|
-
|
|
4910
|
+
rmSync7(dir, { recursive: true, force: true });
|
|
4696
4911
|
}
|
|
4697
4912
|
return { resource, rows: total, ms: Date.now() - started, where };
|
|
4698
4913
|
}
|
|
@@ -4719,17 +4934,17 @@ ${result.output.trim()}`);
|
|
|
4719
4934
|
|
|
4720
4935
|
// src/commands/billing-sync.ts
|
|
4721
4936
|
import { createRequire as createRequire2 } from "module";
|
|
4722
|
-
import { existsSync as
|
|
4937
|
+
import { existsSync as existsSync24, mkdtempSync as mkdtempSync4, readFileSync as readFileSync16, rmSync as rmSync8, writeFileSync as writeFileSync20 } from "fs";
|
|
4723
4938
|
import { tmpdir as tmpdir4 } from "os";
|
|
4724
|
-
import { join as
|
|
4939
|
+
import { join as join26 } from "path";
|
|
4725
4940
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
4726
4941
|
import { kebabCase as kebabCase5 } from "@flaredev/core";
|
|
4727
|
-
import
|
|
4942
|
+
import pc21 from "picocolors";
|
|
4728
4943
|
function stripeKeys(appRoot) {
|
|
4729
4944
|
const map = {};
|
|
4730
|
-
const path =
|
|
4731
|
-
if (
|
|
4732
|
-
for (const line of
|
|
4945
|
+
const path = join26(appRoot, ".dev.vars");
|
|
4946
|
+
if (existsSync24(path)) {
|
|
4947
|
+
for (const line of readFileSync16(path, "utf8").split(/\r?\n/)) {
|
|
4733
4948
|
const match = /^\s*([A-Z][A-Z0-9_]*)=(.*)$/.exec(line);
|
|
4734
4949
|
if (match) map[match[1]] = match[2].trim();
|
|
4735
4950
|
}
|
|
@@ -4812,10 +5027,10 @@ async function syncPlans(options2 = {}) {
|
|
|
4812
5027
|
if (!key) throw new Error("STRIPE_SECRET_KEY is not set in .dev.vars or the environment.");
|
|
4813
5028
|
const db = readD1Databases(appRoot)[0];
|
|
4814
5029
|
if (!db) throw new Error("No d1_databases in wrangler.jsonc.");
|
|
4815
|
-
const appRequire = createRequire2(
|
|
5030
|
+
const appRequire = createRequire2(join26(appRoot, "package.json"));
|
|
4816
5031
|
const stripeModule = await import(pathToFileURL2(appRequire.resolve("stripe")).href);
|
|
4817
5032
|
const stripe = new stripeModule.default(key, { apiVersion: "2026-08-26.dahlia" });
|
|
4818
|
-
const appName = JSON.parse(
|
|
5033
|
+
const appName = JSON.parse(readFileSync16(join26(appRoot, "package.json"), "utf8")).name ?? "app";
|
|
4819
5034
|
const everything = await stripe.products.list({ active: true, limit: 100 }).autoPagingToArray({ limit: 1e4 });
|
|
4820
5035
|
const products = options2.all ? everything : everything.filter((product) => product.metadata?.flare_app === appName);
|
|
4821
5036
|
if (products.length === 0) {
|
|
@@ -4836,41 +5051,41 @@ async function syncPlans(options2 = {}) {
|
|
|
4836
5051
|
${existingResult.output.trim()}`);
|
|
4837
5052
|
}
|
|
4838
5053
|
const known = new Set(extractJson(existingResult.output)[0]?.results.map((row) => row.stripe_price_id));
|
|
4839
|
-
const dir = mkdtempSync4(
|
|
5054
|
+
const dir = mkdtempSync4(join26(tmpdir4(), "flare-sync-"));
|
|
4840
5055
|
try {
|
|
4841
|
-
const file =
|
|
4842
|
-
|
|
5056
|
+
const file = join26(dir, "sync-plans.sql");
|
|
5057
|
+
writeFileSync20(file, renderSyncSql(rows));
|
|
4843
5058
|
const result = await runWrangler(wrangler, ["d1", "execute", db.binding, ...target, "--file", file], appRoot, { capture: true });
|
|
4844
5059
|
if (result.code !== 0) throw new Error(`Could not write plans:
|
|
4845
5060
|
${result.output.trim()}`);
|
|
4846
5061
|
} finally {
|
|
4847
|
-
|
|
5062
|
+
rmSync8(dir, { recursive: true, force: true });
|
|
4848
5063
|
}
|
|
4849
5064
|
let created = 0;
|
|
4850
5065
|
for (const row of rows) {
|
|
4851
5066
|
const isNew = !known.has(row.stripePriceId);
|
|
4852
5067
|
if (isNew) created++;
|
|
4853
5068
|
const price = `${row.currency.toUpperCase()} ${(row.amount / 100).toFixed(2)}${row.interval ? `/${row.interval}` : " once"}`;
|
|
4854
|
-
log2(`${(isNew ?
|
|
5069
|
+
log2(`${(isNew ? pc21.green("create") : pc21.dim("update")).padEnd(18)} ${row.name} (${row.slug}) ${pc21.dim(price)}`);
|
|
4855
5070
|
}
|
|
4856
|
-
for (const name of skipped) log2(
|
|
5071
|
+
for (const name of skipped) log2(pc21.yellow(`skipped ${name}: only monthly, yearly and one-time prices become plans`));
|
|
4857
5072
|
const configuration = await syncPortalConfiguration(stripe, rows, appName);
|
|
4858
|
-
if (configuration) log2(`${
|
|
4859
|
-
log2(
|
|
5073
|
+
if (configuration) log2(`${pc21.dim("portal".padEnd(9))} plan changes enabled for ${new Set(rows.filter((row) => row.interval).map((row) => row.stripeProductId)).size} product(s) (${configuration})`);
|
|
5074
|
+
log2(pc21.green(`
|
|
4860
5075
|
${rows.length} plan(s) synced to the ${options2.remote ? "remote" : "local"} database; plans no longer active in Stripe were deactivated.`));
|
|
4861
5076
|
return { created, updated: rows.length - created, skipped };
|
|
4862
5077
|
}
|
|
4863
5078
|
|
|
4864
5079
|
// src/commands/sync.ts
|
|
4865
|
-
import { existsSync as
|
|
4866
|
-
import { join as
|
|
4867
|
-
import
|
|
5080
|
+
import { existsSync as existsSync25 } from "fs";
|
|
5081
|
+
import { join as join27 } from "path";
|
|
5082
|
+
import pc22 from "picocolors";
|
|
4868
5083
|
async function syncTypes(options2 = {}) {
|
|
4869
5084
|
const log2 = options2.log ?? ((message) => console.log(message));
|
|
4870
5085
|
const appRoot = findAppRoot(options2.cwd ?? process.cwd());
|
|
4871
5086
|
const all = await loadResources(appRoot);
|
|
4872
5087
|
const policies = await loadPolicies(appRoot);
|
|
4873
|
-
const tracksPolicies = policies.length > 0 ||
|
|
5088
|
+
const tracksPolicies = policies.length > 0 || existsSync25(join27(appRoot, "policies/index.ts"));
|
|
4874
5089
|
const plan = planFiles(all, tracksPolicies ? policies : void 0, readStack(appRoot));
|
|
4875
5090
|
if (!options2.check) ensureSupportFiles(appRoot, log2);
|
|
4876
5091
|
const results = applyPlan(appRoot, plan, { force: options2.force, dryRun: options2.check });
|
|
@@ -4882,42 +5097,42 @@ async function syncTypes(options2 = {}) {
|
|
|
4882
5097
|
if (drift.length) {
|
|
4883
5098
|
log2(
|
|
4884
5099
|
`
|
|
4885
|
-
${
|
|
5100
|
+
${pc22.red("Drift:")} ${drift.length} generated block(s) were edited by hand and were ${options2.check ? "not checked further" : "left unchanged"}.
|
|
4886
5101
|
Move custom code outside the // generated:start \u2026 // generated:end block, or re-run with --force to overwrite.`
|
|
4887
5102
|
);
|
|
4888
5103
|
}
|
|
4889
5104
|
if (unmarked.length) {
|
|
4890
5105
|
log2(`
|
|
4891
|
-
${
|
|
5106
|
+
${pc22.red("Conflict:")} ${unmarked.map((r) => r.path).join(", ")} exist without a generated block (hand-written). Rename or remove them.`);
|
|
4892
5107
|
}
|
|
4893
5108
|
if (orphans.length) {
|
|
4894
5109
|
const resources = [...new Set(orphans.map((o) => o.resource))];
|
|
4895
5110
|
log2(`
|
|
4896
|
-
${
|
|
5111
|
+
${pc22.yellow("Orphaned:")} generated files the descriptors no longer produce (${resources.join(", ")}):`);
|
|
4897
5112
|
for (const orphan of orphans) log2(` ${orphan.path}`);
|
|
4898
5113
|
log2(
|
|
4899
|
-
`If the resource was deleted, remove them with ${
|
|
5114
|
+
`If the resource was deleted, remove them with ${pc22.bold(`flare rm resource ${resources[0]}`)}; if it was renamed (table or slug), delete the old files.`
|
|
4900
5115
|
);
|
|
4901
5116
|
}
|
|
4902
5117
|
const tablesChanged = changed.some((r) => r.path.startsWith("db/schema/") && (r.status === "outdated" || r.status === "missing"));
|
|
4903
5118
|
if (tablesChanged && !options2.check) {
|
|
4904
5119
|
log2(`
|
|
4905
|
-
${
|
|
5120
|
+
${pc22.cyan("Tables changed.")} Create a migration with ${pc22.bold("flare gen migration <name> --from-schema")}, then ${pc22.bold("flare migrate")}.`);
|
|
4906
5121
|
}
|
|
4907
5122
|
const problems = drift.length + unmarked.length + orphans.length;
|
|
4908
5123
|
if (options2.check) {
|
|
4909
5124
|
const stale = changed.length;
|
|
4910
|
-
if (stale === 0 && problems === 0) log2(
|
|
5125
|
+
if (stale === 0 && problems === 0) log2(pc22.green("Generated files are in sync with the resource descriptors."));
|
|
4911
5126
|
else log2(`
|
|
4912
|
-
${
|
|
5127
|
+
${pc22.red("Out of sync:")} ${stale} file(s) would change. Run ${pc22.bold("flare sync-types")}.`);
|
|
4913
5128
|
return stale + problems > 0 ? 1 : 0;
|
|
4914
5129
|
}
|
|
4915
|
-
if (changed.length === 0 && problems === 0) log2(
|
|
5130
|
+
if (changed.length === 0 && problems === 0) log2(pc22.green("Everything is in sync."));
|
|
4916
5131
|
return problems > 0 ? 1 : 0;
|
|
4917
5132
|
}
|
|
4918
5133
|
|
|
4919
5134
|
// src/commands/user-role.ts
|
|
4920
|
-
import
|
|
5135
|
+
import pc23 from "picocolors";
|
|
4921
5136
|
var ROLE = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
4922
5137
|
var EMAIL = /^[^\s@'"\\]+@[^\s@'"\\]+\.[^\s@'"\\]+$/;
|
|
4923
5138
|
function userRoleSql(email, role) {
|
|
@@ -4931,8 +5146,8 @@ async function setUserRole(email, role, options2 = {}) {
|
|
|
4931
5146
|
const sql2 = userRoleSql(email, role);
|
|
4932
5147
|
const roles = await listRoles(options2);
|
|
4933
5148
|
if (roles && !roles.includes(role)) {
|
|
4934
|
-
log2(
|
|
4935
|
-
log2(
|
|
5149
|
+
log2(pc23.red(`No role "${role}" is registered. Known roles: ${roles.join(", ") || "none"}.`));
|
|
5150
|
+
log2(pc23.dim(`Register it with: flare role:add ${role}${options2.remote ? " --remote" : ""}`));
|
|
4936
5151
|
return 1;
|
|
4937
5152
|
}
|
|
4938
5153
|
const db = readD1Databases(appRoot)[0];
|
|
@@ -4946,31 +5161,31 @@ ${result.output.trim()}`);
|
|
|
4946
5161
|
const [first] = extractJson(result.output);
|
|
4947
5162
|
const updated = first?.results ?? [];
|
|
4948
5163
|
if (updated.length === 0) {
|
|
4949
|
-
log2(
|
|
5164
|
+
log2(pc23.red(`No user with email ${email} in the ${options2.remote ? "remote" : "local"} database. Sign up first, then run this again.`));
|
|
4950
5165
|
return 1;
|
|
4951
5166
|
}
|
|
4952
|
-
log2(
|
|
5167
|
+
log2(pc23.green(`${updated[0].email} now has role "${updated[0].role}" (${options2.remote ? "remote" : "local"}).`));
|
|
4953
5168
|
return 0;
|
|
4954
5169
|
}
|
|
4955
5170
|
|
|
4956
5171
|
// src/utils/install.ts
|
|
4957
5172
|
import { spawn as spawn5 } from "child_process";
|
|
4958
|
-
import { existsSync as
|
|
4959
|
-
import { join as
|
|
5173
|
+
import { existsSync as existsSync26, readFileSync as readFileSync17, readdirSync as readdirSync10 } from "fs";
|
|
5174
|
+
import { join as join28 } from "path";
|
|
4960
5175
|
function expectedPackages(dir) {
|
|
4961
|
-
const npmLock =
|
|
4962
|
-
if (
|
|
5176
|
+
const npmLock = join28(dir, "package-lock.json");
|
|
5177
|
+
if (existsSync26(npmLock)) {
|
|
4963
5178
|
try {
|
|
4964
|
-
const lock = JSON.parse(
|
|
5179
|
+
const lock = JSON.parse(readFileSync17(npmLock, "utf8"));
|
|
4965
5180
|
return Math.max(0, Object.keys(lock.packages ?? {}).length - 1);
|
|
4966
5181
|
} catch {
|
|
4967
5182
|
return 0;
|
|
4968
5183
|
}
|
|
4969
5184
|
}
|
|
4970
|
-
const pnpmLock =
|
|
4971
|
-
if (
|
|
5185
|
+
const pnpmLock = join28(dir, "pnpm-lock.yaml");
|
|
5186
|
+
if (existsSync26(pnpmLock)) {
|
|
4972
5187
|
try {
|
|
4973
|
-
const text =
|
|
5188
|
+
const text = readFileSync17(pnpmLock, "utf8");
|
|
4974
5189
|
const packages = text.slice(text.indexOf("\npackages:"));
|
|
4975
5190
|
return (packages.match(/\n {2}[^\s:]+:/g) ?? []).length;
|
|
4976
5191
|
} catch {
|
|
@@ -4980,15 +5195,15 @@ function expectedPackages(dir) {
|
|
|
4980
5195
|
return 0;
|
|
4981
5196
|
}
|
|
4982
5197
|
function installedPackages(dir) {
|
|
4983
|
-
const root =
|
|
4984
|
-
if (!
|
|
5198
|
+
const root = join28(dir, "node_modules");
|
|
5199
|
+
if (!existsSync26(root)) return 0;
|
|
4985
5200
|
let total = 0;
|
|
4986
5201
|
try {
|
|
4987
|
-
for (const entry of
|
|
5202
|
+
for (const entry of readdirSync10(root, { withFileTypes: true })) {
|
|
4988
5203
|
if (!entry.isDirectory() || entry.name === ".bin") continue;
|
|
4989
5204
|
if (entry.name.startsWith("@")) {
|
|
4990
5205
|
try {
|
|
4991
|
-
total +=
|
|
5206
|
+
total += readdirSync10(join28(root, entry.name)).length;
|
|
4992
5207
|
} catch {
|
|
4993
5208
|
}
|
|
4994
5209
|
} else if (entry.name !== ".pnpm" && entry.name !== ".vite") {
|
|
@@ -5049,7 +5264,7 @@ function createCli() {
|
|
|
5049
5264
|
});
|
|
5050
5265
|
installed = outcome.code === 0;
|
|
5051
5266
|
if (installed) {
|
|
5052
|
-
spin.stop(`Installed ${formatCount(expectedPackages(result.dir) || 0)} packages with ${result.packageManager} ${
|
|
5267
|
+
spin.stop(`Installed ${formatCount(expectedPackages(result.dir) || 0)} packages with ${result.packageManager} ${pc24.dim(`(${formatDuration(outcome.ms)})`)}`);
|
|
5053
5268
|
} else {
|
|
5054
5269
|
spin.error(`${result.packageManager} install failed`);
|
|
5055
5270
|
process.stderr.write(outcome.output);
|
|
@@ -5099,6 +5314,13 @@ function createCli() {
|
|
|
5099
5314
|
cli.command("seed:resource <resource> [count]", "Fill a resource's table with sample rows built from its descriptor").option("--count <rows>", "How many rows: 1000, 25k, 1m (default: 25)").option("--remote", "Seed the deployed database instead of the local one (needs --yes)").option("--truncate", "Delete the table's rows first").option("--seed <number>", "Same number, same rows").option("--env <name>", "Wrangler environment").option("--database <binding>", "D1 binding, when the app has several").option("-y, --yes", "Confirm writing to the remote database").example("flare seed:resource Contact --count 1000").example("flare seed:resource Contact 1m # a million rows").example("flare seed:resource Contact 5k --remote --yes").action(async (resource, count, options2) => {
|
|
5100
5315
|
await seedResourceCommand(resource, { ...options2, count: options2.count ?? count });
|
|
5101
5316
|
});
|
|
5317
|
+
cli.command("diff [filter]", "Show how the code Flare copied into this app (lib/resource) differs from the current version").example("flare diff").example('flare diff store # just the files whose path contains "store"').action((filter) => {
|
|
5318
|
+
diffTracked({ filter });
|
|
5319
|
+
});
|
|
5320
|
+
cli.command("update [filter]", "Replace this app's copies of Flare's files with the current version (discards your edits)").option("-y, --yes", "Confirm overwriting").example("flare update # lists what would change").example("flare update --yes # applies it").action((filter, options2) => {
|
|
5321
|
+
const code = updateTracked({ filter, yes: options2.yes });
|
|
5322
|
+
if (code !== 0) process.exitCode = code;
|
|
5323
|
+
});
|
|
5102
5324
|
cli.command("db:push [...tables]", "Copy rows from the local database to the deployed one (seeded catalogues, reference data)").option("--truncate", "Delete the remote rows of those tables first").option("--database <binding>", "D1 binding, when the app has several").option("--env <name>", "Wrangler environment").option("-y, --yes", "Confirm writing to the deployed database").example("flare db:push products categories --yes").example("flare db:push plans --truncate --yes").action(async (tables, options2) => {
|
|
5103
5325
|
await dbPush(tables, options2);
|
|
5104
5326
|
});
|
|
@@ -5147,6 +5369,6 @@ try {
|
|
|
5147
5369
|
}
|
|
5148
5370
|
}
|
|
5149
5371
|
} catch (error) {
|
|
5150
|
-
console.error(
|
|
5372
|
+
console.error(pc25.red(`\u2716 ${error instanceof Error ? error.message : String(error)}`));
|
|
5151
5373
|
process.exitCode = 1;
|
|
5152
5374
|
}
|