@flaredev/cli 0.7.1 → 0.7.3

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,8 +450,8 @@ 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
@@ -613,7 +615,7 @@ import { defineResource, field as builders, kebabCase, pascalCase } from "@flare
613
615
 
614
616
  // src/generator/markers.ts
615
617
  import { createHash } from "crypto";
616
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
618
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
617
619
  import { dirname as dirname3 } from "path";
618
620
  var START = "// generated:start";
619
621
  var END = "// generated:end";
@@ -663,7 +665,7 @@ ${indentBlock(body, indent)}${indent}${END}
663
665
  ${after}`;
664
666
  }
665
667
  function inspectGenerated(path, content) {
666
- if (!existsSync4(path)) return "missing";
668
+ if (!existsSync5(path)) return "missing";
667
669
  const parts = splitMarkers(readFileSync3(path, "utf8"));
668
670
  if (!parts) return "unmarked";
669
671
  if (parts.hash && hashBlock(parts.block) !== parts.hash) return "drift";
@@ -680,7 +682,7 @@ var DriftError = class extends Error {
680
682
  };
681
683
  function writeGenerated(path, content, options2 = {}) {
682
684
  const hash = hashBlock(content);
683
- if (!existsSync4(path)) {
685
+ if (!existsSync5(path)) {
684
686
  mkdirSync2(dirname3(path), { recursive: true });
685
687
  writeFileSync4(path, joinMarkers({ before: options2.header ?? "", block: content, after: "", indent: "", hash }));
686
688
  return "create";
@@ -1446,17 +1448,17 @@ var billingDevVarsExampleEntries = () => [
1446
1448
  ].join("\n");
1447
1449
 
1448
1450
  // src/generator/load.ts
1449
- import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
1450
- import { join as join4, resolve as resolve3 } from "path";
1451
+ import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
1452
+ import { join as join5, resolve as resolve3 } from "path";
1451
1453
  import { createJiti } from "jiti";
1452
1454
  async function loadResources(root) {
1453
1455
  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 });
1456
+ const dir = join5(appRoot, "resources");
1457
+ if (!existsSync6(dir)) return [];
1458
+ const jiti = createJiti(join5(appRoot, "package.json"), { alias: { "@": appRoot }, moduleCache: false, fsCache: false });
1457
1459
  const loaded = [];
1458
1460
  for (const file of readdirSync3(dir).filter((name) => name.endsWith(".resource.ts")).sort()) {
1459
- const path = join4(dir, file);
1461
+ const path = join5(dir, file);
1460
1462
  const mod = await jiti.import(path);
1461
1463
  const resource = mod.default;
1462
1464
  if (!resource || typeof resource !== "object" || !("fields" in resource) || !("table" in resource)) {
@@ -1474,20 +1476,20 @@ async function loadResources(root) {
1474
1476
 
1475
1477
  // src/generator/migrations.ts
1476
1478
  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";
1479
+ import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
1480
+ import { join as join10 } from "path";
1479
1481
 
1480
1482
  // src/commands/run.ts
1481
1483
  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";
1484
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
1485
+ import { dirname as dirname4, join as join9, resolve as resolve4 } from "path";
1484
1486
  import pc4 from "picocolors";
1485
1487
 
1486
1488
  // src/tunnel.ts
1487
1489
  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";
1490
+ import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
1489
1491
  import { homedir } from "os";
1490
- import { join as join5 } from "path";
1492
+ import { join as join6 } from "path";
1491
1493
  import pc2 from "picocolors";
1492
1494
  var TUNNEL_URL = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
1493
1495
  var LOCAL_URL = /https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|0\.0\.0\.0):\d+/;
@@ -1512,7 +1514,7 @@ function releaseAsset(platform = process.platform, arch = process.arch) {
1512
1514
  throw new Error(`No cloudflared build for ${platform}/${arch}. Install it yourself: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/`);
1513
1515
  }
1514
1516
  var binName = process.platform === "win32" ? "cloudflared.exe" : "cloudflared";
1515
- var cacheDir = () => join5(homedir(), ".flare", "bin");
1517
+ var cacheDir = () => join6(homedir(), ".flare", "bin");
1516
1518
  function onPath() {
1517
1519
  const probe = spawnSync2(process.platform === "win32" ? "where" : "which", ["cloudflared"], { encoding: "utf8" });
1518
1520
  return probe.status === 0 ? probe.stdout.split(/\r?\n/)[0]?.trim() ?? null : null;
@@ -1521,7 +1523,7 @@ async function download(log2) {
1521
1523
  const asset = releaseAsset();
1522
1524
  const dir = cacheDir();
1523
1525
  mkdirSync3(dir, { recursive: true });
1524
- const target = join5(dir, binName);
1526
+ const target = join6(dir, binName);
1525
1527
  log2(pc2.dim(`Downloading cloudflared (one time) into ${dir}\u2026`));
1526
1528
  const response = await fetch(`https://github.com/cloudflare/cloudflared/releases/latest/download/${asset}`);
1527
1529
  if (!response.ok) throw new Error(`Downloading cloudflared failed: HTTP ${response.status}.`);
