@flaredev/cli 0.7.2 → 0.8.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/dist/index.js CHANGED
@@ -9,8 +9,8 @@ import { FLARE_VERSION as FLARE_VERSION3 } from "@flaredev/core";
9
9
 
10
10
  // src/commands/create.ts
11
11
  import { randomBytes } from "crypto";
12
- import { appendFileSync, copyFileSync, existsSync as existsSync3, readdirSync as readdirSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
13
- import { basename, dirname as dirname2, isAbsolute, join as join3, relative, resolve as resolve2 } from "path";
12
+ import { appendFileSync, copyFileSync, existsSync as existsSync4, readdirSync as readdirSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
13
+ import { basename, dirname as dirname2, isAbsolute, join as join4, relative, resolve as resolve2 } from "path";
14
14
  import * as prompts from "@clack/prompts";
15
15
  import pc from "picocolors";
16
16
  import { FLARE_VERSION } from "@flaredev/core";
@@ -282,6 +282,8 @@ var templatesDir = join2(findUp("templates", dirname(fileURLToPath(import.meta.u
282
282
 
283
283
  // src/utils/pm.ts
284
284
  import { spawnSync } from "child_process";
285
+ import { existsSync as existsSync3 } from "fs";
286
+ import { delimiter, join as join3 } from "path";
285
287
  var MANAGERS = ["pnpm", "npm", "yarn", "bun"];
286
288
  function isPackageManager(value) {
287
289
  return MANAGERS.includes(value);
@@ -291,9 +293,9 @@ function detectPackageManager(userAgent = process.env.npm_config_user_agent ?? "
291
293
  return isPackageManager(name) ? name : "pnpm";
292
294
  }
293
295
  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;
296
+ const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
297
+ const suffixes = process.platform === "win32" ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";") : [""];
298
+ return dirs.some((dir) => suffixes.some((suffix) => existsSync3(join3(dir, packageManager + suffix.toLowerCase()))));
297
299
  }
298
300
  function choosePackageManager(explicit) {
299
301
  if (explicit !== void 0) {
@@ -332,7 +334,7 @@ function toAppName(dir) {
332
334
  function flarePackageSpec(pkg, appDir, packageManager) {
333
335
  const repoPackage = resolve2(templatesDir, "../..", pkg);
334
336
  const repoRoot = resolve2(repoPackage, "../..");
335
- const fromCheckout = existsSync3(join3(repoPackage, "package.json")) && existsSync3(join3(repoRoot, "pnpm-workspace.yaml")) && !repoPackage.split(/[\\/]/).includes("node_modules");
337
+ const fromCheckout = existsSync4(join4(repoPackage, "package.json")) && existsSync4(join4(repoRoot, "pnpm-workspace.yaml")) && !repoPackage.split(/[\\/]/).includes("node_modules");
336
338
  if (!fromCheckout) return `^${FLARE_VERSION}`;
337
339
  const rel = relative(repoRoot, appDir);
338
340
  if (rel && !rel.startsWith("..") && !isAbsolute(rel)) return "workspace:*";
@@ -342,7 +344,7 @@ function flarePackageSpec(pkg, appDir, packageManager) {
342
344
  function createApp(target, options2 = {}) {
343
345
  const dir = resolve2(target);
344
346
  const name = toAppName(dir);
345
- if (existsSync3(dir) && readdirSync2(dir).length > 0) {
347
+ if (existsSync4(dir) && readdirSync2(dir).length > 0) {
346
348
  throw new Error(`Directory ${dir} already exists and is not empty.`);
347
349
  }
348
350
  const { packageManager, preferred: prefersPnpm } = choosePackageManager(options2.pm);
@@ -355,23 +357,23 @@ function createApp(target, options2 = {}) {
355
357
  const theme = parseTheme(options2.theme);
356
358
  const inWorkspace = findUp("pnpm-workspace.yaml", dirname2(dir)) !== void 0;
357
359
  const compatibilityDate = options2.compatibilityDate ?? COMPATIBILITY_DATE;
358
- const files = copyTemplate(join3(templatesDir, "app"), dir, {
360
+ const files = copyTemplate(join4(templatesDir, "app"), dir, {
359
361
  APP_NAME: name,
360
362
  COMPAT_DATE: compatibilityDate,
361
363
  PM: packageManager,
362
364
  THEME: theme
363
365
  });
364
366
  if (stack === "next") {
365
- copyTemplate(join3(templatesDir, "next"), dir, { APP_NAME: name, PM: packageManager, THEME: theme });
366
- for (const path of CLOUDFLARE_ONLY) rmSync(join3(dir, path), { recursive: true, force: true });
367
+ copyTemplate(join4(templatesDir, "next"), dir, { APP_NAME: name, PM: packageManager, THEME: theme });
368
+ for (const path of CLOUDFLARE_ONLY) rmSync(join4(dir, path), { recursive: true, force: true });
367
369
  }
368
- writeFileSync3(join3(dir, "lib", "auth-config.ts"), renderAuthConfig(authMethods, authProviders));
369
- appendFileSync(join3(dir, stack === "next" ? ".env.example" : ".dev.vars.example"), devVarsExampleEntries(authProviders));
370
+ writeFileSync3(join4(dir, "lib", "auth-config.ts"), renderAuthConfig(authMethods, authProviders));
371
+ appendFileSync(join4(dir, stack === "next" ? ".env.example" : ".dev.vars.example"), devVarsExampleEntries(authProviders));
370
372
  const shared = { react: APP_DEPENDENCIES.react, "react-dom": APP_DEPENDENCIES["react-dom"] };
371
373
  const uiOnly = Object.fromEntries(
372
374
  Object.entries(APP_DEPENDENCIES).filter(([key]) => !["vinext", "@vinext/cloudflare", "drizzle-orm", "react-server-dom-webpack"].includes(key))
373
375
  );
374
- writeJson(join3(dir, "package.json"), {
376
+ writeJson(join4(dir, "package.json"), {
375
377
  name,
376
378
  version: "0.1.0",
377
379
  private: true,
@@ -405,14 +407,14 @@ function createApp(target, options2 = {}) {
405
407
  }
406
408
  });
407
409
  writeFileSync3(
408
- join3(dir, stack === "next" ? ".env" : ".dev.vars"),
410
+ join4(dir, stack === "next" ? ".env" : ".dev.vars"),
409
411
  `BETTER_AUTH_SECRET=${randomBytes(32).toString("base64")}
410
412
  RESEND_API_KEY=
411
413
  MAIL_FROM=
412
414
  ${devVarsEntries(authProviders)}`
413
415
  );
414
416
  if (packageManager === "pnpm" && !inWorkspace) {
415
- writeFileSync3(join3(dir, "pnpm-workspace.yaml"), `allowBuilds:
417
+ writeFileSync3(join4(dir, "pnpm-workspace.yaml"), `allowBuilds:
416
418
  ${ALLOW_BUILDS}
417
419
  `);
418
420
  }
@@ -426,9 +428,9 @@ ${ALLOW_BUILDS}
426
428
  function copyLockfile(packageManager, dir) {
427
429
  const file = packageManager === "pnpm" ? "pnpm-lock.yaml" : packageManager === "yarn" ? "yarn.lock" : "package-lock.json";
428
430
  if (!file) return false;
429
- const source = join3(templatesDir, "locks", file);
430
- if (!existsSync3(source)) return false;
431
- copyFileSync(source, join3(dir, file));
431
+ const source = join4(templatesDir, "locks", file);
432
+ if (!existsSync4(source)) return false;
433
+ copyFileSync(source, join4(dir, file));
432
434
  return true;
433
435
  }
434
436
  function printNextSteps(result, installed) {
@@ -448,15 +450,33 @@ function printNextSteps(result, installed) {
448
450
  }
449
451
 
450
452
  // src/commands/gen-billing.ts
451
- import { appendFileSync as appendFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync7 } from "fs";
452
- import { dirname as dirname6, join as join12 } from "path";
453
+ import { appendFileSync as appendFileSync2, existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync7 } from "fs";
454
+ import { dirname as dirname6, join as join13 } from "path";
453
455
  import pc6 from "picocolors";
454
456
 
455
457
  // src/generator/grammar.ts
456
458
  import { camelCase, FILE_CATEGORIES, STRING_FORMATS } from "@flaredev/core";
457
459
  var SIMPLE_KINDS = ["string", "text", "int", "float", "boolean", "date", "datetime"];
458
- var FORMAT_ALIASES = { phone: "tel" };
459
- var ALL_KINDS = [...SIMPLE_KINDS, ...STRING_FORMATS, "phone", "enum", "select", "radio", "multiselect", "file", "belongsTo", "hasMany"];
460
+ var FORMAT_ALIASES = { phone: "tel", website: "url" };
461
+ var NUMBER_SHORTHANDS = {
462
+ money: { kind: "float", format: "money" },
463
+ percent: { kind: "float", format: "percent" },
464
+ rating: { kind: "int", format: "rating" }
465
+ };
466
+ var ALL_KINDS = [
467
+ ...SIMPLE_KINDS,
468
+ ...STRING_FORMATS,
469
+ "phone",
470
+ "website",
471
+ ...Object.keys(NUMBER_SHORTHANDS),
472
+ "enum",
473
+ "select",
474
+ "radio",
475
+ "multiselect",
476
+ "file",
477
+ "belongsTo",
478
+ "hasMany"
479
+ ];
460
480
  var FieldGrammarError = class extends Error {
461
481
  constructor(message) {
462
482
  super(message);
@@ -541,6 +561,10 @@ function parseField(spec) {
541
561
  } else if (STRING_FORMATS.includes(type) || type in FORMAT_ALIASES) {
542
562
  field.kind = "string";
543
563
  field.format = FORMAT_ALIASES[type] ?? type;
564
+ } else if (type in NUMBER_SHORTHANDS) {
565
+ const shorthand = NUMBER_SHORTHANDS[type];
566
+ field.kind = shorthand.kind;
567
+ field.numberFormat = shorthand.format;
544
568
  } else if (match = /^(enum|select|radio|multiselect)\((.*)\)$/.exec(type)) {
545
569
  const choice = match[1];
546
570
  field.kind = choice === "multiselect" ? "multiselect" : "enum";
@@ -613,7 +637,7 @@ import { defineResource, field as builders, kebabCase, pascalCase } from "@flare
613
637
 
614
638
  // src/generator/markers.ts
615
639
  import { createHash } from "crypto";
616
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
640
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
617
641
  import { dirname as dirname3 } from "path";
618
642
  var START = "// generated:start";
619
643
  var END = "// generated:end";
@@ -663,7 +687,7 @@ ${indentBlock(body, indent)}${indent}${END}
663
687
  ${after}`;
664
688
  }
665
689
  function inspectGenerated(path, content) {
666
- if (!existsSync4(path)) return "missing";
690
+ if (!existsSync5(path)) return "missing";
667
691
  const parts = splitMarkers(readFileSync3(path, "utf8"));
668
692
  if (!parts) return "unmarked";
669
693
  if (parts.hash && hashBlock(parts.block) !== parts.hash) return "drift";
@@ -680,7 +704,7 @@ var DriftError = class extends Error {
680
704
  };
681
705
  function writeGenerated(path, content, options2 = {}) {
682
706
  const hash = hashBlock(content);
683
- if (!existsSync4(path)) {
707
+ if (!existsSync5(path)) {
684
708
  mkdirSync2(dirname3(path), { recursive: true });
685
709
  writeFileSync4(path, joinMarkers({ before: options2.header ?? "", block: content, after: "", indent: "", hash }));
686
710
  return "create";
@@ -708,6 +732,7 @@ function options(field) {
708
732
  if (!field.required) opts.required = false;
709
733
  if (field.unique) opts.unique = true;
710
734
  if (field.format) opts.format = field.format;
735
+ if (field.numberFormat) opts.format = field.numberFormat;
711
736
  if (field.widget) opts.widget = field.widget;
712
737
  if (field.kind === "belongsTo" && !field.required) opts.onDelete = "set null";
713
738
  if (field.maxBytes !== void 0) opts.maxBytes = field.maxBytes;
@@ -1446,17 +1471,17 @@ var billingDevVarsExampleEntries = () => [
1446
1471
  ].join("\n");
1447
1472
 
1448
1473
  // src/generator/load.ts
1449
- import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
1450
- import { join as join4, resolve as resolve3 } from "path";
1474
+ import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
1475
+ import { join as join5, resolve as resolve3 } from "path";
1451
1476
  import { createJiti } from "jiti";
1452
1477
  async function loadResources(root) {
1453
1478
  const appRoot = resolve3(root);
1454
- const dir = join4(appRoot, "resources");
1455
- if (!existsSync5(dir)) return [];
1456
- const jiti = createJiti(join4(appRoot, "package.json"), { alias: { "@": appRoot }, moduleCache: false, fsCache: false });
1479
+ const dir = join5(appRoot, "resources");
1480
+ if (!existsSync6(dir)) return [];
1481
+ const jiti = createJiti(join5(appRoot, "package.json"), { alias: { "@": appRoot }, moduleCache: false, fsCache: false });
1457
1482
  const loaded = [];
1458
1483
  for (const file of readdirSync3(dir).filter((name) => name.endsWith(".resource.ts")).sort()) {
1459
- const path = join4(dir, file);
1484
+ const path = join5(dir, file);
1460
1485
  const mod = await jiti.import(path);
1461
1486
  const resource = mod.default;
1462
1487
  if (!resource || typeof resource !== "object" || !("fields" in resource) || !("table" in resource)) {
@@ -1474,20 +1499,20 @@ async function loadResources(root) {
1474
1499
 
1475
1500
  // src/generator/migrations.ts
1476
1501
  import { spawn as spawn4 } from "child_process";
1477
- import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
1478
- import { join as join9 } from "path";
1502
+ import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
1503
+ import { join as join10 } from "path";
1479
1504
 
1480
1505
  // src/commands/run.ts
1481
1506
  import { spawn as spawn3 } from "child_process";
1482
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
1483
- import { dirname as dirname4, join as join8, resolve as resolve4 } from "path";
1507
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
1508
+ import { dirname as dirname4, join as join9, resolve as resolve4 } from "path";
1484
1509
  import pc4 from "picocolors";
1485
1510
 
1486
1511
  // src/tunnel.ts
1487
1512
  import { spawn, spawnSync as spawnSync2 } from "child_process";
1488
- import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
1513
+ import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
1489
1514
  import { homedir } from "os";
1490
- import { join as join5 } from "path";
1515
+ import { join as join6 } from "path";
1491
1516
  import pc2 from "picocolors";
1492
1517
  var TUNNEL_URL = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
1493
1518
  var LOCAL_URL = /https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|0\.0\.0\.0):\d+/;
@@ -1512,7 +1537,7 @@ function releaseAsset(platform = process.platform, arch = process.arch) {
1512
1537
  throw new Error(`No cloudflared build for ${platform}/${arch}. Install it yourself: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/`);
1513
1538
  }
1514
1539
  var binName = process.platform === "win32" ? "cloudflared.exe" : "cloudflared";
1515
- var cacheDir = () => join5(homedir(), ".flare", "bin");
1540
+ var cacheDir = () => join6(homedir(), ".flare", "bin");
1516
1541
  function onPath() {
1517
1542
  const probe = spawnSync2(process.platform === "win32" ? "where" : "which", ["cloudflared"], { encoding: "utf8" });
1518
1543
  return probe.status === 0 ? probe.stdout.split(/\r?\n/)[0]?.trim() ?? null : null;
@@ -1521,7 +1546,7 @@ async function download(log2) {
1521
1546
  const asset = releaseAsset();
1522
1547
  const dir = cacheDir();
1523
1548
  mkdirSync3(dir, { recursive: true });
1524
- const target = join5(dir, binName);
1549
+ const target = join6(dir, binName);
1525
1550
  log2(pc2.dim(`Downloading cloudflared (one time) into ${dir}\u2026`));
1526
1551
  const response = await fetch(`https://github.com/cloudflare/cloudflared/releases/latest/download/${asset}`);
1527
1552
  if (!response.ok) throw new Error(`Downloading cloudflared failed: HTTP ${response.status}.`);
@@ -1542,8 +1567,8 @@ async function download(log2) {
1542
1567
  async function ensureCloudflared(log2 = console.log) {
1543
1568
  const found = onPath();
1544
1569
  if (found) return found;
1545
- const cached = join5(cacheDir(), binName);
1546
- if (existsSync6(cached)) return cached;
1570
+ const cached = join6(cacheDir(), binName);
1571
+ if (existsSync7(cached)) return cached;
1547
1572
  return download(log2);
1548
1573
  }
1549
1574
  async function openTunnel(localUrl, options2 = {}) {
@@ -1599,16 +1624,16 @@ function tunnelBanner(publicUrl, localUrl) {
1599
1624
 
1600
1625
  // src/commands/deploy.ts
1601
1626
  import { randomBytes as randomBytes2 } from "crypto";
1602
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
1603
- import { join as join7 } from "path";
1627
+ import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
1628
+ import { join as join8 } from "path";
1604
1629
  import { createZoneClient } from "@flaredev/core/security";
1605
1630
  import { createJiti as createJiti2 } from "jiti";
1606
1631
  import pc3 from "picocolors";
1607
1632
 
1608
1633
  // src/utils/wrangler.ts
1609
1634
  import { spawn as spawn2 } from "child_process";
1610
- import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
1611
- import { join as join6 } from "path";
1635
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
1636
+ import { join as join7 } from "path";
1612
1637
  import { parse as parseJsonc } from "jsonc-parser";
1613
1638
  function runWrangler(wranglerBin, args, cwd, options2 = {}) {
1614
1639
  return new Promise((resolve6, reject) => {
@@ -1633,8 +1658,8 @@ ${output.trim()}`);
1633
1658
  return JSON.parse(output.slice(start, end + 1));
1634
1659
  }
1635
1660
  function readD1Databases(appRoot) {
1636
- const path = join6(appRoot, "wrangler.jsonc");
1637
- if (!existsSync7(path)) return [];
1661
+ const path = join7(appRoot, "wrangler.jsonc");
1662
+ if (!existsSync8(path)) return [];
1638
1663
  const config = parseJsonc(readFileSync4(path, "utf8"));
1639
1664
  return (config?.d1_databases ?? []).filter((db) => db.binding && db.database_name);
1640
1665
  }
@@ -1675,15 +1700,15 @@ function declaredSecretNames(devVarsExample) {
1675
1700
  return devVarsExample.split(/\r?\n/).map((line) => /^([A-Z][A-Z0-9_]*)=/.exec(line.trim())?.[1]).filter((name) => Boolean(name));
1676
1701
  }
1677
1702
  async function loadSecurityConfig(appRoot) {
1678
- const path = join7(appRoot, "security.config.ts");
1679
- if (!existsSync8(path)) return null;
1680
- const jiti = createJiti2(join7(appRoot, "package.json"), { moduleCache: false, fsCache: false });
1703
+ const path = join8(appRoot, "security.config.ts");
1704
+ if (!existsSync9(path)) return null;
1705
+ const jiti = createJiti2(join8(appRoot, "package.json"), { moduleCache: false, fsCache: false });
1681
1706
  const mod = await jiti.import(path);
1682
1707
  if (!mod.default) throw new Error("security.config.ts must `export default defineSecurity({ ... })`.");
1683
1708
  return mod.default;
1684
1709
  }
1685
1710
  async function provisionZoneSecurity(appRoot, config, credentials, options2) {
1686
- const app = JSON.parse(readFileSync5(join7(appRoot, "package.json"), "utf8")).name ?? "app";
1711
+ const app = JSON.parse(readFileSync5(join8(appRoot, "package.json"), "utf8")).name ?? "app";
1687
1712
  const zone = createZoneClient({ ...credentials, app, fetch: options2.fetch });
1688
1713
  if (config.zone?.rateLimits) {
1689
1714
  const result = await zone.syncRateLimits(config.zone.rateLimits);
@@ -1703,7 +1728,7 @@ async function runDeploy(args, context3) {
1703
1728
  const envArgs = plan.env ? ["--env", plan.env] : [];
1704
1729
  const step = (message) => log2(`
1705
1730
  ${pc3.bold(pc3.cyan("flare"))} ${message}`);
1706
- const databases = plan.skipMigrations ? [] : readD1Databases(appRoot).filter((db) => existsSync8(join7(appRoot, db.migrations_dir ?? "migrations")));
1731
+ const databases = plan.skipMigrations ? [] : readD1Databases(appRoot).filter((db) => existsSync9(join8(appRoot, db.migrations_dir ?? "migrations")));
1707
1732
  const migrateAfterDeploy = [];
1708
1733
  const migrate2 = async (db) => {
1709
1734
  step(`Applying migrations to ${db.database_name} (remote)`);
@@ -1762,8 +1787,8 @@ ${put.output.trim()}`);
1762
1787
  existing.add(name);
1763
1788
  }
1764
1789
  }
1765
- const examplePath = join7(appRoot, ".dev.vars.example");
1766
- const optional = existsSync8(examplePath) ? declaredSecretNames(readFileSync5(examplePath, "utf8")).filter((name) => !existing.has(name)) : [];
1790
+ const examplePath = join8(appRoot, ".dev.vars.example");
1791
+ const optional = existsSync9(examplePath) ? declaredSecretNames(readFileSync5(examplePath, "utf8")).filter((name) => !existing.has(name)) : [];
1767
1792
  if (optional.length > 0) {
1768
1793
  step("Optional secrets not set in production (features using them stay off):");
1769
1794
  for (const name of optional) log2(` wrangler secret put ${name}${plan.env ? ` --env ${plan.env}` : ""}`);
@@ -1798,7 +1823,7 @@ function findAppRoot(cwd) {
1798
1823
  while (dir) {
1799
1824
  const root = findUp("package.json", dir);
1800
1825
  if (!root) break;
1801
- const pkg = JSON.parse(readFileSync6(join8(root, "package.json"), "utf8"));
1826
+ const pkg = JSON.parse(readFileSync6(join9(root, "package.json"), "utf8"));
1802
1827
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1803
1828
  if (deps["@flaredev/core"] || deps.vinext) return root;
1804
1829
  const parent = dirname4(root);
@@ -1807,15 +1832,15 @@ function findAppRoot(cwd) {
1807
1832
  throw new Error("Not inside a Flare app (no package.json depending on @flaredev/core found). Run this from your app directory.");
1808
1833
  }
1809
1834
  function resolveBin(appRoot, pkgName, binName2) {
1810
- const pkgDir = findUp(join8("node_modules", pkgName, "package.json"), appRoot);
1835
+ const pkgDir = findUp(join9("node_modules", pkgName, "package.json"), appRoot);
1811
1836
  if (!pkgDir) {
1812
1837
  throw new Error(`Cannot find ${pkgName} in ${appRoot}. Install dependencies first (e.g. pnpm install).`);
1813
1838
  }
1814
- const manifestPath = join8(pkgDir, "node_modules", pkgName, "package.json");
1839
+ const manifestPath = join9(pkgDir, "node_modules", pkgName, "package.json");
1815
1840
  const { bin } = JSON.parse(readFileSync6(manifestPath, "utf8"));
1816
1841
  const relativeBin = typeof bin === "string" ? bin : bin?.[binName2];
1817
1842
  if (!relativeBin) throw new Error(`${pkgName} does not provide a "${binName2}" executable.`);
1818
- return join8(dirname4(manifestPath), relativeBin);
1843
+ return join9(dirname4(manifestPath), relativeBin);
1819
1844
  }
1820
1845
  var NEXT_COMMANDS = {
1821
1846
  dev: { pkg: "next", bin: "next", args: ["dev"] },
@@ -1888,7 +1913,7 @@ async function runDelegated(command, forwarded, cwd = process.cwd()) {
1888
1913
  if (code !== 0) return code;
1889
1914
  }
1890
1915
  const built = next ? ".next" : "dist/server/wrangler.json";
1891
- if (command === "start" && !wantsHelp && !existsSync9(join8(appRoot, built))) {
1916
+ if (command === "start" && !wantsHelp && !existsSync10(join9(appRoot, built))) {
1892
1917
  console.log("No production build found; running `flare build` first.\n");
1893
1918
  const code = await runDelegated("build", [], appRoot);
1894
1919
  if (code !== 0) return code;
@@ -1939,9 +1964,9 @@ function snapshotColumns(snapshot) {
1939
1964
  }
1940
1965
 
1941
1966
  // src/generator/migrations.ts
1942
- var listSql = (dir) => existsSync10(dir) ? readdirSync4(dir).filter((file) => file.endsWith(".sql")) : [];
1967
+ var listSql = (dir) => existsSync11(dir) ? readdirSync4(dir).filter((file) => file.endsWith(".sql")) : [];
1943
1968
  async function generateSchemaMigration(appRoot, name, options2 = {}) {
1944
- const dir = join9(appRoot, "migrations");
1969
+ const dir = join10(appRoot, "migrations");
1945
1970
  const before = new Set(listSql(dir));
1946
1971
  const bin = resolveBin(appRoot, "drizzle-kit", "drizzle-kit");
1947
1972
  const { code, output } = await new Promise((resolve6, reject) => {
@@ -1969,7 +1994,7 @@ If it asked whether a column was renamed, run \`pnpm exec drizzle-kit generate\`
1969
1994
  for (const file of created) {
1970
1995
  const previous = previousSnapshot(dir, file);
1971
1996
  if (!previous) continue;
1972
- const path = join9(dir, file);
1997
+ const path = join10(dir, file);
1973
1998
  const { sql: sql2, changes } = repairRebuildMigration(readFileSync7(path, "utf8"), snapshotColumns(previous));
1974
1999
  if (changes.length) {
1975
2000
  writeFileSync6(path, sql2);
@@ -1980,32 +2005,32 @@ If it asked whether a column was renamed, run \`pnpm exec drizzle-kit generate\`
1980
2005
  }
1981
2006
  function previousSnapshot(dir, migrationFile) {
1982
2007
  const prefix = /^(\d+)_/.exec(migrationFile)?.[1];
1983
- const metaDir = join9(dir, "meta");
1984
- if (!prefix || !existsSync10(metaDir)) return void 0;
2008
+ const metaDir = join10(dir, "meta");
2009
+ if (!prefix || !existsSync11(metaDir)) return void 0;
1985
2010
  const earlier = readdirSync4(metaDir).filter((file) => /^\d+_snapshot\.json$/.test(file) && file < `${prefix}_snapshot.json`).sort().pop();
1986
- return earlier ? JSON.parse(readFileSync7(join9(metaDir, earlier), "utf8")) : void 0;
2011
+ return earlier ? JSON.parse(readFileSync7(join10(metaDir, earlier), "utf8")) : void 0;
1987
2012
  }
1988
2013
 
1989
2014
  // src/generator/plan.ts
1990
- import { copyFileSync as copyFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync4, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
1991
- import { dirname as dirname5, join as join11, sep } from "path";
2015
+ import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync4, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
2016
+ import { dirname as dirname5, join as join12, sep } from "path";
1992
2017
  import pc5 from "picocolors";
1993
2018
 
1994
2019
  // src/generator/policy.ts
1995
- import { existsSync as existsSync11, readdirSync as readdirSync5 } from "fs";
1996
- import { join as join10, resolve as resolve5 } from "path";
2020
+ import { existsSync as existsSync12, readdirSync as readdirSync5 } from "fs";
2021
+ import { join as join11, resolve as resolve5 } from "path";
1997
2022
  import { camelCase as camelCase2, kebabCase as kebabCase2 } from "@flaredev/core";
1998
2023
  import { createJiti as createJiti3 } from "jiti";
1999
2024
  var policyPath = (name) => `policies/${kebabCase2(name)}.policy.ts`;
2000
2025
  var policyLocal = (stem) => `${camelCase2(stem)}Policy`;
2001
2026
  async function loadPolicies(root) {
2002
2027
  const appRoot = resolve5(root);
2003
- const dir = join10(appRoot, "policies");
2004
- if (!existsSync11(dir)) return [];
2005
- const jiti = createJiti3(join10(appRoot, "package.json"), { moduleCache: false, fsCache: false });
2028
+ const dir = join11(appRoot, "policies");
2029
+ if (!existsSync12(dir)) return [];
2030
+ const jiti = createJiti3(join11(appRoot, "package.json"), { moduleCache: false, fsCache: false });
2006
2031
  const loaded = [];
2007
2032
  for (const file of readdirSync5(dir).filter((name) => name.endsWith(".policy.ts")).sort()) {
2008
- const mod = await jiti.import(join10(dir, file));
2033
+ const mod = await jiti.import(join11(dir, file));
2009
2034
  const policy = mod.default;
2010
2035
  if (!policy || typeof policy !== "object" || !("resource" in policy) || !Array.isArray(policy.read)) {
2011
2036
  throw new Error(`policies/${file} must \`export default definePolicy({ ... })\`.`);
@@ -2677,7 +2702,7 @@ function planFiles(all, policies, stack = "cloudflare") {
2677
2702
  }
2678
2703
  function applyPlan(appRoot, plan, options2 = {}) {
2679
2704
  return plan.map(({ path, content, header }) => {
2680
- const absolute = join11(appRoot, path);
2705
+ const absolute = join12(appRoot, path);
2681
2706
  const status = inspectGenerated(absolute, content);
2682
2707
  if (status === "unmarked") return { path, status, outcome: "skipped-unmarked" };
2683
2708
  if (status === "drift" && !options2.force) return { path, status, outcome: "skipped-drift" };
@@ -2696,22 +2721,22 @@ function applyPlan(appRoot, plan, options2 = {}) {
2696
2721
  function findOrphans(appRoot, all) {
2697
2722
  const planned = new Set(planFiles(all).map((file) => file.path));
2698
2723
  const candidates = [];
2699
- const list2 = (dir, match) => existsSync12(join11(appRoot, dir)) ? readdirSync6(join11(appRoot, dir)).filter(match).map((name) => `${dir}/${name}`) : [];
2724
+ const list2 = (dir, match) => existsSync13(join12(appRoot, dir)) ? readdirSync6(join12(appRoot, dir)).filter(match).map((name) => `${dir}/${name}`) : [];
2700
2725
  candidates.push(...list2("db/schema", (name) => name.endsWith(".ts")));
2701
2726
  candidates.push(...list2("resources", (name) => /\.(client|validators)\.ts$/.test(name)));
2702
2727
  for (const dir of list2("app/api", () => true)) {
2703
- for (const file of [`${dir}/route.ts`, `${dir}/[id]/route.ts`]) if (existsSync12(join11(appRoot, file))) candidates.push(file);
2728
+ for (const file of [`${dir}/route.ts`, `${dir}/[id]/route.ts`]) if (existsSync13(join12(appRoot, file))) candidates.push(file);
2704
2729
  }
2705
2730
  for (const dir of list2("app/dashboard", () => true)) {
2706
2731
  const pages = [`${dir}/page.tsx`, `${dir}/new/page.tsx`, `${dir}/[id]/page.tsx`, `${dir}/[id]/edit/page.tsx`];
2707
2732
  const skeletons = [`${dir}/loading.tsx`, `${dir}/new/loading.tsx`, `${dir}/[id]/loading.tsx`, `${dir}/[id]/edit/loading.tsx`];
2708
2733
  for (const file of [...pages, ...skeletons]) {
2709
- if (existsSync12(join11(appRoot, file))) candidates.push(file);
2734
+ if (existsSync13(join12(appRoot, file))) candidates.push(file);
2710
2735
  }
2711
2736
  }
2712
2737
  const orphans = [];
2713
2738
  for (const path of candidates) {
2714
- const match = HEADER_PATTERN.exec(readFileSync8(join11(appRoot, path), "utf8"));
2739
+ const match = HEADER_PATTERN.exec(readFileSync8(join12(appRoot, path), "utf8"));
2715
2740
  if (match && !planned.has(path)) orphans.push({ path, resource: match[1] });
2716
2741
  }
2717
2742
  return orphans;
@@ -2722,10 +2747,10 @@ function ensureSupportFiles(appRoot, log2) {
2722
2747
  (name) => `lib/resource/${name}`
2723
2748
  );
2724
2749
  for (const path of ["lib/api.ts", "lib/cache.ts", ...engine]) {
2725
- const target = join11(appRoot, path);
2726
- if (existsSync12(target)) continue;
2727
- const source = [join11(templatesDir, "next", path), join11(templatesDir, "app", path)].find(
2728
- (candidate) => (stack === "next" || !candidate.includes(`${sep}next${sep}`)) && existsSync12(candidate)
2750
+ const target = join12(appRoot, path);
2751
+ if (existsSync13(target)) continue;
2752
+ const source = [join12(templatesDir, "next", path), join12(templatesDir, "app", path)].find(
2753
+ (candidate) => (stack === "next" || !candidate.includes(`${sep}next${sep}`)) && existsSync13(candidate)
2729
2754
  );
2730
2755
  if (!source) continue;
2731
2756
  mkdirSync4(dirname5(target), { recursive: true });
@@ -2757,14 +2782,14 @@ function billingFiles(appName) {
2757
2782
  }
2758
2783
  var RETIRED_FILES = ["app/api/billing/status/route.ts", "components/billing/subscribe-button.tsx"];
2759
2784
  function appendVars(appRoot, target) {
2760
- const path = join12(appRoot, target);
2761
- const text = existsSync13(path) ? readFileSync9(path, "utf8") : "";
2785
+ const path = join13(appRoot, target);
2786
+ const text = existsSync14(path) ? readFileSync9(path, "utf8") : "";
2762
2787
  if (/STRIPE_SECRET_KEY/.test(text)) return false;
2763
2788
  appendFileSync2(path, target === ".dev.vars" ? billingDevVarsEntries() : billingDevVarsExampleEntries());
2764
2789
  return true;
2765
2790
  }
2766
2791
  function addStripeDependency(appRoot) {
2767
- const path = join12(appRoot, "package.json");
2792
+ const path = join13(appRoot, "package.json");
2768
2793
  const pkg = JSON.parse(readFileSync9(path, "utf8"));
2769
2794
  pkg.dependencies ??= {};
2770
2795
  if (pkg.dependencies.stripe) return false;
@@ -2774,8 +2799,8 @@ function addStripeDependency(appRoot) {
2774
2799
  }
2775
2800
  function writeBillingResource(appRoot, name, force, log2) {
2776
2801
  const relative3 = descriptorPath(name);
2777
- const path = join12(appRoot, relative3);
2778
- if (!existsSync13(path)) {
2802
+ const path = join13(appRoot, relative3);
2803
+ if (!existsSync14(path)) {
2779
2804
  mkdirSync5(dirname6(path), { recursive: true });
2780
2805
  writeFileSync7(path, renderBillingDescriptor(name));
2781
2806
  log2(`${pc6.green("create".padEnd(9))} ${relative3}`);
@@ -2805,25 +2830,25 @@ async function genBilling(options2 = {}) {
2805
2830
  for (const name of ["Plan", "Customer", "Purchase"]) writeBillingResource(appRoot, name, options2.force, log2);
2806
2831
  for (const [name, roles] of Object.entries(BILLING_POLICIES)) {
2807
2832
  const relative3 = policyPath(name);
2808
- if (existsSync13(join12(appRoot, relative3))) {
2833
+ if (existsSync14(join13(appRoot, relative3))) {
2809
2834
  log2(`${pc6.dim("kept".padEnd(9))} ${relative3} (already exists)`);
2810
2835
  continue;
2811
2836
  }
2812
- mkdirSync5(join12(appRoot, "policies"), { recursive: true });
2813
- writeFileSync7(join12(appRoot, relative3), renderPolicy(name, roles));
2837
+ mkdirSync5(join13(appRoot, "policies"), { recursive: true });
2838
+ writeFileSync7(join13(appRoot, relative3), renderPolicy(name, roles));
2814
2839
  log2(`${pc6.green("create".padEnd(9))} ${relative3}`);
2815
2840
  }
2816
2841
  const files = [];
2817
- const appName = JSON.parse(readFileSync9(join12(appRoot, "package.json"), "utf8")).name ?? "app";
2842
+ const appName = JSON.parse(readFileSync9(join13(appRoot, "package.json"), "utf8")).name ?? "app";
2818
2843
  for (const { path, content } of billingFiles(appName)) {
2819
- const outcome = writeGenerated(join12(appRoot, path), content, { header: billingHeader(), force: options2.force });
2844
+ const outcome = writeGenerated(join13(appRoot, path), content, { header: billingHeader(), force: options2.force });
2820
2845
  files.push({ path, status: "missing", outcome });
2821
2846
  const label = outcome === "create" ? pc6.green("create") : outcome === "update" ? pc6.yellow("update") : pc6.dim("identical");
2822
2847
  log2(`${label.padEnd(18)} ${path}`);
2823
2848
  }
2824
2849
  for (const path of RETIRED_FILES) {
2825
- const absolute = join12(appRoot, path);
2826
- if (!existsSync13(absolute)) continue;
2850
+ const absolute = join13(appRoot, path);
2851
+ if (!existsSync14(absolute)) continue;
2827
2852
  const parts = splitMarkers(readFileSync9(absolute, "utf8"));
2828
2853
  const untouched = parts && parts.before.includes("flare gen billing") && !parts.after.trim() && parts.hash !== void 0 && hashBlock(parts.block) === parts.hash;
2829
2854
  if (untouched) {
@@ -2868,8 +2893,8 @@ async function genBilling(options2 = {}) {
2868
2893
  }
2869
2894
 
2870
2895
  // src/commands/gen-security.ts
2871
- import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
2872
- import { dirname as dirname7, join as join13 } from "path";
2896
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
2897
+ import { dirname as dirname7, join as join14 } from "path";
2873
2898
  import pc7 from "picocolors";
2874
2899
 
2875
2900
  // src/generator/security.ts
@@ -3397,8 +3422,8 @@ function securityFiles(appName) {
3397
3422
  }
3398
3423
  var PASS_THROUGH_HEADER = /^\/\/ Request protection for worker\/index\.ts\.[\s\S]*?\n(?=\/\/ generated:start)/;
3399
3424
  function writeOnce(appRoot, relative3, content, log2) {
3400
- const path = join13(appRoot, relative3);
3401
- if (existsSync14(path)) {
3425
+ const path = join14(appRoot, relative3);
3426
+ if (existsSync15(path)) {
3402
3427
  log2(`${pc7.dim("kept".padEnd(9))} ${relative3} (already exists)`);
3403
3428
  return;
3404
3429
  }
@@ -3409,14 +3434,14 @@ function writeOnce(appRoot, relative3, content, log2) {
3409
3434
  async function genSecurity(options2 = {}) {
3410
3435
  const log2 = options2.log ?? ((message) => console.log(message));
3411
3436
  const appRoot = findAppRoot(options2.cwd ?? process.cwd());
3412
- const appName = JSON.parse(readFileSync10(join13(appRoot, "package.json"), "utf8")).name ?? "app";
3437
+ const appName = JSON.parse(readFileSync10(join14(appRoot, "package.json"), "utf8")).name ?? "app";
3413
3438
  writeOnce(appRoot, "security.config.ts", renderSecurityConfig(), log2);
3414
3439
  writeOnce(appRoot, descriptorPath("SecurityEvent"), renderSecurityDescriptor(), log2);
3415
3440
  writeOnce(appRoot, policyPath("SecurityEvent"), renderPolicy("SecurityEvent", SECURITY_EVENT_POLICY), log2);
3416
3441
  const files = [];
3417
3442
  const write = (path, content, header) => {
3418
- const absolute = join13(appRoot, path);
3419
- if (path === "lib/security.ts" && existsSync14(absolute)) {
3443
+ const absolute = join14(appRoot, path);
3444
+ if (path === "lib/security.ts" && existsSync15(absolute)) {
3420
3445
  const source = readFileSync10(absolute, "utf8");
3421
3446
  if (PASS_THROUGH_HEADER.test(source)) writeFileSync8(absolute, source.replace(PASS_THROUGH_HEADER, securityHeader()));
3422
3447
  }
@@ -3426,12 +3451,12 @@ async function genSecurity(options2 = {}) {
3426
3451
  log2(`${label.padEnd(18)} ${path}`);
3427
3452
  };
3428
3453
  for (const { path, content } of securityFiles(appName)) write(path, content, securityHeader());
3429
- const navPath = join13(appRoot, "lib/dashboard-nav.ts");
3430
- if (existsSync14(navPath) && splitMarkers(readFileSync10(navPath, "utf8"))) write("lib/dashboard-nav.ts", renderAdminNavBlock());
3454
+ const navPath = join14(appRoot, "lib/dashboard-nav.ts");
3455
+ if (existsSync15(navPath) && splitMarkers(readFileSync10(navPath, "utf8"))) write("lib/dashboard-nav.ts", renderAdminNavBlock());
3431
3456
  else log2(pc7.yellow(`lib/dashboard-nav.ts not found: link to /dashboard/security from your dashboard sidebar yourself.`));
3432
- const wranglerPath = join13(appRoot, "wrangler.jsonc");
3457
+ const wranglerPath = join14(appRoot, "wrangler.jsonc");
3433
3458
  let bindings = [];
3434
- if (existsSync14(wranglerPath)) {
3459
+ if (existsSync15(wranglerPath)) {
3435
3460
  const { text, added } = addSecurityBindings(readFileSync10(wranglerPath, "utf8"));
3436
3461
  bindings = added;
3437
3462
  if (added.length) {
@@ -3466,14 +3491,14 @@ async function genSecurity(options2 = {}) {
3466
3491
  }
3467
3492
 
3468
3493
  // src/commands/theme.ts
3469
- import { existsSync as existsSync15, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
3470
- import { join as join14 } from "path";
3494
+ import { existsSync as existsSync16, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
3495
+ import { join as join15 } from "path";
3471
3496
  import pc8 from "picocolors";
3472
3497
  var THEME_LINE = /theme: "([a-z]+)" as ThemeName,/;
3473
3498
  function setTheme(name, cwd = process.cwd(), log2 = console.log) {
3474
3499
  const appRoot = findAppRoot(cwd);
3475
- const path = join14(appRoot, "lib/site.ts");
3476
- if (!existsSync15(path)) throw new Error("lib/site.ts not found: this app was created before themes. Add it from the Flare app template.");
3500
+ const path = join15(appRoot, "lib/site.ts");
3501
+ if (!existsSync16(path)) throw new Error("lib/site.ts not found: this app was created before themes. Add it from the Flare app template.");
3477
3502
  const source = readFileSync11(path, "utf8");
3478
3503
  const current = THEME_LINE.exec(source)?.[1];
3479
3504
  if (!name) {
@@ -3566,8 +3591,8 @@ async function askCreateQuestions(given, appName) {
3566
3591
  }
3567
3592
 
3568
3593
  // src/commands/gen.ts
3569
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "fs";
3570
- import { dirname as dirname8, join as join15 } from "path";
3594
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "fs";
3595
+ import { dirname as dirname8, join as join16 } from "path";
3571
3596
  import { camelCase as camelCase5, defineResource as defineResource2, relationGraph as relationGraph2 } from "@flaredev/core";
3572
3597
  import pc10 from "picocolors";
3573
3598
  function validateFields(name, fields, others, log2) {
@@ -3588,8 +3613,8 @@ async function genResource(rawName, options2) {
3588
3613
  const appRoot = findAppRoot(options2.cwd ?? process.cwd());
3589
3614
  const name = resourceName(rawName);
3590
3615
  const relativePath = descriptorPath(name);
3591
- const path = join15(appRoot, relativePath);
3592
- const exists = existsSync16(path);
3616
+ const path = join16(appRoot, relativePath);
3617
+ const exists = existsSync17(path);
3593
3618
  if (!exists && !options2.fields) {
3594
3619
  throw new Error(`--fields is required, e.g. flare gen resource ${name} --fields "name:string, email:string"`);
3595
3620
  }
@@ -3654,8 +3679,8 @@ Next: ${pc10.bold("flare migrate")} to apply it locally.`);
3654
3679
  }
3655
3680
 
3656
3681
  // src/commands/gen-endpoint.ts
3657
- import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "fs";
3658
- import { join as join16 } from "path";
3682
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "fs";
3683
+ import { join as join17 } from "path";
3659
3684
  import { kebabCase as kebabCase3, pascalCase as pascalCase3 } from "@flaredev/core";
3660
3685
  import pc11 from "picocolors";
3661
3686
  var METHODS = ["GET", "POST", "PATCH", "PUT", "DELETE"];
@@ -3677,9 +3702,9 @@ async function genEndpoint(resourceName2, rawName, options2 = {}) {
3677
3702
  if (!ACTIONS.includes(action)) throw new Error(`Unknown action "${action}". Use one of ${ACTIONS.join(", ")}.`);
3678
3703
  const { resource, stem } = entry;
3679
3704
  const relative3 = options2.record ? `app/api/${resource.slug}/[id]/${name}/route.ts` : `app/api/${resource.slug}/${name}/route.ts`;
3680
- const path = join16(appRoot, relative3);
3681
- if (existsSync17(path)) throw new Error(`${relative3} already exists.`);
3682
- mkdirSync8(join16(path, ".."), { recursive: true });
3705
+ const path = join17(appRoot, relative3);
3706
+ if (existsSync18(path)) throw new Error(`${relative3} already exists.`);
3707
+ mkdirSync8(join17(path, ".."), { recursive: true });
3683
3708
  const route = options2.record ? `/api/${resource.slug}/[id]/${name}` : `/api/${resource.slug}/${name}`;
3684
3709
  writeFileSync11(path, renderEndpoint({ resource: resource.name, stem, method, action, record: Boolean(options2.record), name, route }));
3685
3710
  log2(`${pc11.green("create".padEnd(9))} ${relative3}`);
@@ -3717,8 +3742,8 @@ ${body}
3717
3742
  }
3718
3743
 
3719
3744
  // src/commands/gen-migration.ts
3720
- import { existsSync as existsSync18, mkdirSync as mkdirSync9, writeFileSync as writeFileSync12 } from "fs";
3721
- import { basename as basename2, join as join17 } from "path";
3745
+ import { existsSync as existsSync19, mkdirSync as mkdirSync9, writeFileSync as writeFileSync12 } from "fs";
3746
+ import { basename as basename2, join as join18 } from "path";
3722
3747
  import { snakeCase } from "@flaredev/core";
3723
3748
  import pc12 from "picocolors";
3724
3749
  var DOWN_TEMPLATE = (up) => `-- Rollback for ${up}, run by \`flare migrate:rollback\`.
@@ -3739,11 +3764,11 @@ async function genMigration(rawName, options2 = {}) {
3739
3764
  for (const file of files) log2(`${pc12.green("create".padEnd(9))} ${file}`);
3740
3765
  for (const repair of repairs) log2(pc12.dim(`repaired ${repair}`));
3741
3766
  if (!options2.fromSchema) {
3742
- const downDir = join17(appRoot, "migrations", "down");
3767
+ const downDir = join18(appRoot, "migrations", "down");
3743
3768
  mkdirSync9(downDir, { recursive: true });
3744
3769
  for (const file of files) {
3745
- const down = join17(downDir, basename2(file));
3746
- if (existsSync18(down)) continue;
3770
+ const down = join18(downDir, basename2(file));
3771
+ if (existsSync19(down)) continue;
3747
3772
  writeFileSync12(down, DOWN_TEMPLATE(basename2(file)));
3748
3773
  log2(`${pc12.green("create".padEnd(9))} migrations/down/${basename2(file)}`);
3749
3774
  }
@@ -3757,8 +3782,8 @@ Next: ${pc12.bold("flare migrate")}.`);
3757
3782
  }
3758
3783
 
3759
3784
  // src/commands/gen-policy.ts
3760
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "fs";
3761
- import { dirname as dirname9, join as join18 } from "path";
3785
+ import { existsSync as existsSync20, mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "fs";
3786
+ import { dirname as dirname9, join as join19 } from "path";
3762
3787
  import { definePolicy } from "@flaredev/core";
3763
3788
  import pc13 from "picocolors";
3764
3789
  function parseRoles(input, what = "--roles") {
@@ -3776,8 +3801,8 @@ async function genPolicy(rawName, options2) {
3776
3801
  throw new Error(`No resource "${name}". Generate it first. Known resources: ${known}.`);
3777
3802
  }
3778
3803
  const relative3 = policyPath(name);
3779
- const path = join18(appRoot, relative3);
3780
- const exists = existsSync19(path);
3804
+ const path = join19(appRoot, relative3);
3805
+ const exists = existsSync20(path);
3781
3806
  let roles;
3782
3807
  let deleteRoles;
3783
3808
  if (options2.roles || !exists) {
@@ -3811,9 +3836,9 @@ ${pc13.dim("Roles come from `flare role:add`; the API and the admin both enforce
3811
3836
  }
3812
3837
 
3813
3838
  // src/commands/migrate.ts
3814
- import { existsSync as existsSync20, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
3839
+ import { existsSync as existsSync21, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
3815
3840
  import { tmpdir } from "os";
3816
- import { join as join19 } from "path";
3841
+ import { join as join20 } from "path";
3817
3842
  import pc14 from "picocolors";
3818
3843
 
3819
3844
  // src/generator/down.ts
@@ -3941,7 +3966,7 @@ async function rollback(options2 = {}) {
3941
3966
  const steps = options2.steps ?? 1;
3942
3967
  if (!Number.isInteger(steps) || steps < 1) throw new Error("--steps must be a positive integer.");
3943
3968
  const { appRoot, db, target, where, wrangler, log: log2 } = context(options2);
3944
- const migrationsDir = join19(appRoot, db.migrations_dir ?? "migrations");
3969
+ const migrationsDir = join20(appRoot, db.migrations_dir ?? "migrations");
3945
3970
  const applied = await appliedMigrations(options2, steps);
3946
3971
  if (applied.length === 0) {
3947
3972
  log2("Nothing to roll back.");
@@ -3950,8 +3975,8 @@ async function rollback(options2 = {}) {
3950
3975
  const plan = [];
3951
3976
  for (const { name } of applied) {
3952
3977
  if (!/^[\w.-]+\.sql$/.test(name)) throw new Error(`Unexpected migration name "${name}" in d1_migrations.`);
3953
- const handWritten = join19(migrationsDir, "down", name);
3954
- if (existsSync20(handWritten)) {
3978
+ const handWritten = join20(migrationsDir, "down", name);
3979
+ if (existsSync21(handWritten)) {
3955
3980
  const sql2 = readFileSync12(handWritten, "utf8");
3956
3981
  if (splitStatements(sql2).length === 0) {
3957
3982
  throw new Error(`Can't roll back ${name}: migrations/down/${name} has no SQL statements yet. Write the rollback SQL, then run the rollback again. Nothing was changed.`);
@@ -3959,8 +3984,8 @@ async function rollback(options2 = {}) {
3959
3984
  plan.push({ name, sql: sql2, source: `migrations/down/${name}` });
3960
3985
  continue;
3961
3986
  }
3962
- const upPath = join19(migrationsDir, name);
3963
- if (!existsSync20(upPath)) throw new Error(`${name} was applied but migrations/${name} is missing, so it can't be reversed.`);
3987
+ const upPath = join20(migrationsDir, name);
3988
+ if (!existsSync21(upPath)) throw new Error(`${name} was applied but migrations/${name} is missing, so it can't be reversed.`);
3964
3989
  const down = deriveDown(readFileSync12(upPath, "utf8"));
3965
3990
  if (!down.reversible) {
3966
3991
  throw new Error(
@@ -3981,9 +4006,9 @@ Write the reverse SQL to migrations/down/${name} and run the rollback again. Not
3981
4006
  }
3982
4007
  const script = plan.map(({ name, sql: sql2 }) => `${sql2.trim()}
3983
4008
  DELETE FROM d1_migrations WHERE name = '${name.replace(/'/g, "''")}';`).join("\n") + "\n";
3984
- const dir = mkdtempSync(join19(tmpdir(), "flare-rollback-"));
4009
+ const dir = mkdtempSync(join20(tmpdir(), "flare-rollback-"));
3985
4010
  try {
3986
- const file = join19(dir, "rollback.sql");
4011
+ const file = join20(dir, "rollback.sql");
3987
4012
  writeFileSync14(file, script);
3988
4013
  const result = await runWrangler(wrangler, ["d1", "execute", db.binding, ...target, "--file", file], appRoot, { capture: true });
3989
4014
  if (result.code !== 0) throw new Error(`Rollback failed:
@@ -3997,8 +4022,8 @@ Rolled back ${plan.map((step) => step.name).join(", ")}.`));
3997
4022
  }
3998
4023
 
3999
4024
  // src/commands/rm.ts
4000
- import { existsSync as existsSync21, readdirSync as readdirSync7, readFileSync as readFileSync13, rmdirSync, unlinkSync as unlinkSync2 } from "fs";
4001
- import { dirname as dirname10, join as join20 } from "path";
4025
+ import { existsSync as existsSync22, readdirSync as readdirSync7, readFileSync as readFileSync13, rmdirSync, unlinkSync as unlinkSync2 } from "fs";
4026
+ import { dirname as dirname10, join as join21 } from "path";
4002
4027
  import { relationGraph as relationGraph3 } from "@flaredev/core";
4003
4028
  import pc15 from "picocolors";
4004
4029
  var squash = (text) => text.replace(/\r\n/g, "\n").replace(/\s+/g, " ").trim();
@@ -4017,10 +4042,10 @@ function handWrittenReason(source, header, options2 = {}) {
4017
4042
  return null;
4018
4043
  }
4019
4044
  function removeEmptyDirs(appRoot, start) {
4020
- let dir = dirname10(join20(appRoot, start));
4021
- const roots = [join20(appRoot, "app", "api"), join20(appRoot, "app", "dashboard")];
4045
+ let dir = dirname10(join21(appRoot, start));
4046
+ const roots = [join21(appRoot, "app", "api"), join21(appRoot, "app", "dashboard")];
4022
4047
  const inside = () => roots.some((root) => dir.startsWith(root) && dir !== root);
4023
- while (inside() && existsSync21(dir) && readdirSync7(dir).length === 0) {
4048
+ while (inside() && existsSync22(dir) && readdirSync7(dir).length === 0) {
4024
4049
  rmdirSync(dir);
4025
4050
  dir = dirname10(dir);
4026
4051
  }
@@ -4049,7 +4074,7 @@ async function rmResource(rawName, options2 = {}) {
4049
4074
  if (entry) {
4050
4075
  owned.add(entry.file);
4051
4076
  for (const file of planFiles(all, void 0, readStack(appRoot))) {
4052
- if (existsSync21(join20(appRoot, file.path)) && file.header === resourceHeader(name)) owned.add(file.path);
4077
+ if (existsSync22(join21(appRoot, file.path)) && file.header === resourceHeader(name)) owned.add(file.path);
4053
4078
  }
4054
4079
  }
4055
4080
  for (const orphan of findOrphans(appRoot, remaining)) if (orphan.resource === name) owned.add(orphan.path);
@@ -4058,7 +4083,7 @@ async function rmResource(rawName, options2 = {}) {
4058
4083
  if (!entry && owned.size === 0) throw new Error(`No resource "${name}" (no ${descriptorPath(name)} and no generated files).`);
4059
4084
  const findings = [];
4060
4085
  for (const path of owned) {
4061
- const source = readFileSync13(join20(appRoot, path), "utf8");
4086
+ const source = readFileSync13(join21(appRoot, path), "utf8");
4062
4087
  let wrapper;
4063
4088
  if (entry && path === entry.file) {
4064
4089
  const standard = splitMarkers(renderDescriptor(name, [{ key: "placeholder", kind: "string", required: true, unique: false }]));
@@ -4079,20 +4104,25 @@ Move what you want to keep, or pass --force to delete anyway.`);
4079
4104
  }
4080
4105
  const removed = [...owned].sort();
4081
4106
  for (const path of removed) {
4082
- unlinkSync2(join20(appRoot, path));
4107
+ unlinkSync2(join21(appRoot, path));
4083
4108
  removeEmptyDirs(appRoot, path);
4084
4109
  log2(`${pc15.red("remove".padEnd(9))} ${path}`);
4085
4110
  }
4086
4111
  logResults(applyPlan(appRoot, planFiles(remaining, policy ? await loadPolicies(appRoot) : void 0, readStack(appRoot))), log2);
4087
4112
  const table = entry ? tableExport(entry.resource) : void 0;
4088
- if (table && existsSync21(join20(appRoot, "seeds"))) {
4089
- const seeds = readdirSync7(join20(appRoot, "seeds")).filter(
4090
- (file) => new RegExp(`\\b${table}\\b`).test(readFileSync13(join20(appRoot, "seeds", file), "utf8"))
4113
+ if (table && existsSync22(join21(appRoot, "seeds"))) {
4114
+ const seeds = readdirSync7(join21(appRoot, "seeds")).filter(
4115
+ (file) => new RegExp(`\\b${table}\\b`).test(readFileSync13(join21(appRoot, "seeds", file), "utf8"))
4091
4116
  );
4092
4117
  if (seeds.length) log2(pc15.yellow(`
4093
4118
  These seeds still use ${table}: ${seeds.map((s) => `seeds/${s}`).join(", ")}.`));
4094
4119
  }
4095
4120
  let migrations = [];
4121
+ if (readStack(appRoot) === "next") {
4122
+ if (entry) log2(`
4123
+ Next: ${pc15.bold("npx prisma migrate dev")} to drop the table.`);
4124
+ return { removed, migrations };
4125
+ }
4096
4126
  if (!options2.skipMigration && entry) {
4097
4127
  const result = await generateSchemaMigration(appRoot, `drop_${entry.resource.table}`);
4098
4128
  migrations = result.files;
@@ -4157,8 +4187,8 @@ ${result.output.trim()}`);
4157
4187
  }
4158
4188
 
4159
4189
  // src/commands/eject.ts
4160
- import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync14, readdirSync as readdirSync8, writeFileSync as writeFileSync15 } from "fs";
4161
- import { dirname as dirname11, join as join21, relative as relative2 } from "path";
4190
+ import { existsSync as existsSync23, mkdirSync as mkdirSync11, readFileSync as readFileSync14, readdirSync as readdirSync8, writeFileSync as writeFileSync15 } from "fs";
4191
+ import { dirname as dirname11, join as join22, relative as relative2 } from "path";
4162
4192
  import pc17 from "picocolors";
4163
4193
  var TRACKED = ["lib", "components"];
4164
4194
  var PER_APP = /__APP_NAME__|__PM__|__THEME__|__COMPAT_DATE__/;
@@ -4171,14 +4201,14 @@ function upstreamFile(path, stack) {
4171
4201
  return found;
4172
4202
  }
4173
4203
  function locate(path, stack) {
4174
- const candidates = stack === "next" ? [join21(templatesDir, "next", path), join21(templatesDir, "app", path)] : [join21(templatesDir, "app", path)];
4175
- return candidates.find((candidate) => existsSync22(candidate));
4204
+ const candidates = stack === "next" ? [join22(templatesDir, "next", path), join22(templatesDir, "app", path)] : [join22(templatesDir, "app", path)];
4205
+ return candidates.find((candidate) => existsSync23(candidate));
4176
4206
  }
4177
4207
  function trackedPaths(appRoot, stack) {
4178
4208
  const paths = /* @__PURE__ */ new Set();
4179
4209
  const walk = (base, dir) => {
4180
- if (!existsSync22(join21(base, dir))) return;
4181
- for (const entry of readdirSync8(join21(base, dir), { withFileTypes: true })) {
4210
+ if (!existsSync23(join22(base, dir))) return;
4211
+ for (const entry of readdirSync8(join22(base, dir), { withFileTypes: true })) {
4182
4212
  const next = `${dir}/${entry.name}`;
4183
4213
  if (entry.isDirectory()) walk(base, next);
4184
4214
  else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) paths.add(next);
@@ -4186,8 +4216,8 @@ function trackedPaths(appRoot, stack) {
4186
4216
  };
4187
4217
  for (const root of TRACKED) {
4188
4218
  walk(appRoot, root);
4189
- walk(join21(templatesDir, "app"), root);
4190
- if (stack === "next") walk(join21(templatesDir, "next"), root);
4219
+ walk(join22(templatesDir, "app"), root);
4220
+ if (stack === "next") walk(join22(templatesDir, "next"), root);
4191
4221
  }
4192
4222
  return [...paths].filter((path) => upstreamFile(path, stack) !== void 0).sort();
4193
4223
  }
@@ -4195,8 +4225,8 @@ function compareTracked(appRoot, stack, filter) {
4195
4225
  return trackedPaths(appRoot, stack).filter((path) => !filter || path.includes(filter)).map((path) => {
4196
4226
  const source = upstreamFile(path, stack);
4197
4227
  const theirs = readFileSync14(source, "utf8");
4198
- const target = join21(appRoot, path);
4199
- if (!existsSync22(target)) return { path, state: "missing", theirs };
4228
+ const target = join22(appRoot, path);
4229
+ if (!existsSync23(target)) return { path, state: "missing", theirs };
4200
4230
  const mine = readFileSync14(target, "utf8");
4201
4231
  return { path, state: mine === theirs ? "same" : "changed", mine, theirs };
4202
4232
  });
@@ -4259,7 +4289,7 @@ This discards your edits to those files. Re-run with ${pc17.bold("--yes")} to pr
4259
4289
  return 1;
4260
4290
  }
4261
4291
  for (const entry of changed) {
4262
- const target = join21(appRoot, entry.path);
4292
+ const target = join22(appRoot, entry.path);
4263
4293
  mkdirSync11(dirname11(target), { recursive: true });
4264
4294
  writeFileSync15(target, entry.theirs);
4265
4295
  log2(`${pc17.green("update")} ${relative2(process.cwd(), target) || entry.path}`);
@@ -4270,8 +4300,8 @@ Updated ${changed.length} file(s).`));
4270
4300
  }
4271
4301
 
4272
4302
  // src/commands/seed.ts
4273
- import { existsSync as existsSync23, mkdirSync as mkdirSync12, readdirSync as readdirSync9, rmSync as rmSync5, writeFileSync as writeFileSync17 } from "fs";
4274
- import { join as join23 } from "path";
4303
+ import { existsSync as existsSync24, mkdirSync as mkdirSync12, readdirSync as readdirSync9, rmSync as rmSync5, writeFileSync as writeFileSync17 } from "fs";
4304
+ import { join as join24 } from "path";
4275
4305
  import { camelCase as camelCase6, createFake, kebabCase as kebabCase4, storedFields as storedFields3 } from "@flaredev/core";
4276
4306
  import { createJiti as createJiti4 } from "jiti";
4277
4307
  import pc18 from "picocolors";
@@ -4370,18 +4400,18 @@ function createInsertMany(sink, drizzle, onProgress) {
4370
4400
  // src/seed/local-d1.ts
4371
4401
  import { readFileSync as readFileSync15, rmSync as rmSync4, writeFileSync as writeFileSync16 } from "fs";
4372
4402
  import { createRequire } from "module";
4373
- import { join as join22 } from "path";
4403
+ import { join as join23 } from "path";
4374
4404
  import { pathToFileURL } from "url";
4375
4405
  import { parse as parseJsonc3 } from "jsonc-parser";
4376
4406
  var LOCAL_STATE = ".wrangler/state/v3";
4377
4407
  async function openLocalD1(appRoot, binding) {
4378
4408
  process.env.WRANGLER_LOG ??= "error";
4379
- const appRequire = createRequire(join22(appRoot, "package.json"));
4409
+ const appRequire = createRequire(join23(appRoot, "package.json"));
4380
4410
  const wrangler = await import(pathToFileURL(appRequire.resolve("wrangler")).href);
4381
4411
  const configPath = seedConfig(appRoot);
4382
4412
  const { env, dispose } = await wrangler.getPlatformProxy({
4383
4413
  configPath,
4384
- persist: { path: join22(appRoot, LOCAL_STATE) }
4414
+ persist: { path: join23(appRoot, LOCAL_STATE) }
4385
4415
  });
4386
4416
  const bindings = env;
4387
4417
  const name = binding ?? "DB";
@@ -4400,11 +4430,11 @@ async function openLocalD1(appRoot, binding) {
4400
4430
  };
4401
4431
  }
4402
4432
  function seedConfig(appRoot) {
4403
- const source = join22(appRoot, "wrangler.jsonc");
4433
+ const source = join23(appRoot, "wrangler.jsonc");
4404
4434
  const config = parseJsonc3(readFileSync15(source, "utf8")) ?? {};
4405
4435
  delete config.durable_objects;
4406
4436
  delete config.migrations;
4407
- const path = join22(appRoot, ".flare-seed.wrangler.json");
4437
+ const path = join23(appRoot, ".flare-seed.wrangler.json");
4408
4438
  writeFileSync16(path, JSON.stringify(config, null, 2));
4409
4439
  return path;
4410
4440
  }
@@ -4412,8 +4442,8 @@ function seedConfig(appRoot) {
4412
4442
  // src/commands/seed.ts
4413
4443
  var SEEDS_DIR = "seeds";
4414
4444
  function seedFiles(appRoot, names) {
4415
- const dir = join23(appRoot, SEEDS_DIR);
4416
- const all = existsSync23(dir) ? readdirSync9(dir).filter((file) => file.endsWith(".seed.ts")).sort() : [];
4445
+ const dir = join24(appRoot, SEEDS_DIR);
4446
+ const all = existsSync24(dir) ? readdirSync9(dir).filter((file) => file.endsWith(".seed.ts")).sort() : [];
4417
4447
  if (!names?.length) return all;
4418
4448
  return names.map((name) => {
4419
4449
  const file = `${name.replace(/\.seed\.ts$/, "")}.seed.ts`;
@@ -4435,16 +4465,16 @@ async function runSeeds(options2 = {}) {
4435
4465
  await runPrismaSeeds(appRoot, files, log2);
4436
4466
  return;
4437
4467
  }
4438
- const jiti = createJiti4(join23(appRoot, "package.json"), { alias: { "@": appRoot }, moduleCache: false, fsCache: false });
4468
+ const jiti = createJiti4(join24(appRoot, "package.json"), { alias: { "@": appRoot }, moduleCache: false, fsCache: false });
4439
4469
  const { drizzle } = await jiti.import("drizzle-orm/d1");
4440
4470
  const drizzleHelpers = await jiti.import("drizzle-orm");
4441
- const schema = await jiti.import(join23(appRoot, "db/schema.ts"));
4471
+ const schema = await jiti.import(join24(appRoot, "db/schema.ts"));
4442
4472
  const local = await openLocalD1(appRoot);
4443
4473
  try {
4444
4474
  const db = drizzle(local.db, { schema });
4445
4475
  const insertMany = createInsertMany(local.db, drizzleHelpers);
4446
4476
  for (const file of files) {
4447
- const mod = await jiti.import(join23(appRoot, SEEDS_DIR, file));
4477
+ const mod = await jiti.import(join24(appRoot, SEEDS_DIR, file));
4448
4478
  if (typeof mod.default !== "function") throw new Error(`${SEEDS_DIR}/${file} must \`export default defineSeed(async ({ db }) => { ... })\`.`);
4449
4479
  log2(`${pc18.cyan("seed")} ${file}`);
4450
4480
  await mod.default({ db, env: local.env, insertMany, fake: createFake(), log: (message) => log2(` ${message}`) });
@@ -4561,8 +4591,8 @@ async function makeSeed(rawName, options2 = {}) {
4561
4591
  const name = kebabCase4(rawName);
4562
4592
  if (!name) throw new Error(`Invalid seed name "${rawName}".`);
4563
4593
  const relative3 = `${SEEDS_DIR}/${name}.seed.ts`;
4564
- const path = join23(appRoot, relative3);
4565
- if (existsSync23(path)) throw new Error(`${relative3} already exists.`);
4594
+ const path = join24(appRoot, relative3);
4595
+ if (existsSync24(path)) throw new Error(`${relative3} already exists.`);
4566
4596
  const resources = (await loadResources(appRoot)).map(({ resource: resource2 }) => resource2);
4567
4597
  let resource;
4568
4598
  if (options2.resource) {
@@ -4571,7 +4601,7 @@ async function makeSeed(rawName, options2 = {}) {
4571
4601
  } else {
4572
4602
  resource = resources.find((r) => [kebabCase4(r.name), r.slug].includes(name));
4573
4603
  }
4574
- mkdirSync12(join23(appRoot, SEEDS_DIR), { recursive: true });
4604
+ mkdirSync12(join24(appRoot, SEEDS_DIR), { recursive: true });
4575
4605
  writeFileSync17(path, renderSeed(resource, readStack(appRoot)));
4576
4606
  log2(`${pc18.green("create".padEnd(9))} ${relative3}`);
4577
4607
  log2(`
@@ -4579,7 +4609,7 @@ Run it with ${pc18.bold(`flare seed ${name}`)}.`);
4579
4609
  return relative3;
4580
4610
  }
4581
4611
  async function runPrismaSeeds(appRoot, files, log2) {
4582
- const runner = join23(appRoot, ".flare-seed-runner.mts");
4612
+ const runner = join24(appRoot, ".flare-seed-runner.mts");
4583
4613
  const script = [
4584
4614
  `import "dotenv/config";`,
4585
4615
  `import { createFake } from "@flaredev/core";`,
@@ -4622,7 +4652,7 @@ Ran ${files.length} seed(s).`));
4622
4652
  // src/commands/db-push.ts
4623
4653
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync6, writeFileSync as writeFileSync18 } from "fs";
4624
4654
  import { tmpdir as tmpdir2 } from "os";
4625
- import { join as join24 } from "path";
4655
+ import { join as join25 } from "path";
4626
4656
  import pc19 from "picocolors";
4627
4657
 
4628
4658
  // src/terminal.ts
@@ -4687,7 +4717,7 @@ async function dbPush(tables, options2 = {}) {
4687
4717
  const wrangler = resolveBin(appRoot, "wrangler", "wrangler");
4688
4718
  const started = Date.now();
4689
4719
  const local = await openLocalD1(appRoot, options2.database);
4690
- const dir = mkdtempSync2(join24(tmpdir2(), "flare-push-"));
4720
+ const dir = mkdtempSync2(join25(tmpdir2(), "flare-push-"));
4691
4721
  try {
4692
4722
  const statements = [];
4693
4723
  const counts = [];
@@ -4713,7 +4743,7 @@ async function dbPush(tables, options2 = {}) {
4713
4743
  log2("Nothing to push: those tables are empty locally.");
4714
4744
  return { tables: counts, ms: Date.now() - started };
4715
4745
  }
4716
- const file = join24(dir, "push.sql");
4746
+ const file = join25(dir, "push.sql");
4717
4747
  writeFileSync18(file, statements.join("\n"));
4718
4748
  const target = ["--remote", ...options2.env ? ["--env", options2.env] : []];
4719
4749
  const result = await runWrangler(wrangler, ["d1", "execute", db.binding, ...target, "--yes", "--file", file], appRoot, { capture: true });
@@ -4731,7 +4761,7 @@ ${result.output.trim()}`);
4731
4761
  // src/commands/seed-resource.ts
4732
4762
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync19 } from "fs";
4733
4763
  import { tmpdir as tmpdir3 } from "os";
4734
- import { join as join25 } from "path";
4764
+ import { join as join26 } from "path";
4735
4765
  import pc20 from "picocolors";
4736
4766
 
4737
4767
  // src/seed/rows.ts
@@ -4970,8 +5000,8 @@ ${result.output.trim()}`);
4970
5000
  const [counted] = await query(`SELECT count(*) AS total FROM "${resource.table}"`);
4971
5001
  startIndex = counted?.total ?? 0;
4972
5002
  }
4973
- const dir = mkdtempSync3(join25(tmpdir3(), "flare-seed-"));
4974
- const file = join25(dir, `${resource.table}.sql`);
5003
+ const dir = mkdtempSync3(join26(tmpdir3(), "flare-seed-"));
5004
+ const file = join26(dir, `${resource.table}.sql`);
4975
5005
  try {
4976
5006
  const statements = [];
4977
5007
  if (options2.truncate) statements.push(`DELETE FROM "${resource.table}";`);
@@ -5014,16 +5044,16 @@ ${result.output.trim()}`);
5014
5044
 
5015
5045
  // src/commands/billing-sync.ts
5016
5046
  import { createRequire as createRequire2 } from "module";
5017
- import { existsSync as existsSync24, mkdtempSync as mkdtempSync4, readFileSync as readFileSync16, rmSync as rmSync8, writeFileSync as writeFileSync20 } from "fs";
5047
+ import { existsSync as existsSync25, mkdtempSync as mkdtempSync4, readFileSync as readFileSync16, rmSync as rmSync8, writeFileSync as writeFileSync20 } from "fs";
5018
5048
  import { tmpdir as tmpdir4 } from "os";
5019
- import { join as join26 } from "path";
5049
+ import { join as join27 } from "path";
5020
5050
  import { pathToFileURL as pathToFileURL2 } from "url";
5021
5051
  import { kebabCase as kebabCase5 } from "@flaredev/core";
5022
5052
  import pc21 from "picocolors";
5023
5053
  function stripeKeys(appRoot) {
5024
5054
  const map = {};
5025
- const path = join26(appRoot, ".dev.vars");
5026
- if (existsSync24(path)) {
5055
+ const path = join27(appRoot, ".dev.vars");
5056
+ if (existsSync25(path)) {
5027
5057
  for (const line of readFileSync16(path, "utf8").split(/\r?\n/)) {
5028
5058
  const match = /^\s*([A-Z][A-Z0-9_]*)=(.*)$/.exec(line);
5029
5059
  if (match) map[match[1]] = match[2].trim();
@@ -5107,10 +5137,10 @@ async function syncPlans(options2 = {}) {
5107
5137
  if (!key) throw new Error("STRIPE_SECRET_KEY is not set in .dev.vars or the environment.");
5108
5138
  const db = readD1Databases(appRoot)[0];
5109
5139
  if (!db) throw new Error("No d1_databases in wrangler.jsonc.");
5110
- const appRequire = createRequire2(join26(appRoot, "package.json"));
5140
+ const appRequire = createRequire2(join27(appRoot, "package.json"));
5111
5141
  const stripeModule = await import(pathToFileURL2(appRequire.resolve("stripe")).href);
5112
5142
  const stripe = new stripeModule.default(key, { apiVersion: "2026-08-26.dahlia" });
5113
- const appName = JSON.parse(readFileSync16(join26(appRoot, "package.json"), "utf8")).name ?? "app";
5143
+ const appName = JSON.parse(readFileSync16(join27(appRoot, "package.json"), "utf8")).name ?? "app";
5114
5144
  const everything = await stripe.products.list({ active: true, limit: 100 }).autoPagingToArray({ limit: 1e4 });
5115
5145
  const products = options2.all ? everything : everything.filter((product) => product.metadata?.flare_app === appName);
5116
5146
  if (products.length === 0) {
@@ -5131,9 +5161,9 @@ async function syncPlans(options2 = {}) {
5131
5161
  ${existingResult.output.trim()}`);
5132
5162
  }
5133
5163
  const known = new Set(extractJson(existingResult.output)[0]?.results.map((row) => row.stripe_price_id));
5134
- const dir = mkdtempSync4(join26(tmpdir4(), "flare-sync-"));
5164
+ const dir = mkdtempSync4(join27(tmpdir4(), "flare-sync-"));
5135
5165
  try {
5136
- const file = join26(dir, "sync-plans.sql");
5166
+ const file = join27(dir, "sync-plans.sql");
5137
5167
  writeFileSync20(file, renderSyncSql(rows));
5138
5168
  const result = await runWrangler(wrangler, ["d1", "execute", db.binding, ...target, "--file", file], appRoot, { capture: true });
5139
5169
  if (result.code !== 0) throw new Error(`Could not write plans:
@@ -5157,15 +5187,15 @@ ${rows.length} plan(s) synced to the ${options2.remote ? "remote" : "local"} dat
5157
5187
  }
5158
5188
 
5159
5189
  // src/commands/sync.ts
5160
- import { existsSync as existsSync25 } from "fs";
5161
- import { join as join27 } from "path";
5190
+ import { existsSync as existsSync26 } from "fs";
5191
+ import { join as join28 } from "path";
5162
5192
  import pc22 from "picocolors";
5163
5193
  async function syncTypes(options2 = {}) {
5164
5194
  const log2 = options2.log ?? ((message) => console.log(message));
5165
5195
  const appRoot = findAppRoot(options2.cwd ?? process.cwd());
5166
5196
  const all = await loadResources(appRoot);
5167
5197
  const policies = await loadPolicies(appRoot);
5168
- const tracksPolicies = policies.length > 0 || existsSync25(join27(appRoot, "policies/index.ts"));
5198
+ const tracksPolicies = policies.length > 0 || existsSync26(join28(appRoot, "policies/index.ts"));
5169
5199
  const plan = planFiles(all, tracksPolicies ? policies : void 0, readStack(appRoot));
5170
5200
  if (!options2.check) ensureSupportFiles(appRoot, log2);
5171
5201
  const results = applyPlan(appRoot, plan, { force: options2.force, dryRun: options2.check });
@@ -5213,7 +5243,7 @@ ${pc22.red("Out of sync:")} ${stale} file(s) would change. Run ${pc22.bold("flar
5213
5243
 
5214
5244
  // src/commands/user-role.ts
5215
5245
  import { rmSync as rmSync9, writeFileSync as writeFileSync21 } from "fs";
5216
- import { join as join28 } from "path";
5246
+ import { join as join29 } from "path";
5217
5247
  import pc23 from "picocolors";
5218
5248
  var ROLE = /^[a-z][a-z0-9_-]{0,31}$/;
5219
5249
  var EMAIL = /^[^\s@'"\\]+@[^\s@'"\\]+\.[^\s@'"\\]+$/;
@@ -5251,7 +5281,7 @@ ${result.output.trim()}`);
5251
5281
  return 0;
5252
5282
  }
5253
5283
  async function setUserRoleOnPrisma(appRoot, email, role, log2) {
5254
- const runner = join28(appRoot, ".flare-role-runner.mts");
5284
+ const runner = join29(appRoot, ".flare-role-runner.mts");
5255
5285
  const script = [
5256
5286
  `import "dotenv/config";`,
5257
5287
  `import { prisma } from "./lib/db";`,
@@ -5285,11 +5315,11 @@ async function setUserRoleOnPrisma(appRoot, email, role, log2) {
5285
5315
 
5286
5316
  // src/utils/install.ts
5287
5317
  import { spawn as spawn5 } from "child_process";
5288
- import { existsSync as existsSync26, readFileSync as readFileSync17, readdirSync as readdirSync10 } from "fs";
5289
- import { join as join29 } from "path";
5318
+ import { existsSync as existsSync27, readFileSync as readFileSync17, readdirSync as readdirSync10 } from "fs";
5319
+ import { join as join30 } from "path";
5290
5320
  function expectedPackages(dir) {
5291
- const npmLock = join29(dir, "package-lock.json");
5292
- if (existsSync26(npmLock)) {
5321
+ const npmLock = join30(dir, "package-lock.json");
5322
+ if (existsSync27(npmLock)) {
5293
5323
  try {
5294
5324
  const lock = JSON.parse(readFileSync17(npmLock, "utf8"));
5295
5325
  return Math.max(0, Object.keys(lock.packages ?? {}).length - 1);
@@ -5297,8 +5327,8 @@ function expectedPackages(dir) {
5297
5327
  return 0;
5298
5328
  }
5299
5329
  }
5300
- const pnpmLock = join29(dir, "pnpm-lock.yaml");
5301
- if (existsSync26(pnpmLock)) {
5330
+ const pnpmLock = join30(dir, "pnpm-lock.yaml");
5331
+ if (existsSync27(pnpmLock)) {
5302
5332
  try {
5303
5333
  const text = readFileSync17(pnpmLock, "utf8");
5304
5334
  const packages = text.slice(text.indexOf("\npackages:"));
@@ -5310,15 +5340,15 @@ function expectedPackages(dir) {
5310
5340
  return 0;
5311
5341
  }
5312
5342
  function installedPackages(dir) {
5313
- const root = join29(dir, "node_modules");
5314
- if (!existsSync26(root)) return 0;
5343
+ const root = join30(dir, "node_modules");
5344
+ if (!existsSync27(root)) return 0;
5315
5345
  let total = 0;
5316
5346
  try {
5317
5347
  for (const entry of readdirSync10(root, { withFileTypes: true })) {
5318
5348
  if (!entry.isDirectory() || entry.name === ".bin") continue;
5319
5349
  if (entry.name.startsWith("@")) {
5320
5350
  try {
5321
- total += readdirSync10(join29(root, entry.name)).length;
5351
+ total += readdirSync10(join30(root, entry.name)).length;
5322
5352
  } catch {
5323
5353
  }
5324
5354
  } else if (entry.name !== ".pnpm" && entry.name !== ".vite") {