@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
|
|
13
|
-
import { basename, dirname as dirname2, isAbsolute, join as
|
|
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
|
|
295
|
-
const
|
|
296
|
-
return
|
|
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 =
|
|
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 (
|
|
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(
|
|
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(
|
|
366
|
-
for (const path of CLOUDFLARE_ONLY) rmSync(
|
|
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(
|
|
369
|
-
appendFileSync(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
430
|
-
if (!
|
|
431
|
-
copyFileSync(source,
|
|
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
|
|
452
|
-
import { dirname as dirname6, join as
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
1450
|
-
import { join as
|
|
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 =
|
|
1455
|
-
if (!
|
|
1456
|
-
const jiti = createJiti(
|
|
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 =
|
|
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
|
|
1478
|
-
import { join as
|
|
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
|
|
1483
|
-
import { dirname as dirname4, join as
|
|
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
|
|
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
|
|
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 = () =>
|
|
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 =
|
|
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 =
|
|
1546
|
-
if (
|
|
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
|
|
1603
|
-
import { join as
|
|
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
|
|
1611
|
-
import { join as
|
|
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 =
|
|
1637
|
-
if (!
|
|
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 =
|
|
1679
|
-
if (!
|
|
1680
|
-
const jiti = createJiti2(
|
|
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(
|
|
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) =>
|
|
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 =
|
|
1766
|
-
const optional =
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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 && !
|
|
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) =>
|
|
1967
|
+
var listSql = (dir) => existsSync11(dir) ? readdirSync4(dir).filter((file) => file.endsWith(".sql")) : [];
|
|
1943
1968
|
async function generateSchemaMigration(appRoot, name, options2 = {}) {
|
|
1944
|
-
const dir =
|
|
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 =
|
|
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 =
|
|
1984
|
-
if (!prefix || !
|
|
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(
|
|
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
|
|
1991
|
-
import { dirname as dirname5, join as
|
|
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
|
|
1996
|
-
import { join as
|
|
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 =
|
|
2004
|
-
if (!
|
|
2005
|
-
const jiti = createJiti3(
|
|
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(
|
|
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 =
|
|
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) =>
|
|
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 (
|
|
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 (
|
|
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(
|
|
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 =
|
|
2726
|
-
if (
|
|
2727
|
-
const source = [
|
|
2728
|
-
(candidate) => (stack === "next" || !candidate.includes(`${sep}next${sep}`)) &&
|
|
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 =
|
|
2761
|
-
const text =
|
|
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 =
|
|
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 =
|
|
2778
|
-
if (!
|
|
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 (
|
|
2833
|
+
if (existsSync14(join13(appRoot, relative3))) {
|
|
2809
2834
|
log2(`${pc6.dim("kept".padEnd(9))} ${relative3} (already exists)`);
|
|
2810
2835
|
continue;
|
|
2811
2836
|
}
|
|
2812
|
-
mkdirSync5(
|
|
2813
|
-
writeFileSync7(
|
|
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(
|
|
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(
|
|
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 =
|
|
2826
|
-
if (!
|
|
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
|
|
2872
|
-
import { dirname as dirname7, join as
|
|
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 =
|
|
3401
|
-
if (
|
|
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(
|
|
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 =
|
|
3419
|
-
if (path === "lib/security.ts" &&
|
|
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 =
|
|
3430
|
-
if (
|
|
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 =
|
|
3457
|
+
const wranglerPath = join14(appRoot, "wrangler.jsonc");
|
|
3433
3458
|
let bindings = [];
|
|
3434
|
-
if (
|
|
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
|
|
3470
|
-
import { join as
|
|
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 =
|
|
3476
|
-
if (!
|
|
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
|
|
3570
|
-
import { dirname as dirname8, join as
|
|
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 =
|
|
3592
|
-
const exists =
|
|
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
|
|
3658
|
-
import { join as
|
|
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 =
|
|
3681
|
-
if (
|
|
3682
|
-
mkdirSync8(
|
|
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
|
|
3721
|
-
import { basename as basename2, join as
|
|
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 =
|
|
3767
|
+
const downDir = join18(appRoot, "migrations", "down");
|
|
3743
3768
|
mkdirSync9(downDir, { recursive: true });
|
|
3744
3769
|
for (const file of files) {
|
|
3745
|
-
const down =
|
|
3746
|
-
if (
|
|
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
|
|
3761
|
-
import { dirname as dirname9, join as
|
|
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 =
|
|
3780
|
-
const exists =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
3954
|
-
if (
|
|
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 =
|
|
3963
|
-
if (!
|
|
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(
|
|
4009
|
+
const dir = mkdtempSync(join20(tmpdir(), "flare-rollback-"));
|
|
3985
4010
|
try {
|
|
3986
|
-
const file =
|
|
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
|
|
4001
|
-
import { dirname as dirname10, join as
|
|
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(
|
|
4021
|
-
const roots = [
|
|
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() &&
|
|
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 (
|
|
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(
|
|
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(
|
|
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 &&
|
|
4089
|
-
const seeds = readdirSync7(
|
|
4090
|
-
(file) => new RegExp(`\\b${table}\\b`).test(readFileSync13(
|
|
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
|
|
4161
|
-
import { dirname as dirname11, join as
|
|
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" ? [
|
|
4175
|
-
return candidates.find((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 (!
|
|
4181
|
-
for (const entry of readdirSync8(
|
|
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(
|
|
4190
|
-
if (stack === "next") walk(
|
|
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 =
|
|
4199
|
-
if (!
|
|
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 =
|
|
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
|
|
4274
|
-
import { join as
|
|
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
|
|
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(
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4416
|
-
const all =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
4565
|
-
if (
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
4974
|
-
const file =
|
|
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
|
|
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
|
|
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 =
|
|
5026
|
-
if (
|
|
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(
|
|
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(
|
|
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(
|
|
5164
|
+
const dir = mkdtempSync4(join27(tmpdir4(), "flare-sync-"));
|
|
5135
5165
|
try {
|
|
5136
|
-
const file =
|
|
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
|
|
5161
|
-
import { join as
|
|
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 ||
|
|
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
|
|
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 =
|
|
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
|
|
5289
|
-
import { join as
|
|
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 =
|
|
5292
|
-
if (
|
|
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 =
|
|
5301
|
-
if (
|
|
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 =
|
|
5314
|
-
if (!
|
|
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(
|
|
5351
|
+
total += readdirSync10(join30(root, entry.name)).length;
|
|
5322
5352
|
} catch {
|
|
5323
5353
|
}
|
|
5324
5354
|
} else if (entry.name !== ".pnpm" && entry.name !== ".vite") {
|