@@ -1542,8 +1544,8 @@ async function download(log2) {
1542
1544
  async function ensureCloudflared(log2 = console.log) {
1543
1545
  const found = onPath();
1544
1546
  if (found) return found;
1545
- const cached = join5(cacheDir(), binName);
1546
- if (existsSync6(cached)) return cached;
1547
+ const cached = join6(cacheDir(), binName);
1548
+ if (existsSync7(cached)) return cached;
1547
1549
  return download(log2);
1548
1550
  }
1549
1551
  async function openTunnel(localUrl, options2 = {}) {
@@ -1599,16 +1601,16 @@ function tunnelBanner(publicUrl, localUrl) {
1599
1601
 
1600
1602
  // src/commands/deploy.ts
1601
1603
  import { randomBytes as randomBytes2 } from "crypto";
1602
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
1603
- import { join as join7 } from "path";
1604
+ import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
1605
+ import { join as join8 } from "path";
1604
1606
  import { createZoneClient } from "@flaredev/core/security";
1605
1607
  import { createJiti as createJiti2 } from "jiti";
1606
1608
  import pc3 from "picocolors";
1607
1609
 
1608
1610
  // src/utils/wrangler.ts
1609
1611
  import { spawn as spawn2 } from "child_process";
1610
- import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
1611
- import { join as join6 } from "path";
1612
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
1613
+ import { join as join7 } from "path";
1612
1614
  import { parse as parseJsonc } from "jsonc-parser";
1613
1615
  function runWrangler(wranglerBin, args, cwd, options2 = {}) {
1614
1616
  return new Promise((resolve6, reject) => {
@@ -1633,8 +1635,8 @@ ${output.trim()}`);
1633
1635
  return JSON.parse(output.slice(start, end + 1));
1634
1636
  }
1635
1637
  function readD1Databases(appRoot) {
1636
- const path = join6(appRoot, "wrangler.jsonc");
1637
- if (!existsSync7(path)) return [];
1638
+ const path = join7(appRoot, "wrangler.jsonc");
1639
+ if (!existsSync8(path)) return [];
1638
1640
  const config = parseJsonc(readFileSync4(path, "utf8"));
1639
1641
  return (config?.d1_databases ?? []).filter((db) => db.binding && db.database_name);
1640
1642
  }
@@ -1675,15 +1677,15 @@ function declaredSecretNames(devVarsExample) {
1675
1677
  return devVarsExample.split(/\r?\n/).map((line) => /^([A-Z][A-Z0-9_]*)=/.exec(line.trim())?.[1]).filter((name) => Boolean(name));
1676
1678
  }
1677
1679
  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 });
1680
+ const path = join8(appRoot, "security.config.ts");
1681
+ if (!existsSync9(path)) return null;
1682
+ const jiti = createJiti2(join8(appRoot, "package.json"), { moduleCache: false, fsCache: false });
1681
1683
  const mod = await jiti.import(path);
1682
1684
  if (!mod.default) throw new Error("security.config.ts must `export default defineSecurity({ ... })`.");
1683
1685
  return mod.default;
1684
1686
  }
1685
1687
  async function provisionZoneSecurity(appRoot, config, credentials, options2) {
1686
- const app = JSON.parse(readFileSync5(join7(appRoot, "package.json"), "utf8")).name ?? "app";
1688
+ const app = JSON.parse(readFileSync5(join8(appRoot, "package.json"), "utf8")).name ?? "app";
1687
1689
  const zone = createZoneClient({ ...credentials, app, fetch: options2.fetch });
1688
1690
  if (config.zone?.rateLimits) {
1689
1691
  const result = await zone.syncRateLimits(config.zone.rateLimits);
@@ -1703,7 +1705,7 @@ async function runDeploy(args, context3) {
1703
1705
  const envArgs = plan.env ? ["--env", plan.env] : [];
1704
1706
  const step = (message) => log2(`
1705
1707
  ${pc3.bold(pc3.cyan("flare"))} ${message}`);
1706
- const databases = plan.skipMigrations ? [] : readD1Databases(appRoot).filter((db) => existsSync8(join7(appRoot, db.migrations_dir ?? "migrations")));
1708
+ const databases = plan.skipMigrations ? [] : readD1Databases(appRoot).filter((db) => existsSync9(join8(appRoot, db.migrations_dir ?? "migrations")));
1707
1709
  const migrateAfterDeploy = [];
1708
1710
  const migrate2 = async (db) => {
1709
1711
  step(`Applying migrations to ${db.database_name} (remote)`);
@@ -1762,8 +1764,8 @@ ${put.output.trim()}`);
1762
1764
  existing.add(name);
1763
1765
  }
1764
1766
  }
1765
- const examplePath = join7(appRoot, ".dev.vars.example");
1766
- const optional = existsSync8(examplePath) ? declaredSecretNames(readFileSync5(examplePath, "utf8")).filter((name) => !existing.has(name)) : [];
1767
+ const examplePath = join8(appRoot, ".dev.vars.example");
1768
+ const optional = existsSync9(examplePath) ? declaredSecretNames(readFileSync5(examplePath, "utf8")).filter((name) => !existing.has(name)) : [];
1767
1769
  if (optional.length > 0) {
1768
1770
  step("Optional secrets not set in production (features using them stay off):");
1769
1771
  for (const name of optional) log2(` wrangler secret put ${name}${plan.env ? ` --env ${plan.env}` : ""}`);
@@ -1798,7 +1800,7 @@ function findAppRoot(cwd) {
1798
1800
  while (dir) {
1799
1801
  const root = findUp("package.json", dir);
1800
1802
  if (!root) break;
1801
- const pkg = JSON.parse(readFileSync6(join8(root, "package.json"), "utf8"));
1803
+ const pkg = JSON.parse(readFileSync6(join9(root, "package.json"), "utf8"));
1802
1804
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1803
1805
  if (deps["@flaredev/core"] || deps.vinext) return root;
1804
1806
  const parent = dirname4(root);
@@ -1807,15 +1809,15 @@ function findAppRoot(cwd) {
1807
1809
  throw new Error("Not inside a Flare app (no package.json depending on @flaredev/core found). Run this from your app directory.");
1808
1810
  }
1809
1811
  function resolveBin(appRoot, pkgName, binName2) {
1810
- const pkgDir = findUp(join8("node_modules", pkgName, "package.json"), appRoot);
1812
+ const pkgDir = findUp(join9("node_modules", pkgName, "package.json"), appRoot);
1811
1813
  if (!pkgDir) {
1812
1814
  throw new Error(`Cannot find ${pkgName} in ${appRoot}. Install dependencies first (e.g. pnpm install).`);
1813
1815
  }
1814
- const manifestPath = join8(pkgDir, "node_modules", pkgName, "package.json");
1816
+ const manifestPath = join9(pkgDir, "node_modules", pkgName, "package.json");
1815
1817
  const { bin } = JSON.parse(readFileSync6(manifestPath, "utf8"));
1816
1818
  const relativeBin = typeof bin === "string" ? bin : bin?.[binName2];
1817
1819
  if (!relativeBin) throw new Error(`${pkgName} does not provide a "${binName2}" executable.`);
1818
- return join8(dirname4(manifestPath), relativeBin);
1820
+ return join9(dirname4(manifestPath), relativeBin);
1819
1821
  }
1820
1822
  var NEXT_COMMANDS = {
1821
1823
  dev: { pkg: "next", bin: "next", args: ["dev"] },
@@ -1888,7 +1890,7 @@ async function runDelegated(command, forwarded, cwd = process.cwd()) {
1888
1890
  if (code !== 0) return code;
1889
1891
  }
1890
1892
  const built = next ? ".next" : "dist/server/wrangler.json";
1891
- if (command === "start" && !wantsHelp && !existsSync9(join8(appRoot, built))) {
1893
+ if (command === "start" && !wantsHelp && !existsSync10(join9(appRoot, built))) {
1892
1894
  console.log("No production build found; running `flare build` first.\n");
1893
1895
  const code = await runDelegated("build", [], appRoot);
1894
1896
  if (code !== 0) return code;
@@ -1939,9 +1941,9 @@ function snapshotColumns(snapshot) {
1939
1941
  }
1940
1942
 
1941
1943
  // src/generator/migrations.ts
1942
- var listSql = (dir) => existsSync10(dir) ? readdirSync4(dir).filter((file) => file.endsWith(".sql")) : [];
1944
+ var listSql = (dir) => existsSync11(dir) ? readdirSync4(dir).filter((file) => file.endsWith(".sql")) : [];
1943
1945
  async function generateSchemaMigration(appRoot, name, options2 = {}) {
1944
- const dir = join9(appRoot, "migrations");
1946
+ const dir = join10(appRoot, "migrations");
1945
1947
  const before = new Set(listSql(dir));
1946
1948
  const bin = resolveBin(appRoot, "drizzle-kit", "drizzle-kit");
1947
1949
  const { code, output } = await new Promise((resolve6, reject) => {
@@ -1969,7 +1971,7 @@ If it asked whether a column was renamed, run \`pnpm exec drizzle-kit generate\`
1969
1971
  for (const file of created) {
1970
1972
  const previous = previousSnapshot(dir, file);
1971
1973
  if (!previous) continue;
1972
- const path = join9(dir, file);
1974
+ const path = join10(dir, file);
1973
1975
  const { sql: sql2, changes } = repairRebuildMigration(readFileSync7(path, "utf8"), snapshotColumns(previous));
1974
1976
  if (changes.length) {
1975
1977
  writeFileSync6(path, sql2);
@@ -1980,32 +1982,32 @@ If it asked whether a column was renamed, run \`pnpm exec drizzle-kit generate\`
1980
1982
  }
1981
1983
  function previousSnapshot(dir, migrationFile) {
1982
1984
  const prefix = /^(\d+)_/.exec(migrationFile)?.[1];
1983
- const metaDir = join9(dir, "meta");
1984
- if (!prefix || !existsSync10(metaDir)) return void 0;
1985
+ const metaDir = join10(dir, "meta");
1986
+ if (!prefix || !existsSync11(metaDir)) return void 0;
1985
1987
  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;
1988
+ return earlier ? JSON.parse(readFileSync7(join10(metaDir, earlier), "utf8")) : void 0;
1987
1989
  }
1988
1990
 
1989
1991
  // 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";
1992
+ import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync4, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
1993
+ import { dirname as dirname5, join as join12, sep } from "path";
1992
1994
  import pc5 from "picocolors";
1993
1995
 
1994
1996
  // src/generator/policy.ts
1995
- import { existsSync as existsSync11, readdirSync as readdirSync5 } from "fs";
1996
- import { join as join10, resolve as resolve5 } from "path";
1997
+ import { existsSync as existsSync12, readdirSync as readdirSync5 } from "fs";
1998
+ import { join as join11, resolve as resolve5 } from "path";
1997
1999
  import { camelCase as camelCase2, kebabCase as kebabCase2 } from "@flaredev/core";
1998
2000
  import { createJiti as createJiti3 } from "jiti";
1999
2001
  var policyPath = (name) => `policies/${kebabCase2(name)}.policy.ts`;
2000
2002
  var policyLocal = (stem) => `${camelCase2(stem)}Policy`;
2001
2003
  async function loadPolicies(root) {
2002
2004
  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 });
2005
+ const dir = join11(appRoot, "policies");
2006
+ if (!existsSync12(dir)) return [];
2007
+ const jiti = createJiti3(join11(appRoot, "package.json"), { moduleCache: false, fsCache: false });
2006
2008
  const loaded = [];
2007
2009
  for (const file of readdirSync5(dir).filter((name) => name.endsWith(".policy.ts")).sort()) {
2008
- const mod = await jiti.import(join10(dir, file));
2010
+ const mod = await jiti.import(join11(dir, file));
2009
2011
  const policy = mod.default;
2010
2012
  if (!policy || typeof policy !== "object" || !("resource" in policy) || !Array.isArray(policy.read)) {
2011
2013
  throw new Error(`policies/${file} must \`export default definePolicy({ ... })\`.`);
@@ -2677,7 +2679,7 @@ function planFiles(all, policies, stack = "cloudflare") {
2677
2679
  }
2678
2680
  function applyPlan(appRoot, plan, options2 = {}) {
2679
2681
  return plan.map(({ path, content, header }) => {
2680
- const absolute = join11(appRoot, path);
2682
+ const absolute = join12(appRoot, path);
2681
2683
  const status = inspectGenerated(absolute, content);
2682
2684
  if (status === "unmarked") return { path, status, outcome: "skipped-unmarked" };
2683
2685
  if (status === "drift" && !options2.force) return { path, status, outcome: "skipped-drift" };
@@ -2696,22 +2698,22 @@ function applyPlan(appRoot, plan, options2 = {}) {
2696
2698
  function findOrphans(appRoot, all) {
2697
2699
  const planned = new Set(planFiles(all).map((file) => file.path));
2698
2700
  const candidates = [];
2699
- const list2 = (dir, match) => existsSync12(join11(appRoot, dir)) ? readdirSync6(join11(appRoot, dir)).filter(match).map((name) => `${dir}/${name}`) : [];
2701
+ const list2 = (dir, match) => existsSync13(join12(appRoot, dir)) ? readdirSync6(join12(appRoot, dir)).filter(match).map((name) => `${dir}/${name}`) : [];
2700
2702
  candidates.push(...list2("db/schema", (name) => name.endsWith(".ts")));
2701
2703
  candidates.push(...list2("resources", (name) => /\.(client|validators)\.ts$/.test(name)));
2702
2704
  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);
2705
+ for (const file of [`${dir}/route.ts`, `${dir}/[id]/route.ts`]) if (existsSync13(join12(appRoot, file))) candidates.push(file);
2704
2706
  }
2705
2707
  for (const dir of list2("app/dashboard", () => true)) {
2706
2708
  const pages = [`${dir}/page.tsx`, `${dir}/new/page.tsx`, `${dir}/[id]/page.tsx`, `${dir}/[id]/edit/page.tsx`];
2707
2709
  const skeletons = [`${dir}/loading.tsx`, `${dir}/new/loading.tsx`, `${dir}/[id]/loading.tsx`, `${dir}/[id]/edit/loading.tsx`];
2708
2710
  for (const file of [...pages, ...skeletons]) {
2709
- if (existsSync12(join11(appRoot, file))) candidates.push(file);
2711
+ if (existsSync13(join12(appRoot, file))) candidates.push(file);
2710
2712
  }
2711
2713
  }
2712
2714
  const orphans = [];
2713
2715
  for (const path of candidates) {
2714
- const match = HEADER_PATTERN.exec(readFileSync8(join11(appRoot, path), "utf8"));
2716
+ const match = HEADER_PATTERN.exec(readFileSync8(join12(appRoot, path), "utf8"));
2715
2717
  if (match && !planned.has(path)) orphans.push({ path, resource: match[1] });
2716
2718
  }
2717
2719
  return orphans;
@@ -2722,10 +2724,10 @@ function ensureSupportFiles(appRoot, log2) {
2722
2724
  (name) => `lib/resource/${name}`
2723
2725
  );
2724
2726
  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)
2727
+ const target = join12(appRoot, path);
2728
+ if (existsSync13(target)) continue;
2729
+ const source = [join12(templatesDir, "next", path), join12(templatesDir, "app", path)].find(
2730
+ (candidate) => (stack === "next" || !candidate.includes(`${sep}next${sep}`)) && existsSync13(candidate)
2729
2731
  );
2730
2732
  if (!source) continue;
2731
2733
  mkdirSync4(dirname5(target), { recursive: true });
@@ -2757,14 +2759,14 @@ function billingFiles(appName) {
2757
2759
  }
2758
2760
  var RETIRED_FILES = ["app/api/billing/status/route.ts", "components/billing/subscribe-button.tsx"];
2759
2761
  function appendVars(appRoot, target) {
2760
- const path = join12(appRoot, target);
2761
- const text = existsSync13(path) ? readFileSync9(path, "utf8") : "";
2762
+ const path = join13(appRoot, target);
2763
+ const text = existsSync14(path) ? readFileSync9(path, "utf8") : "";
2762
2764
  if (/STRIPE_SECRET_KEY/.test(text)) return false;
2763
2765
  appendFileSync2(path, target === ".dev.vars" ? billingDevVarsEntries() : billingDevVarsExampleEntries());
2764
2766
  return true;
2765
2767
  }
2766
2768
  function addStripeDependency(appRoot) {
2767
- const path = join12(appRoot, "package.json");
2769
+ const path = join13(appRoot, "package.json");
2768
2770
  const pkg = JSON.parse(readFileSync9(path, "utf8"));
2769
2771
  pkg.dependencies ??= {};
2770
2772
  if (pkg.dependencies.stripe) return false;
@@ -2774,8 +2776,8 @@ function addStripeDependency(appRoot) {
2774
2776
  }
2775
2777
  function writeBillingResource(appRoot, name, force, log2) {
2776
2778
  const relative3 = descriptorPath(name);
2777
- const path = join12(appRoot, relative3);
2778
- if (!existsSync13(path)) {
2779
+ const path = join13(appRoot, relative3);
2780
+ if (!existsSync14(path)) {
2779
2781
  mkdirSync5(dirname6(path), { recursive: true });
2780
2782
  writeFileSync7(path, renderBillingDescriptor(name));
2781
2783
  log2(`${pc6.green("create".padEnd(9))} ${relative3}`);
@@ -2805,25 +2807,25 @@ async function genBilling(options2 = {}) {
2805
2807
  for (const name of ["Plan", "Customer", "Purchase"]) writeBillingResource(appRoot, name, options2.force, log2);
2806
2808
  for (const [name, roles] of Object.entries(BILLING_POLICIES)) {
2807
2809
  const relative3 = policyPath(name);
2808
- if (existsSync13(join12(appRoot, relative3))) {
2810
+ if (existsSync14(join13(appRoot, relative3))) {
2809
2811
  log2(`${pc6.dim("kept".padEnd(9))} ${relative3} (already exists)`);
2810
2812
  continue;
2811
2813
  }
2812
- mkdirSync5(join12(appRoot, "policies"), { recursive: true });
2813
- writeFileSync7(join12(appRoot, relative3), renderPolicy(name, roles));
2814
+ mkdirSync5(join13(appRoot, "policies"), { recursive: true });
2815
+ writeFileSync7(join13(appRoot, relative3), renderPolicy(name, roles));
2814
2816
  log2(`${pc6.green("create".padEnd(9))} ${relative3}`);
2815
2817
  }
2816
2818
  const files = [];
2817
- const appName = JSON.parse(readFileSync9(join12(appRoot, "package.json"), "utf8")).name ?? "app";
2819
+ const appName = JSON.parse(readFileSync9(join13(appRoot, "package.json"), "utf8")).name ?? "app";
2818
2820
  for (const { path, content } of billingFiles(appName)) {
2819
- const outcome = writeGenerated(join12(appRoot, path), content, { header: billingHeader(), force: options2.force });
2821
+ const outcome = writeGenerated(join13(appRoot, path), content, { header: billingHeader(), force: options2.force });
2820
2822
  files.push({ path, status: "missing", outcome });
2821
2823
  const label = outcome === "create" ? pc6.green("create") : outcome === "update" ? pc6.yellow("update") : pc6.dim("identical");
2822
2824
  log2(`${label.padEnd(18)} ${path}`);
2823
2825
  }
2824
2826
  for (const path of RETIRED_FILES) {
2825
- const absolute = join12(appRoot, path);
2826
- if (!existsSync13(absolute)) continue;
2827
+ const absolute = join13(appRoot, path);
2828
+ if (!existsSync14(absolute)) continue;
2827
2829
  const parts = splitMarkers(readFileSync9(absolute, "utf8"));
2828
2830
  const untouched = parts && parts.before.includes("flare gen billing") && !parts.after.trim() && parts.hash !== void 0 && hashBlock(parts.block) === parts.hash;
2829
2831
  if (untouched) {
@@ -2868,8 +2870,8 @@ async function genBilling(options2 = {}) {
2868
2870
  }
2869
2871
 
2870
2872
  // 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";
2873
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
2874
+ import { dirname as dirname7, join as join14 } from "path";
2873
2875
  import pc7 from "picocolors";
2874
2876
 
2875
2877
  // src/generator/security.ts
@@ -3397,8 +3399,8 @@ function securityFiles(appName) {
3397
3399
  }
3398
3400
  var PASS_THROUGH_HEADER = /^\/\/ Request protection for worker\/index\.ts\.[\s\S]*?\n(?=\/\/ generated:start)/;
3399
3401
  function writeOnce(appRoot, relative3, content, log2) {
3400
- const path = join13(appRoot, relative3);
3401
- if (existsSync14(path)) {
3402
+ const path = join14(appRoot, relative3);
3403
+ if (existsSync15(path)) {
3402
3404
  log2(`${pc7.dim("kept".padEnd(9))} ${relative3} (already exists)`);
3403
3405
  return;
3404
3406
  }
@@ -3409,14 +3411,14 @@ function writeOnce(appRoot, relative3, content, log2) {
3409
3411
  async function genSecurity(options2 = {}) {
3410
3412
  const log2 = options2.log ?? ((message) => console.log(message));
3411
3413
  const appRoot = findAppRoot(options2.cwd ?? process.cwd());
3412
- const appName = JSON.parse(readFileSync10(join13(appRoot, "package.json"), "utf8")).name ?? "app";
3414
+ const appName = JSON.parse(readFileSync10(join14(appRoot, "package.json"), "utf8")).name ?? "app";
3413
3415
  writeOnce(appRoot, "security.config.ts", renderSecurityConfig(), log2);
3414
3416
  writeOnce(appRoot, descriptorPath("SecurityEvent"), renderSecurityDescriptor(), log2);
3415
3417
  writeOnce(appRoot, policyPath("SecurityEvent"), renderPolicy("SecurityEvent", SECURITY_EVENT_POLICY), log2);
3416
3418
  const files = [];
3417
3419
  const write = (path, content, header) => {
3418
- const absolute = join13(appRoot, path);
3419
- if (path === "lib/security.ts" && existsSync14(absolute)) {
3420
+ const absolute = join14(appRoot, path);
3421
+ if (path === "lib/security.ts" && existsSync15(absolute)) {
3420
3422
  const source = readFileSync10(absolute, "utf8");
3421
3423
  if (PASS_THROUGH_HEADER.test(source)) writeFileSync8(absolute, source.replace(PASS_THROUGH_HEADER, securityHeader()));
3422
3424
  }
@@ -3426,12 +3428,12 @@ async function genSecurity(options2 = {}) {
3426
3428
  log2(`${label.padEnd(18)} ${path}`);
3427
3429
  };
3428
3430
  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());
3431
+ const navPath = join14(appRoot, "lib/dashboard-nav.ts");
3432
+ if (existsSync15(navPath) && splitMarkers(readFileSync10(navPath, "utf8"))) write("lib/dashboard-nav.ts", renderAdminNavBlock());
3431
3433
  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");
3434
+ const wranglerPath = join14(appRoot, "wrangler.jsonc");
3433
3435
  let bindings = [];
3434
- if (existsSync14(wranglerPath)) {
3436
+ if (existsSync15(wranglerPath)) {
3435
3437
  const { text, added } = addSecurityBindings(readFileSync10(wranglerPath, "utf8"));
3436
3438
  bindings = added;
3437
3439
  if (added.length) {
@@ -3466,14 +3468,14 @@ async function genSecurity(options2 = {}) {
3466
3468
  }
3467
3469
 
3468
3470
  // src/commands/theme.ts
3469
- import { existsSync as existsSync15, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
3470
- import { join as join14 } from "path";
3471
+ import { existsSync as existsSync16, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
3472
+ import { join as join15 } from "path";
3471
3473
  import pc8 from "picocolors";
3472
3474
  var THEME_LINE = /theme: "([a-z]+)" as ThemeName,/;
3473
3475
  function setTheme(name, cwd = process.cwd(), log2 = console.log) {
3474
3476
  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.");
3477
+ const path = join15(appRoot, "lib/site.ts");
3478
+ 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
3479
  const source = readFileSync11(path, "utf8");
3478
3480
  const current = THEME_LINE.exec(source)?.[1];
3479
3481
  if (!name) {
@@ -3566,8 +3568,8 @@ async function askCreateQuestions(given, appName) {
3566
3568
  }
3567
3569
 
3568
3570
  // 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";
3571
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "fs";
3572
+ import { dirname as dirname8, join as join16 } from "path";
3571
3573
  import { camelCase as camelCase5, defineResource as defineResource2, relationGraph as relationGraph2 } from "@flaredev/core";
3572
3574
  import pc10 from "picocolors";
3573
3575
  function validateFields(name, fields, others, log2) {
@@ -3588,8 +3590,8 @@ async function genResource(rawName, options2) {
3588
3590
  const appRoot = findAppRoot(options2.cwd ?? process.cwd());
3589
3591
  const name = resourceName(rawName);
3590
3592
  const relativePath = descriptorPath(name);
3591
- const path = join15(appRoot, relativePath);
3592
- const exists = existsSync16(path);
3593
+ const path = join16(appRoot, relativePath);
3594
+ const exists = existsSync17(path);
3593
3595
  if (!exists && !options2.fields) {
3594
3596
  throw new Error(`--fields is required, e.g. flare gen resource ${name} --fields "name:string, email:string"`);
3595
3597
  }
@@ -3654,8 +3656,8 @@ Next: ${pc10.bold("flare migrate")} to apply it locally.`);
3654
3656
  }
3655
3657
 
3656
3658
  // 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";
3659
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "fs";
3660
+ import { join as join17 } from "path";
3659
3661
  import { kebabCase as kebabCase3, pascalCase as pascalCase3 } from "@flaredev/core";
3660
3662
  import pc11 from "picocolors";
3661
3663
  var METHODS = ["GET", "POST", "PATCH", "PUT", "DELETE"];
@@ -3677,9 +3679,9 @@ async function genEndpoint(resourceName2, rawName, options2 = {}) {
3677
3679
  if (!ACTIONS.includes(action)) throw new Error(`Unknown action "${action}". Use one of ${ACTIONS.join(", ")}.`);
3678
3680
  const { resource, stem } = entry;
3679
3681
  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 });
3682
+ const path = join17(appRoot, relative3);
3683
+ if (existsSync18(path)) throw new Error(`${relative3} already exists.`);
3684
+ mkdirSync8(join17(path, ".."), { recursive: true });
3683
3685
  const route = options2.record ? `/api/${resource.slug}/[id]/${name}` : `/api/${resource.slug}/${name}`;
3684
3686
  writeFileSync11(path, renderEndpoint({ resource: resource.name, stem, method, action, record: Boolean(options2.record), name, route }));
3685
3687
  log2(`${pc11.green("create".padEnd(9))} ${relative3}`);
@@ -3717,8 +3719,8 @@ ${body}
3717
3719
  }
3718
3720
 
3719
3721
  // 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";
3722
+ import { existsSync as existsSync19, mkdirSync as mkdirSync9, writeFileSync as writeFileSync12 } from "fs";
3723
+ import { basename as basename2, join as join18 } from "path";
3722
3724
  import { snakeCase } from "@flaredev/core";
3723
3725
  import pc12 from "picocolors";
3724
3726
  var DOWN_TEMPLATE = (up) => `-- Rollback for ${up}, run by \`flare migrate:rollback\`.
@@ -3739,11 +3741,11 @@ async function genMigration(rawName, options2 = {}) {
3739
3741
  for (const file of files) log2(`${pc12.green("create".padEnd(9))} ${file}`);
3740
3742
  for (const repair of repairs) log2(pc12.dim(`repaired ${repair}`));
3741
3743
  if (!options2.fromSchema) {
3742
- const downDir = join17(appRoot, "migrations", "down");
3744
+ const downDir = join18(appRoot, "migrations", "down");
3743
3745
  mkdirSync9(downDir, { recursive: true });
3744
3746
  for (const file of files) {
3745
- const down = join17(downDir, basename2(file));
3746
- if (existsSync18(down)) continue;
3747
+ const down = join18(downDir, basename2(file));
3748
+ if (existsSync19(down)) continue;
3747
3749
  writeFileSync12(down, DOWN_TEMPLATE(basename2(file)));
3748
3750
  log2(`${pc12.green("create".padEnd(9))} migrations/down/${basename2(file)}`);
3749
3751
  }
@@ -3757,8 +3759,8 @@ Next: ${pc12.bold("flare migrate")}.`);
3757
3759
  }
3758
3760
 
3759
3761
  // 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";
3762
+ import { existsSync as existsSync20, mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "fs";
3763
+ import { dirname as dirname9, join as join19 } from "path";
3762
3764
  import { definePolicy } from "@flaredev/core";
3763
3765
  import pc13 from "picocolors";
3764
3766
  function parseRoles(input, what = "--roles") {
@@ -3776,8 +3778,8 @@ async function genPolicy(rawName, options2) {
3776
3778
  throw new Error(`No resource "${name}". Generate it first. Known resources: ${known}.`);
3777
3779
  }
3778
3780
  const relative3 = policyPath(name);
3779
- const path = join18(appRoot, relative3);
3780
- const exists = existsSync19(path);
3781
+ const path = join19(appRoot, relative3);
3782
+ const exists = existsSync20(path);
3781
3783
  let roles;
3782
3784
  let deleteRoles;
3783
3785
  if (options2.roles || !exists) {
@@ -3811,9 +3813,9 @@ ${pc13.dim("Roles come from `flare role:add`; the API and the admin both enforce
3811
3813
  }
3812
3814
 
3813
3815
  // src/commands/migrate.ts
3814
- import { existsSync as existsSync20, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
3816
+ import { existsSync as existsSync21, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
3815
3817
  import { tmpdir } from "os";
3816
- import { join as join19 } from "path";
3818
+ import { join as join20 } from "path";
3817
3819
  import pc14 from "picocolors";
3818
3820
 
3819
3821
  // src/generator/down.ts
@@ -3941,7 +3943,7 @@ async function rollback(options2 = {}) {
3941
3943
  const steps = options2.steps ?? 1;
3942
3944
  if (!Number.isInteger(steps) || steps < 1) throw new Error("--steps must be a positive integer.");
3943
3945
  const { appRoot, db, target, where, wrangler, log: log2 } = context(options2);
3944
- const migrationsDir = join19(appRoot, db.migrations_dir ?? "migrations");
3946
+ const migrationsDir = join20(appRoot, db.migrations_dir ?? "migrations");
3945
3947
  const applied = await appliedMigrations(options2, steps);
3946
3948
  if (applied.length === 0) {
3947
3949
  log2("Nothing to roll back.");
@@ -3950,8 +3952,8 @@ async function rollback(options2 = {}) {
3950
3952
  const plan = [];
3951
3953
  for (const { name } of applied) {
3952
3954
  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)) {
3955
+ const handWritten = join20(migrationsDir, "down", name);
3956
+ if (existsSync21(handWritten)) {
3955
3957
  const sql2 = readFileSync12(handWritten, "utf8");
3956
3958
  if (splitStatements(sql2).length === 0) {
3957
3959
  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 +3961,8 @@ async function rollback(options2 = {}) {
3959
3961
  plan.push({ name, sql: sql2, source: `migrations/down/${name}` });
3960
3962
  continue;
3961
3963
  }
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.`);
3964
+ const upPath = join20(migrationsDir, name);
3965
+ if (!existsSync21(upPath)) throw new Error(`${name} was applied but migrations/${name} is missing, so it can't be reversed.`);
3964
3966
  const down = deriveDown(readFileSync12(upPath, "utf8"));
3965
3967
  if (!down.reversible) {
3966
3968
  throw new Error(
@@ -3981,9 +3983,9 @@ Write the reverse SQL to migrations/down/${name} and run the rollback again. Not
3981
3983
  }
3982
3984
  const script = plan.map(({ name, sql: sql2 }) => `${sql2.trim()}
3983
3985
  DELETE FROM d1_migrations WHERE name = '${name.replace(/'/g, "''")}';`).join("\n") + "\n";
3984
- const dir = mkdtempSync(join19(tmpdir(), "flare-rollback-"));
3986
+ const dir = mkdtempSync(join20(tmpdir(), "flare-rollback-"));
3985
3987
  try {
3986
- const file = join19(dir, "rollback.sql");
3988
+ const file = join20(dir, "rollback.sql");
3987
3989
  writeFileSync14(file, script);
3988
3990
  const result = await runWrangler(wrangler, ["d1", "execute", db.binding, ...target, "--file", file], appRoot, { capture: true });
3989
3991
  if (result.code !== 0) throw new Error(`Rollback failed:
@@ -3997,8 +3999,8 @@ Rolled back ${plan.map((step) => step.name).join(", ")}.`));
3997
3999
  }
3998
4000
 
3999
4001
  // 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";
4002
+ import { existsSync as existsSync22, readdirSync as readdirSync7, readFileSync as readFileSync13, rmdirSync, unlinkSync as unlinkSync2 } from "fs";
4003
+ import { dirname as dirname10, join as join21 } from "path";
4002
4004
  import { relationGraph as relationGraph3 } from "@flaredev/core";
4003
4005
  import pc15 from "picocolors";
4004
4006
  var squash = (text) => text.replace(/\r\n/g, "\n").replace(/\s+/g, " ").trim();
@@ -4017,10 +4019,10 @@ function handWrittenReason(source, header, options2 = {}) {
4017
4019
  return null;
4018
4020
  }
4019
4021
  function removeEmptyDirs(appRoot, start) {
4020
- let dir = dirname10(join20(appRoot, start));
4021
- const roots = [join20(appRoot, "app", "api"), join20(appRoot, "app", "dashboard")];
4022
+ let dir = dirname10(join21(appRoot, start));
4023
+ const roots = [join21(appRoot, "app", "api"), join21(appRoot, "app", "dashboard")];
4022
4024
  const inside = () => roots.some((root) => dir.startsWith(root) && dir !== root);
4023
- while (inside() && existsSync21(dir) && readdirSync7(dir).length === 0) {
4025
+ while (inside() && existsSync22(dir) && readdirSync7(dir).length === 0) {
4024
4026
  rmdirSync(dir);
4025
4027
  dir = dirname10(dir);
4026
4028
  }
@@ -4049,7 +4051,7 @@ async function rmResource(rawName, options2 = {}) {
4049
4051
  if (entry) {
4050
4052
  owned.add(entry.file);
4051
4053
  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);
4054
+ if (existsSync22(join21(appRoot, file.path)) && file.header === resourceHeader(name)) owned.add(file.path);
4053
4055
  }
4054
4056
  }
4055
4057
  for (const orphan of findOrphans(appRoot, remaining)) if (orphan.resource === name) owned.add(orphan.path);
@@ -4058,7 +4060,7 @@ async function rmResource(rawName, options2 = {}) {
4058
4060
  if (!entry && owned.size === 0) throw new Error(`No resource "${name}" (no ${descriptorPath(name)} and no generated files).`);
4059
4061
  const findings = [];
4060
4062
  for (const path of owned) {
4061
- const source = readFileSync13(join20(appRoot, path), "utf8");
4063
+ const source = readFileSync13(join21(appRoot, path), "utf8");
4062
4064
  let wrapper;
4063
4065
  if (entry && path === entry.file) {
4064
4066
  const standard = splitMarkers(renderDescriptor(name, [{ key: "placeholder", kind: "string", required: true, unique: false }]));
@@ -4079,15 +4081,15 @@ Move what you want to keep, or pass --force to delete anyway.`);
4079
4081
  }
4080
4082
  const removed = [...owned].sort();
4081
4083
  for (const path of removed) {
4082
- unlinkSync2(join20(appRoot, path));
4084
+ unlinkSync2(join21(appRoot, path));
4083
4085
  removeEmptyDirs(appRoot, path);
4084
4086
  log2(`${pc15.red("remove".padEnd(9))} ${path}`);
4085
4087
  }
4086
4088
  logResults(applyPlan(appRoot, planFiles(remaining, policy ? await loadPolicies(appRoot) : void 0, readStack(appRoot))), log2);
4087
4089
  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"))
4090
+ if (table && existsSync22(join21(appRoot, "seeds"))) {
4091
+ const seeds = readdirSync7(join21(appRoot, "seeds")).filter(
4092
+ (file) => new RegExp(`\\b${table}\\b`).test(readFileSync13(join21(appRoot, "seeds", file), "utf8"))
4091
4093
  );
4092
4094
  if (seeds.length) log2(pc15.yellow(`
4093
4095
  These seeds still use ${table}: ${seeds.map((s) => `seeds/${s}`).join(", ")}.`));
@@ -4157,21 +4159,28 @@ ${result.output.trim()}`);
4157
4159
  }
4158
4160
 
4159
4161
  // 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";
4162
+ import { existsSync as existsSync23, mkdirSync as mkdirSync11, readFileSync as readFileSync14, readdirSync as readdirSync8, writeFileSync as writeFileSync15 } from "fs";
4163
+ import { dirname as dirname11, join as join22, relative as relative2 } from "path";
4162
4164
  import pc17 from "picocolors";
4163
- var TRACKED = ["lib/resource"];
4165
+ var TRACKED = ["lib", "components"];
4166
+ var PER_APP = /__APP_NAME__|__PM__|__THEME__|__COMPAT_DATE__/;
4167
+ var WRITTEN_PER_APP = /* @__PURE__ */ new Set(["lib/auth-config.ts"]);
4164
4168
  var prunedOn = (path, stack) => stack === "next" && CLOUDFLARE_ONLY.some((only) => path === only || path.startsWith(`${only}/`));
4165
4169
  function upstreamFile(path, stack) {
4166
- if (prunedOn(path, stack)) return void 0;
4167
- const candidates = stack === "next" ? [join21(templatesDir, "next", path), join21(templatesDir, "app", path)] : [join21(templatesDir, "app", path)];
4168
- return candidates.find((candidate) => existsSync22(candidate));
4170
+ if (prunedOn(path, stack) || WRITTEN_PER_APP.has(path)) return void 0;
4171
+ const found = locate(path, stack);
4172
+ if (found && PER_APP.test(readFileSync14(found, "utf8"))) return void 0;
4173
+ return found;
4174
+ }
4175
+ function locate(path, stack) {
4176
+ const candidates = stack === "next" ? [join22(templatesDir, "next", path), join22(templatesDir, "app", path)] : [join22(templatesDir, "app", path)];
4177
+ return candidates.find((candidate) => existsSync23(candidate));
4169
4178
  }
4170
4179
  function trackedPaths(appRoot, stack) {
4171
4180
  const paths = /* @__PURE__ */ new Set();
4172
4181
  const walk = (base, dir) => {
4173
- if (!existsSync22(join21(base, dir))) return;
4174
- for (const entry of readdirSync8(join21(base, dir), { withFileTypes: true })) {
4182
+ if (!existsSync23(join22(base, dir))) return;
4183
+ for (const entry of readdirSync8(join22(base, dir), { withFileTypes: true })) {
4175
4184
  const next = `${dir}/${entry.name}`;
4176
4185
  if (entry.isDirectory()) walk(base, next);
4177
4186
  else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) paths.add(next);
@@ -4179,8 +4188,8 @@ function trackedPaths(appRoot, stack) {
4179
4188
  };
4180
4189
  for (const root of TRACKED) {
4181
4190
  walk(appRoot, root);
4182
- walk(join21(templatesDir, "app"), root);
4183
- if (stack === "next") walk(join21(templatesDir, "next"), root);
4191
+ walk(join22(templatesDir, "app"), root);
4192
+ if (stack === "next") walk(join22(templatesDir, "next"), root);
4184
4193
  }
4185
4194
  return [...paths].filter((path) => upstreamFile(path, stack) !== void 0).sort();
4186
4195
  }
@@ -4188,8 +4197,8 @@ function compareTracked(appRoot, stack, filter) {
4188
4197
  return trackedPaths(appRoot, stack).filter((path) => !filter || path.includes(filter)).map((path) => {
4189
4198
  const source = upstreamFile(path, stack);
4190
4199
  const theirs = readFileSync14(source, "utf8");
4191
- const target = join21(appRoot, path);
4192
- if (!existsSync22(target)) return { path, state: "missing", theirs };
4200
+ const target = join22(appRoot, path);
4201
+ if (!existsSync23(target)) return { path, state: "missing", theirs };
4193
4202
  const mine = readFileSync14(target, "utf8");
4194
4203
  return { path, state: mine === theirs ? "same" : "changed", mine, theirs };
4195
4204
  });
@@ -4252,7 +4261,7 @@ This discards your edits to those files. Re-run with ${pc17.bold("--yes")} to pr
4252
4261
  return 1;
4253
4262
  }
4254
4263
  for (const entry of changed) {
4255
- const target = join21(appRoot, entry.path);
4264
+ const target = join22(appRoot, entry.path);
4256
4265
  mkdirSync11(dirname11(target), { recursive: true });
4257
4266
  writeFileSync15(target, entry.theirs);
4258
4267
  log2(`${pc17.green("update")} ${relative2(process.cwd(), target) || entry.path}`);
@@ -4263,8 +4272,8 @@ Updated ${changed.length} file(s).`));
4263
4272
  }
4264
4273
 
4265
4274
  // src/commands/seed.ts
4266
- import { existsSync as existsSync23, mkdirSync as mkdirSync12, readdirSync as readdirSync9, rmSync as rmSync5, writeFileSync as writeFileSync17 } from "fs";
4267
- import { join as join23 } from "path";
4275
+ import { existsSync as existsSync24, mkdirSync as mkdirSync12, readdirSync as readdirSync9, rmSync as rmSync5, writeFileSync as writeFileSync17 } from "fs";
4276
+ import { join as join24 } from "path";
4268
4277
  import { camelCase as camelCase6, createFake, kebabCase as kebabCase4, storedFields as storedFields3 } from "@flaredev/core";
4269
4278
  import { createJiti as createJiti4 } from "jiti";
4270
4279
  import pc18 from "picocolors";
@@ -4363,18 +4372,18 @@ function createInsertMany(sink, drizzle, onProgress) {
4363
4372
  // src/seed/local-d1.ts
4364
4373
  import { readFileSync as readFileSync15, rmSync as rmSync4, writeFileSync as writeFileSync16 } from "fs";
4365
4374
  import { createRequire } from "module";
4366
- import { join as join22 } from "path";
4375
+ import { join as join23 } from "path";
4367
4376
  import { pathToFileURL } from "url";
4368
4377
  import { parse as parseJsonc3 } from "jsonc-parser";
4369
4378
  var LOCAL_STATE = ".wrangler/state/v3";
4370
4379
  async function openLocalD1(appRoot, binding) {
4371
4380
  process.env.WRANGLER_LOG ??= "error";
4372
- const appRequire = createRequire(join22(appRoot, "package.json"));
4381
+ const appRequire = createRequire(join23(appRoot, "package.json"));
4373
4382
  const wrangler = await import(pathToFileURL(appRequire.resolve("wrangler")).href);
4374
4383
  const configPath = seedConfig(appRoot);
4375
4384
  const { env, dispose } = await wrangler.getPlatformProxy({
4376
4385
  configPath,
4377
- persist: { path: join22(appRoot, LOCAL_STATE) }
4386
+ persist: { path: join23(appRoot, LOCAL_STATE) }
4378
4387
  });
4379
4388
  const bindings = env;
4380
4389
  const name = binding ?? "DB";
@@ -4393,11 +4402,11 @@ async function openLocalD1(appRoot, binding) {
4393
4402
  };
4394
4403
  }
4395
4404
  function seedConfig(appRoot) {
4396
- const source = join22(appRoot, "wrangler.jsonc");
4405
+ const source = join23(appRoot, "wrangler.jsonc");
4397
4406
  const config = parseJsonc3(readFileSync15(source, "utf8")) ?? {};
4398
4407
  delete config.durable_objects;
4399
4408
  delete config.migrations;
4400
- const path = join22(appRoot, ".flare-seed.wrangler.json");
4409
+ const path = join23(appRoot, ".flare-seed.wrangler.json");
4401
4410
  writeFileSync16(path, JSON.stringify(config, null, 2));
4402
4411
  return path;
4403
4412
  }
@@ -4405,8 +4414,8 @@ function seedConfig(appRoot) {
4405
4414
  // src/commands/seed.ts
4406
4415
  var SEEDS_DIR = "seeds";
4407
4416
  function seedFiles(appRoot, names) {
4408
- const dir = join23(appRoot, SEEDS_DIR);
4409
- const all = existsSync23(dir) ? readdirSync9(dir).filter((file) => file.endsWith(".seed.ts")).sort() : [];
4417
+ const dir = join24(appRoot, SEEDS_DIR);
4418
+ const all = existsSync24(dir) ? readdirSync9(dir).filter((file) => file.endsWith(".seed.ts")).sort() : [];
4410
4419
  if (!names?.length) return all;
4411
4420
  return names.map((name) => {
4412
4421
  const file = `${name.replace(/\.seed\.ts$/, "")}.seed.ts`;
@@ -4428,16 +4437,16 @@ async function runSeeds(options2 = {}) {
4428
4437
  await runPrismaSeeds(appRoot, files, log2);
4429
4438
  return;
4430
4439
  }
4431
- const jiti = createJiti4(join23(appRoot, "package.json"), { alias: { "@": appRoot }, moduleCache: false, fsCache: false });
4440
+ const jiti = createJiti4(join24(appRoot, "package.json"), { alias: { "@": appRoot }, moduleCache: false, fsCache: false });
4432
4441
  const { drizzle } = await jiti.import("drizzle-orm/d1");
4433
4442
  const drizzleHelpers = await jiti.import("drizzle-orm");
4434
- const schema = await jiti.import(join23(appRoot, "db/schema.ts"));
4443
+ const schema = await jiti.import(join24(appRoot, "db/schema.ts"));
4435
4444
  const local = await openLocalD1(appRoot);
4436
4445
  try {
4437
4446
  const db = drizzle(local.db, { schema });
4438
4447
  const insertMany = createInsertMany(local.db, drizzleHelpers);
4439
4448
  for (const file of files) {
4440
- const mod = await jiti.import(join23(appRoot, SEEDS_DIR, file));
4449
+ const mod = await jiti.import(join24(appRoot, SEEDS_DIR, file));
4441
4450
  if (typeof mod.default !== "function") throw new Error(`${SEEDS_DIR}/${file} must \`export default defineSeed(async ({ db }) => { ... })\`.`);
4442
4451
  log2(`${pc18.cyan("seed")} ${file}`);
4443
4452
  await mod.default({ db, env: local.env, insertMany, fake: createFake(), log: (message) => log2(` ${message}`) });
@@ -4554,8 +4563,8 @@ async function makeSeed(rawName, options2 = {}) {
4554
4563
  const name = kebabCase4(rawName);
4555
4564
  if (!name) throw new Error(`Invalid seed name "${rawName}".`);
4556
4565
  const relative3 = `${SEEDS_DIR}/${name}.seed.ts`;
4557
- const path = join23(appRoot, relative3);
4558
- if (existsSync23(path)) throw new Error(`${relative3} already exists.`);
4566
+ const path = join24(appRoot, relative3);
4567
+ if (existsSync24(path)) throw new Error(`${relative3} already exists.`);
4559
4568
  const resources = (await loadResources(appRoot)).map(({ resource: resource2 }) => resource2);
4560
4569
  let resource;
4561
4570
  if (options2.resource) {
@@ -4564,7 +4573,7 @@ async function makeSeed(rawName, options2 = {}) {
4564
4573
  } else {
4565
4574
  resource = resources.find((r) => [kebabCase4(r.name), r.slug].includes(name));
4566
4575
  }
4567
- mkdirSync12(join23(appRoot, SEEDS_DIR), { recursive: true });
4576
+ mkdirSync12(join24(appRoot, SEEDS_DIR), { recursive: true });
4568
4577
  writeFileSync17(path, renderSeed(resource, readStack(appRoot)));
4569
4578
  log2(`${pc18.green("create".padEnd(9))} ${relative3}`);
4570
4579
  log2(`
@@ -4572,7 +4581,7 @@ Run it with ${pc18.bold(`flare seed ${name}`)}.`);
4572
4581
  return relative3;
4573
4582
  }
4574
4583
  async function runPrismaSeeds(appRoot, files, log2) {
4575
- const runner = join23(appRoot, ".flare-seed-runner.mts");
4584
+ const runner = join24(appRoot, ".flare-seed-runner.mts");
4576
4585
  const script = [
4577
4586
  `import "dotenv/config";`,
4578
4587
  `import { createFake } from "@flaredev/core";`,
@@ -4615,7 +4624,7 @@ Ran ${files.length} seed(s).`));
4615
4624
  // src/commands/db-push.ts
4616
4625
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync6, writeFileSync as writeFileSync18 } from "fs";
4617
4626
  import { tmpdir as tmpdir2 } from "os";
4618
- import { join as join24 } from "path";
4627
+ import { join as join25 } from "path";
4619
4628
  import pc19 from "picocolors";
4620
4629
 
4621
4630
  // src/terminal.ts
@@ -4680,7 +4689,7 @@ async function dbPush(tables, options2 = {}) {
4680
4689
  const wrangler = resolveBin(appRoot, "wrangler", "wrangler");
4681
4690
  const started = Date.now();
4682
4691
  const local = await openLocalD1(appRoot, options2.database);
4683
- const dir = mkdtempSync2(join24(tmpdir2(), "flare-push-"));
4692
+ const dir = mkdtempSync2(join25(tmpdir2(), "flare-push-"));
4684
4693
  try {
4685
4694
  const statements = [];
4686
4695
  const counts = [];
@@ -4706,7 +4715,7 @@ async function dbPush(tables, options2 = {}) {
4706
4715
  log2("Nothing to push: those tables are empty locally.");
4707
4716
  return { tables: counts, ms: Date.now() - started };
4708
4717
  }
4709
- const file = join24(dir, "push.sql");
4718
+ const file = join25(dir, "push.sql");
4710
4719
  writeFileSync18(file, statements.join("\n"));
4711
4720
  const target = ["--remote", ...options2.env ? ["--env", options2.env] : []];
4712
4721
  const result = await runWrangler(wrangler, ["d1", "execute", db.binding, ...target, "--yes", "--file", file], appRoot, { capture: true });
@@ -4724,7 +4733,7 @@ ${result.output.trim()}`);
4724
4733
  // src/commands/seed-resource.ts
4725
4734
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync19 } from "fs";
4726
4735
  import { tmpdir as tmpdir3 } from "os";
4727
- import { join as join25 } from "path";
4736
+ import { join as join26 } from "path";
4728
4737
  import pc20 from "picocolors";
4729
4738
 
4730
4739
  // src/seed/rows.ts
@@ -4963,8 +4972,8 @@ ${result.output.trim()}`);
4963
4972
  const [counted] = await query(`SELECT count(*) AS total FROM "${resource.table}"`);
4964
4973
  startIndex = counted?.total ?? 0;
4965
4974
  }
4966
- const dir = mkdtempSync3(join25(tmpdir3(), "flare-seed-"));
4967
- const file = join25(dir, `${resource.table}.sql`);
4975
+ const dir = mkdtempSync3(join26(tmpdir3(), "flare-seed-"));
4976
+ const file = join26(dir, `${resource.table}.sql`);
4968
4977
  try {
4969
4978
  const statements = [];
4970
4979
  if (options2.truncate) statements.push(`DELETE FROM "${resource.table}";`);
@@ -5007,16 +5016,16 @@ ${result.output.trim()}`);
5007
5016
 
5008
5017
  // src/commands/billing-sync.ts
5009
5018
  import { createRequire as createRequire2 } from "module";
5010
- import { existsSync as existsSync24, mkdtempSync as mkdtempSync4, readFileSync as readFileSync16, rmSync as rmSync8, writeFileSync as writeFileSync20 } from "fs";
5019
+ import { existsSync as existsSync25, mkdtempSync as mkdtempSync4, readFileSync as readFileSync16, rmSync as rmSync8, writeFileSync as writeFileSync20 } from "fs";
5011
5020
  import { tmpdir as tmpdir4 } from "os";
5012
- import { join as join26 } from "path";
5021
+ import { join as join27 } from "path";
5013
5022
  import { pathToFileURL as pathToFileURL2 } from "url";
5014
5023
  import { kebabCase as kebabCase5 } from "@flaredev/core";
5015
5024
  import pc21 from "picocolors";
5016
5025
  function stripeKeys(appRoot) {
5017
5026
  const map = {};
5018
- const path = join26(appRoot, ".dev.vars");
5019
- if (existsSync24(path)) {
5027
+ const path = join27(appRoot, ".dev.vars");
5028
+ if (existsSync25(path)) {
5020
5029
  for (const line of readFileSync16(path, "utf8").split(/\r?\n/)) {
5021
5030
  const match = /^\s*([A-Z][A-Z0-9_]*)=(.*)$/.exec(line);
5022
5031
  if (match) map[match[1]] = match[2].trim();
@@ -5100,10 +5109,10 @@ async function syncPlans(options2 = {}) {
5100
5109
  if (!key) throw new Error("STRIPE_SECRET_KEY is not set in .dev.vars or the environment.");
5101
5110
  const db = readD1Databases(appRoot)[0];
5102
5111
  if (!db) throw new Error("No d1_databases in wrangler.jsonc.");
5103
- const appRequire = createRequire2(join26(appRoot, "package.json"));
5112
+ const appRequire = createRequire2(join27(appRoot, "package.json"));
5104
5113
  const stripeModule = await import(pathToFileURL2(appRequire.resolve("stripe")).href);
5105
5114
  const stripe = new stripeModule.default(key, { apiVersion: "2026-08-26.dahlia" });
5106
- const appName = JSON.parse(readFileSync16(join26(appRoot, "package.json"), "utf8")).name ?? "app";
5115
+ const appName = JSON.parse(readFileSync16(join27(appRoot, "package.json"), "utf8")).name ?? "app";
5107
5116
  const everything = await stripe.products.list({ active: true, limit: 100 }).autoPagingToArray({ limit: 1e4 });
5108
5117
  const products = options2.all ? everything : everything.filter((product) => product.metadata?.flare_app === appName);
5109
5118
  if (products.length === 0) {
@@ -5124,9 +5133,9 @@ async function syncPlans(options2 = {}) {
5124
5133
  ${existingResult.output.trim()}`);
5125
5134
  }
5126
5135
  const known = new Set(extractJson(existingResult.output)[0]?.results.map((row) => row.stripe_price_id));
5127
- const dir = mkdtempSync4(join26(tmpdir4(), "flare-sync-"));
5136
+ const dir = mkdtempSync4(join27(tmpdir4(), "flare-sync-"));
5128
5137
  try {
5129
- const file = join26(dir, "sync-plans.sql");
5138
+ const file = join27(dir, "sync-plans.sql");
5130
5139
  writeFileSync20(file, renderSyncSql(rows));
5131
5140
  const result = await runWrangler(wrangler, ["d1", "execute", db.binding, ...target, "--file", file], appRoot, { capture: true });
5132
5141
  if (result.code !== 0) throw new Error(`Could not write plans:
@@ -5150,15 +5159,15 @@ ${rows.length} plan(s) synced to the ${options2.remote ? "remote" : "local"} dat
5150
5159
  }
5151
5160
 
5152
5161
  // src/commands/sync.ts
5153
- import { existsSync as existsSync25 } from "fs";
5154
- import { join as join27 } from "path";
5162
+ import { existsSync as existsSync26 } from "fs";
5163
+ import { join as join28 } from "path";
5155
5164
  import pc22 from "picocolors";
5156
5165
  async function syncTypes(options2 = {}) {
5157
5166
  const log2 = options2.log ?? ((message) => console.log(message));
5158
5167
  const appRoot = findAppRoot(options2.cwd ?? process.cwd());
5159
5168
  const all = await loadResources(appRoot);
5160
5169
  const policies = await loadPolicies(appRoot);
5161
- const tracksPolicies = policies.length > 0 || existsSync25(join27(appRoot, "policies/index.ts"));
5170
+ const tracksPolicies = policies.length > 0 || existsSync26(join28(appRoot, "policies/index.ts"));
5162
5171
  const plan = planFiles(all, tracksPolicies ? policies : void 0, readStack(appRoot));
5163
5172
  if (!options2.check) ensureSupportFiles(appRoot, log2);
5164
5173
  const results = applyPlan(appRoot, plan, { force: options2.force, dryRun: options2.check });
@@ -5206,7 +5215,7 @@ ${pc22.red("Out of sync:")} ${stale} file(s) would change. Run ${pc22.bold("flar
5206
5215
 
5207
5216
  // src/commands/user-role.ts
5208
5217
  import { rmSync as rmSync9, writeFileSync as writeFileSync21 } from "fs";
5209
- import { join as join28 } from "path";
5218
+ import { join as join29 } from "path";
5210
5219
  import pc23 from "picocolors";
5211
5220
  var ROLE = /^[a-z][a-z0-9_-]{0,31}$/;
5212
5221
  var EMAIL = /^[^\s@'"\\]+@[^\s@'"\\]+\.[^\s@'"\\]+$/;
@@ -5244,7 +5253,7 @@ ${result.output.trim()}`);
5244
5253
  return 0;
5245
5254
  }
5246
5255
  async function setUserRoleOnPrisma(appRoot, email, role, log2) {
5247
- const runner = join28(appRoot, ".flare-role-runner.mts");
5256
+ const runner = join29(appRoot, ".flare-role-runner.mts");
5248
5257
  const script = [
5249
5258
  `import "dotenv/config";`,
5250
5259
  `import { prisma } from "./lib/db";`,
@@ -5278,11 +5287,11 @@ async function setUserRoleOnPrisma(appRoot, email, role, log2) {
5278
5287
 
5279
5288
  // src/utils/install.ts
5280
5289
  import { spawn as spawn5 } from "child_process";
5281
- import { existsSync as existsSync26, readFileSync as readFileSync17, readdirSync as readdirSync10 } from "fs";
5282
- import { join as join29 } from "path";
5290
+ import { existsSync as existsSync27, readFileSync as readFileSync17, readdirSync as readdirSync10 } from "fs";
5291
+ import { join as join30 } from "path";
5283
5292
  function expectedPackages(dir) {
5284
- const npmLock = join29(dir, "package-lock.json");
5285
- if (existsSync26(npmLock)) {
5293
+ const npmLock = join30(dir, "package-lock.json");
5294
+ if (existsSync27(npmLock)) {
5286
5295
  try {
5287
5296
  const lock = JSON.parse(readFileSync17(npmLock, "utf8"));
5288
5297
  return Math.max(0, Object.keys(lock.packages ?? {}).length - 1);
@@ -5290,8 +5299,8 @@ function expectedPackages(dir) {
5290
5299
  return 0;
5291
5300
  }
5292
5301
  }
5293
- const pnpmLock = join29(dir, "pnpm-lock.yaml");
5294
- if (existsSync26(pnpmLock)) {
5302
+ const pnpmLock = join30(dir, "pnpm-lock.yaml");
5303
+ if (existsSync27(pnpmLock)) {
5295
5304
  try {
5296
5305
  const text = readFileSync17(pnpmLock, "utf8");
5297
5306
  const packages = text.slice(text.indexOf("\npackages:"));
@@ -5303,15 +5312,15 @@ function expectedPackages(dir) {
5303
5312
  return 0;
5304
5313
  }
5305
5314
  function installedPackages(dir) {
5306
- const root = join29(dir, "node_modules");
5307
- if (!existsSync26(root)) return 0;
5315
+ const root = join30(dir, "node_modules");
5316
+ if (!existsSync27(root)) return 0;
5308
5317
  let total = 0;
5309
5318
  try {
5310
5319
  for (const entry of readdirSync10(root, { withFileTypes: true })) {
5311
5320
  if (!entry.isDirectory() || entry.name === ".bin") continue;
5312
5321
  if (entry.name.startsWith("@")) {
5313
5322
  try {
5314
- total += readdirSync10(join29(root, entry.name)).length;
5323
+ total += readdirSync10(join30(root, entry.name)).length;
5315
5324
  } catch {
5316
5325
  }
5317
5326
  } else if (entry.name !== ".pnpm" && entry.name !== ".vite") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flaredev/cli",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "The flare command-line tool: create, generate, migrate, and deploy Flare apps",
5
5
  "license": "MIT",
6
6
  "author": "Muke Johnbaptist",
@@ -41,7 +41,7 @@
41
41
  "jiti": "^2.7.0",
42
42
  "jsonc-parser": "^3.3.1",
43
43
  "picocolors": "^1.1.1",
44
- "@flaredev/core": "0.7.1"
44
+ "@flaredev/core": "0.7.3"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=22"
@@ -36,7 +36,20 @@ export interface RowsQuery {
36
36
  * `(sort field, id)` together so rows sharing a sort value are neither skipped nor
37
37
  * repeated.
38
38
  */
39
- cursor?: { field: string; value: unknown; id: string; greaterThan: boolean };
39
+ cursor?: {
40
+ field: string;
41
+ value: unknown;
42
+ id: string;
43
+ greaterThan: boolean;
44
+ /**
45
+ * Whether the sort column holds a date.
46
+ *
47
+ * A cursor travels through a URL, so a Date arrives back as a number. SQLite stores
48
+ * dates as milliseconds and wants that number; Postgres wants a Date. Neither
49
+ * adapter can tell which a column is, and the store can — so it says.
50
+ */
51
+ isDate?: boolean;
52
+ };
40
53
  limit: number;
41
54
  /** Offset paging, for the page-number case. Never combined with a cursor. */
42
55
  offset?: number;
@@ -42,7 +42,7 @@ export type Result<T> = { ok: true; data: T } | Failure;
42
42
  */
43
43
  export const COUNT_LIMIT = 10_000;
44
44
 
45
- /** Dates are stored as milliseconds, and a cursor has to compare the way the column does. */
45
+ /** A cursor is JSON in a URL, so a Date has to cross it as a number. */
46
46
  function toCursorValue(value: unknown): string | number | boolean | null {
47
47
  if (value instanceof Date) return value.getTime();
48
48
  if (value === null || value === undefined) return null;
@@ -211,6 +211,16 @@ export function createResourceStore(options: ResourceStoreOptions) {
211
211
  if ("issues" in parsed) return fail(400, "Invalid query.", { queryIssues: parsed.issues });
212
212
  const { page, perPage, sort, q, filters, cursor } = parsed.query;
213
213
 
214
+ /**
215
+ * Whether a sort column holds a date, so the adapter can shape the cursor for it.
216
+ * The timestamps every row has, plus any date or datetime field.
217
+ */
218
+ const isDateField = (key: string) => {
219
+ if (key === "createdAt" || key === "updatedAt") return true;
220
+ const def = fields.find(([name]) => name === key)?.[1];
221
+ return def?.kind === "date" || def?.kind === "datetime";
222
+ };
223
+
214
224
  const searchable = fields.filter(([, def]) => isSearchable(def)).map(([key]) => key);
215
225
  const matching = { search: q ? { term: q, fields: searchable } : undefined, filters };
216
226
 
@@ -224,7 +234,9 @@ export function createResourceStore(options: ResourceStoreOptions) {
224
234
  rows.find({
225
235
  ...matching,
226
236
  sort: { field: sort.field, direction: descending ? "desc" : "asc" },
227
- cursor: cursor ? { field: sort.field, value: cursor.value, id: cursor.id, greaterThan: !descending } : undefined,
237
+ cursor: cursor
238
+ ? { field: sort.field, value: cursor.value, id: cursor.id, greaterThan: !descending, isDate: isDateField(sort.field) }
239
+ : undefined,
228
240
  limit: perPage + 1,
229
241
  offset: cursor ? undefined : (page - 1) * perPage,
230
242
  }),
@@ -97,8 +97,12 @@ export function prismaRows(delegate: PrismaDelegate, client: unknown): ResourceR
97
97
  async find(query) {
98
98
  const where = matching(query);
99
99
  if (query.cursor) {
100
- const { field, value, id, greaterThan } = query.cursor;
100
+ const { field, id, greaterThan, isDate } = query.cursor;
101
101
  const op = greaterThan ? "gt" : "lt";
102
+ // Postgres compares a timestamp against a Date, not the number the cursor
103
+ // carried. Without this, paging past the first page of a date-sorted list fails
104
+ // with "Expected DateTime, provided Int".
105
+ const value = isDate && (typeof query.cursor.value === "number" || typeof query.cursor.value === "string") ? new Date(query.cursor.value) : query.cursor.value;
102
106
  // (sort, id) as one comparison, so rows sharing a sort value are neither skipped
103
107
  // nor repeated. AND-ed with the filters rather than replacing them.
104
108
  const keyset = [{ [field]: { [op]: value } }, { AND: [{ [field]: value }, { id: { [op]: id } }] }];