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