@blinkk/root 3.0.3 → 3.0.5

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.
@@ -753,7 +753,7 @@ async function build(rootProjectDir, options) {
753
753
  const outFilePath2 = urlPath.slice(1);
754
754
  const outPath2 = path3.join(buildDir, outFilePath2);
755
755
  await makeDir(path3.dirname(outPath2));
756
- await writeFile(outPath2, body);
756
+ await writeFile(outPath2, normalizeLineEndings(body));
757
757
  printFileOutput(fileSize(outPath2), "dist/html/", outFilePath2);
758
758
  return;
759
759
  }
@@ -791,7 +791,7 @@ async function build(rootProjectDir, options) {
791
791
  } else if (rootConfig.minifyHtml) {
792
792
  html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
793
793
  }
794
- await writeFile(outPath, html);
794
+ await writeFile(outPath, normalizeLineEndings(html));
795
795
  printFileOutput(fileSize(outPath), "dist/html/", outFilePath);
796
796
  } catch (e) {
797
797
  logBuildError(
@@ -892,6 +892,9 @@ function formatParams(params) {
892
892
  return ` ${key}: ${value}`;
893
893
  }).join("\n");
894
894
  }
895
+ function normalizeLineEndings(str) {
896
+ return str.replace(/\r\n?/g, "\n");
897
+ }
895
898
 
896
899
  // src/cli/codegen.ts
897
900
  import { promises as fs3 } from "node:fs";
@@ -1107,11 +1110,11 @@ async function bundleTsFile(srcPath, outPath) {
1107
1110
  }
1108
1111
 
1109
1112
  // src/cli/dev.ts
1110
- import path8 from "node:path";
1113
+ import path12 from "node:path";
1111
1114
  import { fileURLToPath as fileURLToPath3 } from "node:url";
1112
1115
  import cookieParser from "cookie-parser";
1113
1116
  import { default as express } from "express";
1114
- import { dim as dim4 } from "kleur/colors";
1117
+ import { dim as dim5 } from "kleur/colors";
1115
1118
  import sirv from "sirv";
1116
1119
  import glob4 from "tiny-glob";
1117
1120
 
@@ -1391,12 +1394,1196 @@ function getSessionCookieSecret(rootConfig, rootDir) {
1391
1394
  return randString(36);
1392
1395
  }
1393
1396
 
1397
+ // src/cli/secrets.ts
1398
+ import { spawn as spawn3 } from "node:child_process";
1399
+ import fs8 from "node:fs";
1400
+ import path10 from "node:path";
1401
+ import * as readline from "node:readline";
1402
+ import { dim as dim3, green, red, yellow } from "kleur/colors";
1403
+
1404
+ // src/secrets/manifest.ts
1405
+ import fs5 from "node:fs";
1406
+ import path7 from "node:path";
1407
+ var MANIFEST_FILENAME = ".root.secrets.json";
1408
+ var GSM_KEY_RE = /^[A-Za-z0-9_-]{1,255}$/;
1409
+ var ENV_NAME_RE = /^[A-Za-z0-9_]+$/;
1410
+ function manifestPath(dir) {
1411
+ return path7.join(dir, MANIFEST_FILENAME);
1412
+ }
1413
+ function isValidEnvName(name) {
1414
+ return ENV_NAME_RE.test(name);
1415
+ }
1416
+ async function readManifest(filePath) {
1417
+ let raw;
1418
+ try {
1419
+ raw = await fs5.promises.readFile(filePath, "utf8");
1420
+ } catch (err) {
1421
+ if (err.code === "ENOENT") {
1422
+ return null;
1423
+ }
1424
+ throw err;
1425
+ }
1426
+ let parsed;
1427
+ try {
1428
+ parsed = JSON.parse(raw);
1429
+ } catch {
1430
+ throw new Error(`invalid JSON in ${filePath}`);
1431
+ }
1432
+ return validateManifest(parsed, filePath);
1433
+ }
1434
+ async function writeManifest(filePath, manifest) {
1435
+ const sortedSecrets = {};
1436
+ for (const name of Object.keys(manifest.secrets).sort()) {
1437
+ sortedSecrets[name] = manifest.secrets[name];
1438
+ }
1439
+ const ordered = {
1440
+ version: manifest.version,
1441
+ gcpProjectId: manifest.gcpProjectId,
1442
+ gsmKey: manifest.gsmKey,
1443
+ ...manifest.import ? { import: manifest.import } : {},
1444
+ secrets: sortedSecrets
1445
+ };
1446
+ await fs5.promises.mkdir(path7.dirname(filePath), { recursive: true });
1447
+ await fs5.promises.writeFile(
1448
+ filePath,
1449
+ JSON.stringify(ordered, null, 2) + "\n",
1450
+ "utf8"
1451
+ );
1452
+ }
1453
+ function emptyManifest(gcpProjectId, gsmKey) {
1454
+ return { version: 1, gcpProjectId, gsmKey, secrets: {} };
1455
+ }
1456
+ async function resolveManagedKeys(rootDir) {
1457
+ const sitePath = manifestPath(rootDir);
1458
+ const site = await readManifest(sitePath);
1459
+ if (!site) {
1460
+ return null;
1461
+ }
1462
+ const keys = [];
1463
+ const siteNames = /* @__PURE__ */ new Set();
1464
+ for (const [name, entry] of Object.entries(site.secrets)) {
1465
+ siteNames.add(name);
1466
+ keys.push({
1467
+ name,
1468
+ gsmKey: site.gsmKey,
1469
+ gcpProjectId: site.gcpProjectId,
1470
+ updatedAt: entry.updatedAt
1471
+ });
1472
+ }
1473
+ let shared;
1474
+ if (site.import) {
1475
+ const sharedPath = path7.resolve(rootDir, site.import.manifest);
1476
+ const loaded = await readManifest(sharedPath);
1477
+ if (!loaded) {
1478
+ throw new Error(`imported manifest not found: ${sharedPath}`);
1479
+ }
1480
+ shared = loaded;
1481
+ if (shared.gcpProjectId !== site.gcpProjectId) {
1482
+ throw new Error(
1483
+ `imported manifest ${sharedPath} uses gcpProjectId "${shared.gcpProjectId}" but the site uses "${site.gcpProjectId}"`
1484
+ );
1485
+ }
1486
+ const requested = resolveImportedNames(site.import, shared, sharedPath);
1487
+ for (const name of requested) {
1488
+ if (siteNames.has(name)) {
1489
+ throw new Error(
1490
+ `"${name}" is declared in both the site manifest and the imported shared manifest; remove it from one`
1491
+ );
1492
+ }
1493
+ const entry = shared.secrets[name];
1494
+ keys.push({
1495
+ name,
1496
+ gsmKey: shared.gsmKey,
1497
+ gcpProjectId: shared.gcpProjectId,
1498
+ updatedAt: entry.updatedAt
1499
+ });
1500
+ }
1501
+ }
1502
+ return { rootDir, site, shared, keys };
1503
+ }
1504
+ function resolveImportedNames(config, shared, sharedPath) {
1505
+ if (!config.keys || config.keys === "*") {
1506
+ return Object.keys(shared.secrets);
1507
+ }
1508
+ for (const name of config.keys) {
1509
+ if (!Object.prototype.hasOwnProperty.call(shared.secrets, name)) {
1510
+ throw new Error(`imported key "${name}" is not defined in ${sharedPath}`);
1511
+ }
1512
+ }
1513
+ return config.keys;
1514
+ }
1515
+ function validateManifest(value, filePath) {
1516
+ if (typeof value !== "object" || value === null) {
1517
+ throw new Error(`invalid manifest ${filePath}: expected an object`);
1518
+ }
1519
+ const obj = value;
1520
+ if (typeof obj.gcpProjectId !== "string" || !obj.gcpProjectId) {
1521
+ throw new Error(`invalid manifest ${filePath}: missing "gcpProjectId"`);
1522
+ }
1523
+ if (typeof obj.gsmKey !== "string" || !GSM_KEY_RE.test(obj.gsmKey)) {
1524
+ throw new Error(
1525
+ `invalid manifest ${filePath}: "gsmKey" must match ${GSM_KEY_RE}`
1526
+ );
1527
+ }
1528
+ const secretsValue = obj.secrets ?? {};
1529
+ if (typeof secretsValue !== "object" || secretsValue === null) {
1530
+ throw new Error(
1531
+ `invalid manifest ${filePath}: "secrets" must be an object`
1532
+ );
1533
+ }
1534
+ const secrets = {};
1535
+ for (const [name, entry] of Object.entries(
1536
+ secretsValue
1537
+ )) {
1538
+ if (!ENV_NAME_RE.test(name)) {
1539
+ throw new Error(
1540
+ `invalid manifest ${filePath}: secret name "${name}" must match ${ENV_NAME_RE}`
1541
+ );
1542
+ }
1543
+ if (typeof entry !== "object" || entry === null) {
1544
+ throw new Error(
1545
+ `invalid manifest ${filePath}: secret "${name}" must be an object`
1546
+ );
1547
+ }
1548
+ const e = entry;
1549
+ if (typeof e.updatedAt !== "string" || !e.updatedAt) {
1550
+ throw new Error(
1551
+ `invalid manifest ${filePath}: secret "${name}" missing "updatedAt"`
1552
+ );
1553
+ }
1554
+ secrets[name] = {
1555
+ updatedAt: e.updatedAt,
1556
+ ...typeof e.updatedBy === "string" ? { updatedBy: e.updatedBy } : {}
1557
+ };
1558
+ }
1559
+ let importConfig;
1560
+ if (obj.import !== void 0) {
1561
+ if (typeof obj.import !== "object" || obj.import === null) {
1562
+ throw new Error(
1563
+ `invalid manifest ${filePath}: "import" must be an object`
1564
+ );
1565
+ }
1566
+ const imp = obj.import;
1567
+ if (typeof imp.manifest !== "string" || !imp.manifest) {
1568
+ throw new Error(
1569
+ `invalid manifest ${filePath}: "import.manifest" must be a path`
1570
+ );
1571
+ }
1572
+ let keys;
1573
+ if (imp.keys === "*" || imp.keys === void 0) {
1574
+ keys = imp.keys;
1575
+ } else if (Array.isArray(imp.keys)) {
1576
+ keys = imp.keys.map((k) => {
1577
+ if (typeof k !== "string" || !ENV_NAME_RE.test(k)) {
1578
+ throw new Error(
1579
+ `invalid manifest ${filePath}: "import.keys" entries must match ${ENV_NAME_RE}`
1580
+ );
1581
+ }
1582
+ return k;
1583
+ });
1584
+ } else {
1585
+ throw new Error(
1586
+ `invalid manifest ${filePath}: "import.keys" must be an array or "*"`
1587
+ );
1588
+ }
1589
+ importConfig = { manifest: imp.manifest, ...keys ? { keys } : {} };
1590
+ }
1591
+ return {
1592
+ version: typeof obj.version === "number" ? obj.version : 1,
1593
+ gcpProjectId: obj.gcpProjectId,
1594
+ gsmKey: obj.gsmKey,
1595
+ ...importConfig ? { import: importConfig } : {},
1596
+ secrets
1597
+ };
1598
+ }
1599
+
1600
+ // src/secrets/secrets.ts
1601
+ import { spawn as spawn2 } from "node:child_process";
1602
+ import fs7 from "node:fs";
1603
+ import path9 from "node:path";
1604
+
1605
+ // src/secrets/env-file.ts
1606
+ import fs6 from "node:fs";
1607
+ import path8 from "node:path";
1608
+ import dotenv from "dotenv";
1609
+ async function readEnvFile(envPath2) {
1610
+ try {
1611
+ return await fs6.promises.readFile(envPath2, "utf8");
1612
+ } catch (err) {
1613
+ if (err.code === "ENOENT") {
1614
+ return "";
1615
+ }
1616
+ throw err;
1617
+ }
1618
+ }
1619
+ async function writeEnvFile(envPath2, content) {
1620
+ const dir = path8.dirname(envPath2);
1621
+ await fs6.promises.mkdir(dir, { recursive: true });
1622
+ const tmp = path8.join(dir, `.env.tmp-${process.pid}-${Date.now()}`);
1623
+ await fs6.promises.writeFile(tmp, content, "utf8");
1624
+ await fs6.promises.rename(tmp, envPath2);
1625
+ }
1626
+ function parseEnv(content) {
1627
+ return dotenv.parse(content);
1628
+ }
1629
+ function serializeEnvValue(value) {
1630
+ if (!value.includes("'") && !value.includes("\n") && !value.includes("\r")) {
1631
+ return `'${value}'`;
1632
+ }
1633
+ const escaped = value.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/"/g, '\\"');
1634
+ return `"${escaped}"`;
1635
+ }
1636
+ function upsertEnvVars(content, updates, removals = []) {
1637
+ const eol = detectEol(content);
1638
+ const removeSet = new Set(removals);
1639
+ const written = /* @__PURE__ */ new Set();
1640
+ const lines = content.length ? content.split(/\r?\n/) : [];
1641
+ if (lines.length > 0 && lines[lines.length - 1] === "" && /\r?\n$/.test(content)) {
1642
+ lines.pop();
1643
+ }
1644
+ const out = [];
1645
+ for (const line of lines) {
1646
+ const key = parseAssignmentKey(line);
1647
+ if (key && removeSet.has(key)) {
1648
+ continue;
1649
+ }
1650
+ if (key && Object.prototype.hasOwnProperty.call(updates, key)) {
1651
+ if (!written.has(key)) {
1652
+ out.push(`${key}=${serializeEnvValue(updates[key])}`);
1653
+ written.add(key);
1654
+ }
1655
+ continue;
1656
+ }
1657
+ out.push(line);
1658
+ }
1659
+ for (const key of Object.keys(updates)) {
1660
+ if (!written.has(key)) {
1661
+ out.push(`${key}=${serializeEnvValue(updates[key])}`);
1662
+ written.add(key);
1663
+ }
1664
+ }
1665
+ while (out.length > 0 && out[out.length - 1].trim() === "") {
1666
+ out.pop();
1667
+ }
1668
+ if (out.length === 0) {
1669
+ return "";
1670
+ }
1671
+ return out.join(eol) + eol;
1672
+ }
1673
+ function parseAssignmentKey(line) {
1674
+ const match = line.match(/^\s*(?:export\s+)?([\w.-]+)\s*=/);
1675
+ return match ? match[1] : null;
1676
+ }
1677
+ function detectEol(content) {
1678
+ const crlf = (content.match(/\r\n/g) || []).length;
1679
+ const lf = (content.match(/\n/g) || []).length - crlf;
1680
+ return crlf > lf ? "\r\n" : "\n";
1681
+ }
1682
+
1683
+ // src/secrets/gcloud.ts
1684
+ import { spawn } from "node:child_process";
1685
+ var GcloudError = class extends Error {
1686
+ code;
1687
+ stderr;
1688
+ constructor(code, message, stderr = "") {
1689
+ super(message);
1690
+ this.name = "GcloudError";
1691
+ this.code = code;
1692
+ this.stderr = stderr;
1693
+ }
1694
+ };
1695
+ function runGcloud(args, options = {}) {
1696
+ return new Promise((resolve, reject) => {
1697
+ const child = spawn("gcloud", args, {
1698
+ stdio: ["pipe", "pipe", "pipe"]
1699
+ });
1700
+ let stdout = "";
1701
+ let stderr = "";
1702
+ child.stdout.on("data", (chunk) => {
1703
+ stdout += chunk.toString();
1704
+ });
1705
+ child.stderr.on("data", (chunk) => {
1706
+ stderr += chunk.toString();
1707
+ });
1708
+ child.on("error", (err) => {
1709
+ if (err.code === "ENOENT") {
1710
+ reject(
1711
+ new GcloudError(
1712
+ "ENOENT",
1713
+ "The Google Cloud CLI (gcloud) was not found. Install it: https://cloud.google.com/sdk/docs/install"
1714
+ )
1715
+ );
1716
+ return;
1717
+ }
1718
+ reject(err);
1719
+ });
1720
+ child.on("close", (code) => {
1721
+ if (code === 0) {
1722
+ resolve(stdout);
1723
+ return;
1724
+ }
1725
+ reject(classifyError(stderr));
1726
+ });
1727
+ if (options.input !== void 0) {
1728
+ child.stdin.end(options.input);
1729
+ } else {
1730
+ child.stdin.end();
1731
+ }
1732
+ });
1733
+ }
1734
+ async function accessSecretJson(gsmKey, project) {
1735
+ let stdout;
1736
+ try {
1737
+ stdout = await runGcloud([
1738
+ "secrets",
1739
+ "versions",
1740
+ "access",
1741
+ "latest",
1742
+ "--secret",
1743
+ gsmKey,
1744
+ "--project",
1745
+ project
1746
+ ]);
1747
+ } catch (err) {
1748
+ if (err instanceof GcloudError && err.code === "NOT_FOUND") {
1749
+ return {};
1750
+ }
1751
+ throw err;
1752
+ }
1753
+ const trimmed = stdout.trim();
1754
+ if (!trimmed) {
1755
+ return {};
1756
+ }
1757
+ let parsed;
1758
+ try {
1759
+ parsed = JSON.parse(trimmed);
1760
+ } catch {
1761
+ throw new Error(
1762
+ `secret "${gsmKey}" does not contain valid JSON (expected an object of env-name to value)`
1763
+ );
1764
+ }
1765
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1766
+ throw new Error(`secret "${gsmKey}" must be a JSON object`);
1767
+ }
1768
+ return parsed;
1769
+ }
1770
+ async function writeSecretJson(gsmKey, project, data) {
1771
+ await ensureSecret(gsmKey, project);
1772
+ await runGcloud(
1773
+ [
1774
+ "secrets",
1775
+ "versions",
1776
+ "add",
1777
+ gsmKey,
1778
+ "--project",
1779
+ project,
1780
+ "--data-file=-"
1781
+ ],
1782
+ { input: JSON.stringify(data, null, 2) }
1783
+ );
1784
+ }
1785
+ async function ensureSecret(gsmKey, project) {
1786
+ try {
1787
+ await runGcloud([
1788
+ "secrets",
1789
+ "create",
1790
+ gsmKey,
1791
+ "--project",
1792
+ project,
1793
+ "--replication-policy",
1794
+ "automatic",
1795
+ "--labels",
1796
+ "managed-by=root"
1797
+ ]);
1798
+ } catch (err) {
1799
+ if (err instanceof GcloudError && (err.code === "ALREADY_EXISTS" || /already exists/i.test(err.stderr))) {
1800
+ return;
1801
+ }
1802
+ throw err;
1803
+ }
1804
+ }
1805
+ function classifyError(stderr) {
1806
+ const text = stderr.trim();
1807
+ if (/NOT_FOUND|was not found|does not exist/i.test(text)) {
1808
+ return new GcloudError("NOT_FOUND", text || "not found", stderr);
1809
+ }
1810
+ if (/ALREADY_EXISTS|already exists/i.test(text)) {
1811
+ return new GcloudError("ALREADY_EXISTS", text || "already exists", stderr);
1812
+ }
1813
+ if (/PERMISSION_DENIED|permission|forbidden|403/i.test(text)) {
1814
+ return new GcloudError(
1815
+ "PERMISSION_DENIED",
1816
+ "Permission denied by Google Cloud. Ensure your account has the right IAM roles (roles/secretmanager.admin to write, roles/secretmanager.secretAccessor to read).\n" + text,
1817
+ stderr
1818
+ );
1819
+ }
1820
+ if (/UNAUTHENTICATED|not authenticated|reauth|login/i.test(text)) {
1821
+ return new GcloudError(
1822
+ "UNAUTHENTICATED",
1823
+ "Not authenticated with Google Cloud. Run `gcloud auth login`.\n" + text,
1824
+ stderr
1825
+ );
1826
+ }
1827
+ return new GcloudError("UNKNOWN", text || "gcloud command failed", stderr);
1828
+ }
1829
+
1830
+ // src/secrets/hash.ts
1831
+ import crypto2 from "node:crypto";
1832
+ function randomSalt() {
1833
+ return crypto2.randomBytes(16).toString("hex");
1834
+ }
1835
+ function hashValue(salt, value) {
1836
+ return crypto2.createHash("sha256").update(salt).update("\0").update(value, "utf8").digest("hex");
1837
+ }
1838
+
1839
+ // src/secrets/secrets.ts
1840
+ var STATE_FILENAME = "secrets-sync.json";
1841
+ function localStatePath(rootDir) {
1842
+ return path9.join(rootDir, ".root", STATE_FILENAME);
1843
+ }
1844
+ function envPath(rootDir) {
1845
+ return path9.join(rootDir, ".env");
1846
+ }
1847
+ var inFlight = /* @__PURE__ */ new Map();
1848
+ function syncSecrets(options) {
1849
+ const { rootDir, apply, force = false } = options;
1850
+ if (apply && !force) {
1851
+ const existing = inFlight.get(rootDir);
1852
+ if (existing) {
1853
+ return existing;
1854
+ }
1855
+ const promise = doSync(options).finally(() => inFlight.delete(rootDir));
1856
+ inFlight.set(rootDir, promise);
1857
+ return promise;
1858
+ }
1859
+ return doSync(options);
1860
+ }
1861
+ async function doSync(options) {
1862
+ const { rootDir, apply, force = false } = options;
1863
+ const result = {
1864
+ changed: [],
1865
+ kept: [],
1866
+ conflicts: [],
1867
+ overwritten: [],
1868
+ removed: [],
1869
+ errors: [],
1870
+ ranNetwork: false
1871
+ };
1872
+ const resolved = await resolveManagedKeys(rootDir);
1873
+ if (!resolved) {
1874
+ return result;
1875
+ }
1876
+ const envFilePath = envPath(rootDir);
1877
+ const content = await readEnvFile(envFilePath);
1878
+ const parsed = parseEnv(content);
1879
+ const state = await readLocalState(rootDir);
1880
+ const plans = resolved.keys.map((key) => {
1881
+ const synced = state.secrets[key.name];
1882
+ const envVal = parsed[key.name];
1883
+ const remoteChanged = force || !synced || key.updatedAt !== synced.updatedAt;
1884
+ const localChanged = synced ? hashValue(state.salt, envVal ?? "") !== synced.hash : envVal !== void 0;
1885
+ return { key, synced, envVal, remoteChanged, localChanged };
1886
+ });
1887
+ const needsTheirs = (plan) => force || !plan.synced || plan.remoteChanged && !plan.localChanged;
1888
+ const blobs = /* @__PURE__ */ new Map();
1889
+ const toFetch = /* @__PURE__ */ new Map();
1890
+ for (const plan of plans) {
1891
+ if (needsTheirs(plan)) {
1892
+ toFetch.set(plan.key.gsmKey, plan.key.gcpProjectId);
1893
+ }
1894
+ }
1895
+ for (const [gsmKey, project] of toFetch) {
1896
+ try {
1897
+ blobs.set(gsmKey, await accessSecretJson(gsmKey, project));
1898
+ result.ranNetwork = true;
1899
+ } catch (err) {
1900
+ result.errors.push({ gsmKey, message: err?.message || String(err) });
1901
+ }
1902
+ }
1903
+ const updates = {};
1904
+ const nextState = { salt: state.salt, secrets: { ...state.secrets } };
1905
+ for (const plan of plans) {
1906
+ const name = plan.key.name;
1907
+ if (!needsTheirs(plan)) {
1908
+ if (plan.remoteChanged && plan.localChanged) {
1909
+ result.conflicts.push(name);
1910
+ } else if (!plan.remoteChanged && plan.localChanged) {
1911
+ result.kept.push(name);
1912
+ }
1913
+ continue;
1914
+ }
1915
+ if (!blobs.has(plan.key.gsmKey)) {
1916
+ continue;
1917
+ }
1918
+ const theirs = blobs.get(plan.key.gsmKey)[name];
1919
+ if (theirs === void 0) {
1920
+ continue;
1921
+ }
1922
+ if (!plan.synced && plan.envVal !== void 0 && plan.envVal !== theirs) {
1923
+ result.overwritten.push(name);
1924
+ }
1925
+ updates[name] = theirs;
1926
+ nextState.secrets[name] = {
1927
+ updatedAt: plan.key.updatedAt,
1928
+ hash: hashValue(state.salt, theirs)
1929
+ };
1930
+ result.changed.push(name);
1931
+ }
1932
+ const managed = new Set(resolved.keys.map((key) => key.name));
1933
+ const removals = [];
1934
+ for (const name of Object.keys(state.secrets)) {
1935
+ if (!managed.has(name)) {
1936
+ removals.push(name);
1937
+ delete nextState.secrets[name];
1938
+ result.removed.push(name);
1939
+ }
1940
+ }
1941
+ if (apply) {
1942
+ if (Object.keys(updates).length > 0 || removals.length > 0) {
1943
+ const next = upsertEnvVars(content, updates, removals);
1944
+ if (next !== content) {
1945
+ await writeEnvFile(envFilePath, next);
1946
+ }
1947
+ for (const [name, value] of Object.entries(updates)) {
1948
+ process.env[name] = value;
1949
+ }
1950
+ for (const name of removals) {
1951
+ delete process.env[name];
1952
+ }
1953
+ }
1954
+ await writeLocalState(rootDir, nextState);
1955
+ }
1956
+ return result;
1957
+ }
1958
+ async function setSecret(options) {
1959
+ const { rootDir, manifestFilePath, name, value } = options;
1960
+ if (!isValidEnvName(name)) {
1961
+ throw new Error(`invalid secret name "${name}" (must match [A-Za-z0-9_]+)`);
1962
+ }
1963
+ const manifest = await readManifest(manifestFilePath);
1964
+ if (!manifest) {
1965
+ throw new Error(
1966
+ `no manifest at ${manifestFilePath}; run \`root secrets init\` first`
1967
+ );
1968
+ }
1969
+ const blob = await accessSecretJson(manifest.gsmKey, manifest.gcpProjectId);
1970
+ blob[name] = value;
1971
+ await writeSecretJson(manifest.gsmKey, manifest.gcpProjectId, blob);
1972
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1973
+ const updatedBy = options.updatedBy ?? await getGitEmail();
1974
+ manifest.secrets[name] = { updatedAt, ...updatedBy ? { updatedBy } : {} };
1975
+ await writeManifest(manifestFilePath, manifest);
1976
+ const envFilePath = envPath(rootDir);
1977
+ const content = await readEnvFile(envFilePath);
1978
+ const next = upsertEnvVars(content, { [name]: value });
1979
+ if (next !== content) {
1980
+ await writeEnvFile(envFilePath, next);
1981
+ }
1982
+ const state = await readLocalState(rootDir);
1983
+ state.secrets[name] = { updatedAt, hash: hashValue(state.salt, value) };
1984
+ await writeLocalState(rootDir, state);
1985
+ }
1986
+ async function removeSecret(options) {
1987
+ const { rootDir, manifestFilePath, name } = options;
1988
+ const manifest = await readManifest(manifestFilePath);
1989
+ if (!manifest) {
1990
+ throw new Error(`no manifest at ${manifestFilePath}`);
1991
+ }
1992
+ if (Object.prototype.hasOwnProperty.call(manifest.secrets, name)) {
1993
+ delete manifest.secrets[name];
1994
+ await writeManifest(manifestFilePath, manifest);
1995
+ }
1996
+ const blob = await accessSecretJson(manifest.gsmKey, manifest.gcpProjectId);
1997
+ if (Object.prototype.hasOwnProperty.call(blob, name)) {
1998
+ delete blob[name];
1999
+ await writeSecretJson(manifest.gsmKey, manifest.gcpProjectId, blob);
2000
+ }
2001
+ const envFilePath = envPath(rootDir);
2002
+ const content = await readEnvFile(envFilePath);
2003
+ const next = upsertEnvVars(content, {}, [name]);
2004
+ if (next !== content) {
2005
+ await writeEnvFile(envFilePath, next);
2006
+ }
2007
+ const state = await readLocalState(rootDir);
2008
+ if (state.secrets[name]) {
2009
+ delete state.secrets[name];
2010
+ await writeLocalState(rootDir, state);
2011
+ }
2012
+ }
2013
+ async function pushEnvToSecrets(options) {
2014
+ const { rootDir, manifestFilePath, only } = options;
2015
+ const manifest = await readManifest(manifestFilePath);
2016
+ if (!manifest) {
2017
+ throw new Error(
2018
+ `no manifest at ${manifestFilePath}; run \`root secrets init\` first`
2019
+ );
2020
+ }
2021
+ const content = await readEnvFile(envPath(rootDir));
2022
+ const parsed = parseEnv(content);
2023
+ const skipped = [];
2024
+ const sharedNames = await sharedImportedNames(rootDir, manifestFilePath);
2025
+ const requested = only && only.length > 0 ? only : Object.keys(parsed);
2026
+ const candidates = [];
2027
+ for (const name of requested) {
2028
+ if (!Object.prototype.hasOwnProperty.call(parsed, name)) {
2029
+ skipped.push({ name, reason: "not found in .env" });
2030
+ } else if (!isValidEnvName(name)) {
2031
+ skipped.push({ name, reason: "invalid name" });
2032
+ } else if (sharedNames.has(name)) {
2033
+ skipped.push({ name, reason: "provided by shared manifest" });
2034
+ } else {
2035
+ candidates.push(name);
2036
+ }
2037
+ }
2038
+ if (candidates.length === 0) {
2039
+ return { pushed: [], skipped, aborted: false, gsmKey: manifest.gsmKey };
2040
+ }
2041
+ if (options.confirm && !await options.confirm(candidates, manifest.gsmKey)) {
2042
+ return { pushed: [], skipped, aborted: true, gsmKey: manifest.gsmKey };
2043
+ }
2044
+ const blob = await accessSecretJson(manifest.gsmKey, manifest.gcpProjectId);
2045
+ for (const name of candidates) {
2046
+ blob[name] = parsed[name];
2047
+ }
2048
+ await writeSecretJson(manifest.gsmKey, manifest.gcpProjectId, blob);
2049
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2050
+ const updatedBy = options.updatedBy ?? await getGitEmail();
2051
+ for (const name of candidates) {
2052
+ manifest.secrets[name] = { updatedAt, ...updatedBy ? { updatedBy } : {} };
2053
+ }
2054
+ await writeManifest(manifestFilePath, manifest);
2055
+ const state = await readLocalState(rootDir);
2056
+ for (const name of candidates) {
2057
+ state.secrets[name] = {
2058
+ updatedAt,
2059
+ hash: hashValue(state.salt, parsed[name])
2060
+ };
2061
+ }
2062
+ await writeLocalState(rootDir, state);
2063
+ return {
2064
+ pushed: candidates,
2065
+ skipped,
2066
+ aborted: false,
2067
+ gsmKey: manifest.gsmKey
2068
+ };
2069
+ }
2070
+ async function sharedImportedNames(rootDir, manifestFilePath) {
2071
+ if (path9.resolve(manifestFilePath) !== path9.resolve(manifestPath(rootDir))) {
2072
+ return /* @__PURE__ */ new Set();
2073
+ }
2074
+ try {
2075
+ const resolved = await resolveManagedKeys(rootDir);
2076
+ if (!resolved || !resolved.shared) {
2077
+ return /* @__PURE__ */ new Set();
2078
+ }
2079
+ const sharedGsmKey = resolved.shared.gsmKey;
2080
+ return new Set(
2081
+ resolved.keys.filter((key) => key.gsmKey === sharedGsmKey).map((key) => key.name)
2082
+ );
2083
+ } catch {
2084
+ return /* @__PURE__ */ new Set();
2085
+ }
2086
+ }
2087
+ async function getSecretsStatus(rootDir) {
2088
+ const resolved = await resolveManagedKeys(rootDir);
2089
+ if (!resolved) {
2090
+ return { resolved: null, keys: [] };
2091
+ }
2092
+ const content = await readEnvFile(envPath(rootDir));
2093
+ const parsed = parseEnv(content);
2094
+ const state = await readLocalState(rootDir);
2095
+ const keys = resolved.keys.map((key) => {
2096
+ const synced = state.secrets[key.name];
2097
+ const envVal = parsed[key.name];
2098
+ const remoteChanged = !synced || key.updatedAt !== synced.updatedAt;
2099
+ const localChanged = synced ? hashValue(state.salt, envVal ?? "") !== synced.hash : envVal !== void 0;
2100
+ let kind;
2101
+ if (!synced) {
2102
+ kind = "not-pulled";
2103
+ } else if (remoteChanged && localChanged) {
2104
+ kind = "conflict";
2105
+ } else if (remoteChanged) {
2106
+ kind = "remote-newer";
2107
+ } else if (localChanged) {
2108
+ kind = "locally-edited";
2109
+ } else {
2110
+ kind = "in-sync";
2111
+ }
2112
+ return {
2113
+ name: key.name,
2114
+ gsmKey: key.gsmKey,
2115
+ updatedAt: key.updatedAt,
2116
+ kind,
2117
+ inEnv: envVal !== void 0
2118
+ };
2119
+ });
2120
+ return { resolved, keys };
2121
+ }
2122
+ async function isSecretsSyncEnabled(rootDir) {
2123
+ if (isSyncDisabledByEnv()) {
2124
+ return false;
2125
+ }
2126
+ return fileExists(manifestPath(rootDir));
2127
+ }
2128
+ function isSyncDisabledByEnv() {
2129
+ const value = (process.env.ROOT_DISABLE_SECRETS_SYNC || "").trim().toLowerCase();
2130
+ return value !== "" && value !== "0" && value !== "false";
2131
+ }
2132
+ async function readLocalState(rootDir) {
2133
+ try {
2134
+ const raw = await fs7.promises.readFile(localStatePath(rootDir), "utf8");
2135
+ const parsed = JSON.parse(raw);
2136
+ if (parsed && typeof parsed === "object" && typeof parsed.salt === "string" && parsed.secrets && typeof parsed.secrets === "object") {
2137
+ return {
2138
+ salt: parsed.salt,
2139
+ secrets: parsed.secrets
2140
+ };
2141
+ }
2142
+ } catch {
2143
+ }
2144
+ return { salt: randomSalt(), secrets: {} };
2145
+ }
2146
+ async function writeLocalState(rootDir, state) {
2147
+ const file = localStatePath(rootDir);
2148
+ await fs7.promises.mkdir(path9.dirname(file), { recursive: true });
2149
+ await fs7.promises.writeFile(
2150
+ file,
2151
+ JSON.stringify(state, null, 2) + "\n",
2152
+ "utf8"
2153
+ );
2154
+ }
2155
+ function getGitEmail() {
2156
+ return new Promise((resolve) => {
2157
+ try {
2158
+ const child = spawn2("git", ["config", "user.email"]);
2159
+ let out = "";
2160
+ child.stdout.on("data", (chunk) => {
2161
+ out += chunk.toString();
2162
+ });
2163
+ child.on("error", () => resolve(void 0));
2164
+ child.on("close", (code) => {
2165
+ resolve(code === 0 && out.trim() ? out.trim() : void 0);
2166
+ });
2167
+ child.stdin.end();
2168
+ } catch {
2169
+ resolve(void 0);
2170
+ }
2171
+ });
2172
+ }
2173
+
2174
+ // src/cli/secrets.ts
2175
+ var BAR = dim3("\u2503");
2176
+ var DEV_SYNC_TIMEOUT_MS = 1e4;
2177
+ function registerSecretsCommands(program) {
2178
+ const secrets = program.command("secrets").description(
2179
+ "manage shared secrets backed by Google Cloud Secret Manager (requires the gcloud CLI)"
2180
+ );
2181
+ secrets.command("init").description("create a secrets manifest").requiredOption("--gcp-project <id>", "GCP project id").requiredOption("--gsm-key <key>", "Secret Manager key for this manifest").option(
2182
+ "--manifest <path>",
2183
+ "manifest path (defaults to ./.root.secrets.json)"
2184
+ ).option("--import <path>", "path to a shared manifest to import keys from").option(
2185
+ "--import-keys <names>",
2186
+ "comma-separated subset of shared keys to import"
2187
+ ).action(action(secretsInit));
2188
+ secrets.command("set <name>").description(
2189
+ "store a secret value, read from a prompt (TTY) or piped stdin"
2190
+ ).option(
2191
+ "--manifest <path>",
2192
+ "target manifest (defaults to ./.root.secrets.json)"
2193
+ ).action(action(secretsSet));
2194
+ secrets.command("rm <name>").description("remove a secret").option(
2195
+ "--manifest <path>",
2196
+ "target manifest (defaults to ./.root.secrets.json)"
2197
+ ).action(action(secretsRm));
2198
+ secrets.command("sync").description("three-way merge managed secrets into .env").action(action(secretsSync));
2199
+ secrets.command("pull").description(
2200
+ "force-download managed secrets into .env (overwrites local edits)"
2201
+ ).action(action(secretsPull));
2202
+ secrets.command("push").description(
2203
+ "push .env values up to Secret Manager and record them in the manifest"
2204
+ ).option(
2205
+ "--manifest <path>",
2206
+ "target manifest (defaults to ./.root.secrets.json)"
2207
+ ).option(
2208
+ "--keys <names>",
2209
+ "comma-separated subset of .env keys (default: all)"
2210
+ ).option("--yes", "skip the confirmation prompt").action(action(secretsPush));
2211
+ secrets.command("status").description("show managed secrets and their sync status (no network)").action(action(secretsStatus));
2212
+ }
2213
+ async function secretsInit(opts) {
2214
+ const rootDir = process.cwd();
2215
+ const target = opts.manifest ? path10.resolve(rootDir, opts.manifest) : manifestPath(rootDir);
2216
+ if (await readManifest(target)) {
2217
+ console.log(
2218
+ `${BAR} ${yellow("secrets:")} manifest already exists at ${rel(rootDir, target)}`
2219
+ );
2220
+ return;
2221
+ }
2222
+ const manifest = emptyManifest(opts.gcpProject, opts.gsmKey);
2223
+ if (opts.import) {
2224
+ const importConfig = { manifest: opts.import };
2225
+ if (opts.importKeys) {
2226
+ const keys = opts.importKeys.split(",").map((k) => k.trim()).filter(Boolean);
2227
+ if (keys.length > 0) {
2228
+ importConfig.keys = keys;
2229
+ }
2230
+ }
2231
+ manifest.import = importConfig;
2232
+ }
2233
+ await writeManifest(target, manifest);
2234
+ const addedGitignore = await ensureRootGitignored(rootDir);
2235
+ console.log();
2236
+ console.log(`${BAR} ${green("secrets:")} created ${rel(rootDir, target)}`);
2237
+ if (addedGitignore) {
2238
+ console.log(`${BAR} added ${dim3(".root/")} to .gitignore`);
2239
+ }
2240
+ await warnIfEnvNotIgnored(rootDir);
2241
+ console.log(
2242
+ `${BAR} ${dim3("next: `root secrets set <NAME>` to store a value")}`
2243
+ );
2244
+ console.log();
2245
+ }
2246
+ async function secretsSet(name, opts) {
2247
+ const rootDir = process.cwd();
2248
+ const manifestFilePath = opts.manifest ? path10.resolve(rootDir, opts.manifest) : manifestPath(rootDir);
2249
+ const secretValue = await readSecretValue(name);
2250
+ if (!secretValue) {
2251
+ throw new Error("no value provided on stdin");
2252
+ }
2253
+ await setSecret({ rootDir, manifestFilePath, name, value: secretValue });
2254
+ console.log(`${BAR} ${green("secrets:")} set ${name}`);
2255
+ console.log(
2256
+ `${BAR} ${dim3(`commit ${rel(rootDir, manifestFilePath)} to share this change`)}`
2257
+ );
2258
+ }
2259
+ async function secretsRm(name, opts) {
2260
+ const rootDir = process.cwd();
2261
+ const manifestFilePath = opts.manifest ? path10.resolve(rootDir, opts.manifest) : manifestPath(rootDir);
2262
+ await removeSecret({ rootDir, manifestFilePath, name });
2263
+ console.log(`${BAR} ${green("secrets:")} removed ${name}`);
2264
+ console.log(
2265
+ `${BAR} ${dim3(`commit ${rel(rootDir, manifestFilePath)} to share this change`)}`
2266
+ );
2267
+ }
2268
+ async function secretsPush(opts) {
2269
+ const rootDir = process.cwd();
2270
+ const manifestFilePath = opts.manifest ? path10.resolve(rootDir, opts.manifest) : manifestPath(rootDir);
2271
+ const only = opts.keys ? opts.keys.split(",").map((k) => k.trim()).filter(Boolean) : void 0;
2272
+ const confirm = opts.yes ? void 0 : async (names, gsmKey) => {
2273
+ if (!process.stdin.isTTY) {
2274
+ throw new Error(
2275
+ "refusing to import without --yes on a non-interactive shell"
2276
+ );
2277
+ }
2278
+ console.log();
2279
+ console.log(
2280
+ `${BAR} ${green("secrets:")} push ${names.length} value(s) from .env to "${gsmKey}":`
2281
+ );
2282
+ for (const name of names) {
2283
+ console.log(`${BAR} ${name}`);
2284
+ }
2285
+ return promptYesNo(`${BAR} continue? [y/N] `);
2286
+ };
2287
+ const result = await pushEnvToSecrets({
2288
+ rootDir,
2289
+ manifestFilePath,
2290
+ only,
2291
+ confirm
2292
+ });
2293
+ printPushResult(result, rootDir, manifestFilePath);
2294
+ }
2295
+ async function secretsSync() {
2296
+ const rootDir = process.cwd();
2297
+ const result = await syncSecrets({ rootDir, apply: true });
2298
+ printSyncSummary(result);
2299
+ failOnSyncErrors(result);
2300
+ }
2301
+ async function secretsPull() {
2302
+ const rootDir = process.cwd();
2303
+ const result = await syncSecrets({ rootDir, apply: true, force: true });
2304
+ printSyncSummary(result, { pull: true });
2305
+ failOnSyncErrors(result);
2306
+ }
2307
+ function failOnSyncErrors(result) {
2308
+ if (result.errors.length > 0) {
2309
+ process.exitCode = 1;
2310
+ }
2311
+ }
2312
+ async function secretsStatus() {
2313
+ const rootDir = process.cwd();
2314
+ const { resolved, keys } = await getSecretsStatus(rootDir);
2315
+ if (!resolved) {
2316
+ console.log(
2317
+ `${BAR} ${yellow("secrets:")} no ${MANIFEST_FILENAME} here; run \`root secrets init\``
2318
+ );
2319
+ return;
2320
+ }
2321
+ console.log();
2322
+ console.log(
2323
+ `${BAR} ${green("secrets:")} ${resolved.site.gsmKey} ${dim3(`(${resolved.site.gcpProjectId})`)}`
2324
+ );
2325
+ if (resolved.shared) {
2326
+ console.log(`${BAR} imports ${dim3(resolved.shared.gsmKey)}`);
2327
+ }
2328
+ if (keys.length === 0) {
2329
+ console.log(`${BAR} ${dim3("no managed keys yet")}`);
2330
+ }
2331
+ for (const key of keys) {
2332
+ console.log(`${BAR} ${formatStatus(key)}`);
2333
+ }
2334
+ console.log();
2335
+ }
2336
+ async function syncSecretsOnDev(rootDir) {
2337
+ if (!await isSecretsSyncEnabled(rootDir)) {
2338
+ return;
2339
+ }
2340
+ try {
2341
+ const result = await withTimeout(
2342
+ syncSecrets({ rootDir, apply: true }),
2343
+ DEV_SYNC_TIMEOUT_MS
2344
+ );
2345
+ printSyncSummary(result, { dev: true });
2346
+ } catch (err) {
2347
+ console.log();
2348
+ console.log(
2349
+ `${BAR} ${yellow("secrets:")} sync skipped (${firstLine(err?.message || String(err))})`
2350
+ );
2351
+ console.log();
2352
+ }
2353
+ }
2354
+ function formatStatus(key) {
2355
+ switch (key.kind) {
2356
+ case "in-sync":
2357
+ return `${green("\u2713")} ${key.name}`;
2358
+ case "remote-newer":
2359
+ return `${yellow("\u2193")} ${key.name} ${dim3("(update available \u2014 run `root secrets sync`)")}`;
2360
+ case "locally-edited":
2361
+ return `${dim3("\u2022")} ${key.name} ${dim3("(locally edited)")}`;
2362
+ case "conflict":
2363
+ return `${yellow("!")} ${key.name} ${dim3("(changed locally & remotely)")}`;
2364
+ case "not-pulled":
2365
+ return `${yellow("\u2193")} ${key.name} ${dim3("(not pulled yet)")}`;
2366
+ default:
2367
+ return key.name;
2368
+ }
2369
+ }
2370
+ function printSyncSummary(result, opts = {}) {
2371
+ const notable = result.changed.length > 0 || result.removed.length > 0 || result.conflicts.length > 0 || result.overwritten.length > 0 || result.errors.length > 0;
2372
+ if (opts.dev && !notable) {
2373
+ return;
2374
+ }
2375
+ const parts = [];
2376
+ if (result.changed.length) {
2377
+ parts.push(green(`${result.changed.length} updated`));
2378
+ }
2379
+ if (result.removed.length) {
2380
+ parts.push(`${result.removed.length} removed`);
2381
+ }
2382
+ if (result.kept.length) {
2383
+ parts.push(dim3(`${result.kept.length} local`));
2384
+ }
2385
+ if (result.conflicts.length) {
2386
+ parts.push(yellow(`${result.conflicts.length} conflict`));
2387
+ }
2388
+ if (result.errors.length) {
2389
+ parts.push(red(`${result.errors.length} error`));
2390
+ }
2391
+ console.log();
2392
+ console.log(
2393
+ `${BAR} ${green("secrets:")} ${parts.length ? parts.join(", ") : "up to date"}`
2394
+ );
2395
+ for (const name of result.changed) {
2396
+ console.log(`${BAR} ${green("+")} ${name}`);
2397
+ }
2398
+ for (const name of result.removed) {
2399
+ console.log(`${BAR} ${dim3("-")} ${name} ${dim3("(removed)")}`);
2400
+ }
2401
+ for (const name of result.overwritten) {
2402
+ console.log(
2403
+ `${BAR} ${yellow("!")} ${name} ${dim3("(local value replaced)")}`
2404
+ );
2405
+ }
2406
+ for (const name of result.conflicts) {
2407
+ console.log(
2408
+ `${BAR} ${yellow("!")} ${name} ${dim3(
2409
+ "(changed locally & remotely; keeping yours \u2014 `root secrets pull` to take remote)"
2410
+ )}`
2411
+ );
2412
+ }
2413
+ for (const err of result.errors) {
2414
+ console.log(
2415
+ `${BAR} ${red("\xD7")} ${err.gsmKey}: ${firstLine(err.message)}`
2416
+ );
2417
+ }
2418
+ console.log();
2419
+ }
2420
+ function printPushResult(result, rootDir, manifestFilePath) {
2421
+ if (result.aborted) {
2422
+ console.log(`${BAR} ${yellow("secrets:")} aborted, nothing written`);
2423
+ return;
2424
+ }
2425
+ console.log();
2426
+ if (result.pushed.length === 0) {
2427
+ console.log(`${BAR} ${yellow("secrets:")} nothing to push`);
2428
+ } else {
2429
+ console.log(
2430
+ `${BAR} ${green("secrets:")} pushed ${result.pushed.length} value(s) to "${result.gsmKey}"`
2431
+ );
2432
+ for (const name of result.pushed) {
2433
+ console.log(`${BAR} ${green("+")} ${name}`);
2434
+ }
2435
+ }
2436
+ for (const skip of result.skipped) {
2437
+ console.log(`${BAR} ${dim3("-")} ${skip.name} ${dim3(`(${skip.reason})`)}`);
2438
+ }
2439
+ if (result.pushed.length > 0) {
2440
+ console.log(
2441
+ `${BAR} ${dim3(`commit ${rel(rootDir, manifestFilePath)} to share`)}`
2442
+ );
2443
+ }
2444
+ console.log();
2445
+ }
2446
+ async function ensureRootGitignored(rootDir) {
2447
+ const gitignore = path10.join(rootDir, ".gitignore");
2448
+ let content = "";
2449
+ try {
2450
+ content = await fs8.promises.readFile(gitignore, "utf8");
2451
+ } catch {
2452
+ }
2453
+ const lines = content.split(/\r?\n/).map((line) => line.trim());
2454
+ if (lines.includes(".root/") || lines.includes(".root")) {
2455
+ return false;
2456
+ }
2457
+ const prefix = content && !content.endsWith("\n") ? "\n" : "";
2458
+ await fs8.promises.writeFile(gitignore, `${content}${prefix}.root/
2459
+ `, "utf8");
2460
+ return true;
2461
+ }
2462
+ async function warnIfEnvNotIgnored(rootDir) {
2463
+ const ignored = await isPathGitIgnored(rootDir, ".env");
2464
+ if (ignored === false) {
2465
+ console.log(
2466
+ `${BAR} ${yellow("warning:")} ${dim3(".env is not git-ignored \u2014 add it to .gitignore")}`
2467
+ );
2468
+ }
2469
+ }
2470
+ function isPathGitIgnored(rootDir, relPath) {
2471
+ return new Promise((resolve) => {
2472
+ try {
2473
+ const child = spawn3("git", ["check-ignore", "--quiet", relPath], {
2474
+ cwd: rootDir
2475
+ });
2476
+ child.on("error", () => resolve(void 0));
2477
+ child.on("close", (code) => {
2478
+ if (code === 0) {
2479
+ resolve(true);
2480
+ } else if (code === 1) {
2481
+ resolve(false);
2482
+ } else {
2483
+ resolve(void 0);
2484
+ }
2485
+ });
2486
+ } catch {
2487
+ resolve(void 0);
2488
+ }
2489
+ });
2490
+ }
2491
+ function readSecretValue(name) {
2492
+ if (process.stdin.isTTY) {
2493
+ return promptSecret(`Value for ${name}: `);
2494
+ }
2495
+ return readStdin().then(stripOneTrailingNewline);
2496
+ }
2497
+ function readStdin() {
2498
+ return new Promise((resolve, reject) => {
2499
+ let data = "";
2500
+ process.stdin.setEncoding("utf8");
2501
+ process.stdin.on("data", (chunk) => {
2502
+ data += chunk;
2503
+ });
2504
+ process.stdin.on("end", () => resolve(data));
2505
+ process.stdin.on("error", reject);
2506
+ });
2507
+ }
2508
+ function stripOneTrailingNewline(value) {
2509
+ return value.replace(/\r?\n$/, "");
2510
+ }
2511
+ function promptYesNo(question) {
2512
+ return new Promise((resolve) => {
2513
+ const rl = readline.createInterface({
2514
+ input: process.stdin,
2515
+ output: process.stdout
2516
+ });
2517
+ rl.question(question, (answer) => {
2518
+ rl.close();
2519
+ const normalized = answer.trim().toLowerCase();
2520
+ resolve(normalized === "y" || normalized === "yes");
2521
+ });
2522
+ });
2523
+ }
2524
+ function promptSecret(question) {
2525
+ return new Promise((resolve) => {
2526
+ const rl = readline.createInterface({
2527
+ input: process.stdin,
2528
+ output: process.stdout,
2529
+ terminal: true
2530
+ });
2531
+ let muted = false;
2532
+ const rlAny = rl;
2533
+ rlAny._writeToOutput = (chunk) => {
2534
+ if (!muted) {
2535
+ rlAny.output.write(chunk);
2536
+ } else if (chunk.includes("\n") || chunk.includes("\r")) {
2537
+ rlAny.output.write("\n");
2538
+ }
2539
+ };
2540
+ rl.question(question, (answer) => {
2541
+ rl.close();
2542
+ resolve(answer.trim());
2543
+ });
2544
+ muted = true;
2545
+ });
2546
+ }
2547
+ function withTimeout(promise, ms) {
2548
+ return new Promise((resolve, reject) => {
2549
+ const timer = setTimeout(() => {
2550
+ reject(new Error(`timed out after ${ms}ms`));
2551
+ }, ms);
2552
+ promise.then(
2553
+ (value) => {
2554
+ clearTimeout(timer);
2555
+ resolve(value);
2556
+ },
2557
+ (err) => {
2558
+ clearTimeout(timer);
2559
+ reject(err);
2560
+ }
2561
+ );
2562
+ });
2563
+ }
2564
+ function rel(rootDir, filePath) {
2565
+ return path10.relative(rootDir, filePath) || path10.basename(filePath);
2566
+ }
2567
+ function firstLine(text) {
2568
+ return String(text).split("\n")[0];
2569
+ }
2570
+ function action(fn) {
2571
+ return async (...args) => {
2572
+ try {
2573
+ await fn(...args);
2574
+ } catch (err) {
2575
+ console.error(`${red("error:")} ${err?.message || err}`);
2576
+ process.exitCode = 1;
2577
+ }
2578
+ };
2579
+ }
2580
+
1394
2581
  // src/cli/startup/startup-tasks.ts
1395
2582
  import os from "node:os";
1396
- import path7 from "node:path";
2583
+ import path11 from "node:path";
1397
2584
 
1398
2585
  // src/cli/startup/check-version.ts
1399
- import { dim as dim3, green, yellow } from "kleur/colors";
2586
+ import { dim as dim4, green as green2, yellow as yellow2 } from "kleur/colors";
1400
2587
  var PACKAGE_NAME = "@blinkk/root";
1401
2588
  var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
1402
2589
  var REGISTRY_TIMEOUT_MS = 3e3;
@@ -1467,16 +2654,16 @@ function parseVersion(version) {
1467
2654
  return [toInt(parts[0]), toInt(parts[1]), toInt(parts[2])];
1468
2655
  }
1469
2656
  function printUpdateNotice(current, latest) {
1470
- const bar = dim3("\u2503");
1471
- const label = yellow(`Update available for ${PACKAGE_NAME}:`);
2657
+ const bar = dim4("\u2503");
2658
+ const label = yellow2(`Update available for ${PACKAGE_NAME}:`);
1472
2659
  console.log();
1473
- console.log(`${bar} ${label} ${dim3(current)} \u2192 ${green(latest)}`);
2660
+ console.log(`${bar} ${label} ${dim4(current)} \u2192 ${green2(latest)}`);
1474
2661
  console.log();
1475
2662
  }
1476
2663
 
1477
2664
  // src/cli/startup/startup-tasks.ts
1478
2665
  var STARTUP_TASKS = [checkVersionTask];
1479
- var STATE_FILE = path7.join(os.homedir(), ".root", "startup-tasks.json");
2666
+ var STATE_FILE = path11.join(os.homedir(), ".root", "startup-tasks.json");
1480
2667
  async function runStartupTasks(ctx) {
1481
2668
  try {
1482
2669
  const state = await readState();
@@ -1527,16 +2714,17 @@ async function writeState(state) {
1527
2714
  }
1528
2715
 
1529
2716
  // src/cli/dev.ts
1530
- var __dirname3 = path8.dirname(fileURLToPath3(import.meta.url));
2717
+ var __dirname3 = path12.dirname(fileURLToPath3(import.meta.url));
1531
2718
  var DEV_SERVER_404_LOG_IGNORE_PATHS = /* @__PURE__ */ new Set([
1532
2719
  "/.well-known/appspecific/com.chrome.devtools.json"
1533
2720
  ]);
1534
2721
  async function dev(rootProjectDir, options) {
1535
2722
  process.env.NODE_ENV = "development";
1536
- const rootDir = path8.resolve(rootProjectDir || process.cwd());
2723
+ const rootDir = path12.resolve(rootProjectDir || process.cwd());
1537
2724
  const defaultPort = parseInt(process.env.PORT || "4007");
1538
2725
  const host = options?.host || "localhost";
1539
2726
  const port = await findOpenPort(defaultPort, defaultPort + 10);
2727
+ await syncSecretsOnDev(rootDir);
1540
2728
  void runStartupTasks({ rootDir, version: options?.version });
1541
2729
  let currentServer = null;
1542
2730
  let currentViteServer = null;
@@ -1548,19 +2736,19 @@ async function dev(rootProjectDir, options) {
1548
2736
  const basePath = rootConfig.base || "";
1549
2737
  const homePagePath = rootConfig.server?.homePagePath || basePath;
1550
2738
  console.log();
1551
- console.log(`${dim4("\u2503")} project: ${rootDir}`);
1552
- console.log(`${dim4("\u2503")} server: http://${host}:${port}${homePagePath}`);
2739
+ console.log(`${dim5("\u2503")} project: ${rootDir}`);
2740
+ console.log(`${dim5("\u2503")} server: http://${host}:${port}${homePagePath}`);
1553
2741
  if (testCmsEnabled(rootConfig)) {
1554
- console.log(`${dim4("\u2503")} cms: http://${host}:${port}/cms/`);
2742
+ console.log(`${dim5("\u2503")} cms: http://${host}:${port}/cms/`);
1555
2743
  }
1556
- console.log(`${dim4("\u2503")} mode: development`);
2744
+ console.log(`${dim5("\u2503")} mode: development`);
1557
2745
  console.log();
1558
2746
  currentServer = server.listen(port, host);
1559
2747
  const rootConfigDependencies = server.get(
1560
2748
  "rootConfigDependencies"
1561
2749
  );
1562
2750
  const dependencies = [
1563
- path8.resolve(rootDir, "root.config.ts"),
2751
+ path12.resolve(rootDir, "root.config.ts"),
1564
2752
  ...rootConfigDependencies
1565
2753
  ];
1566
2754
  viteServer.watcher.add(dependencies);
@@ -1568,7 +2756,7 @@ async function dev(rootProjectDir, options) {
1568
2756
  if (dependencies.includes(file)) {
1569
2757
  console.log(
1570
2758
  `
1571
- ${dim4("\u2503")} root.config.ts changed. restarting server...`
2759
+ ${dim5("\u2503")} root.config.ts changed. restarting server...`
1572
2760
  );
1573
2761
  await restart();
1574
2762
  }
@@ -1588,7 +2776,7 @@ ${dim4("\u2503")} root.config.ts changed. restarting server...`
1588
2776
  await start2();
1589
2777
  }
1590
2778
  async function createDevServer(options) {
1591
- const rootDir = path8.resolve(options?.rootDir || process.cwd());
2779
+ const rootDir = path12.resolve(options?.rootDir || process.cwd());
1592
2780
  const { rootConfig, dependencies } = await loadRootConfigWithDeps(rootDir, {
1593
2781
  command: "dev"
1594
2782
  });
@@ -1624,7 +2812,7 @@ async function createDevServer(options) {
1624
2812
  if (rootConfig.server?.headers) {
1625
2813
  server.use(headersMiddleware({ rootConfig }));
1626
2814
  }
1627
- const publicDir = path8.join(rootDir, "public");
2815
+ const publicDir = path12.join(rootDir, "public");
1628
2816
  if (await dirExists(publicDir)) {
1629
2817
  server.use(rootPublicDirMiddleware({ publicDir, viteServer }));
1630
2818
  }
@@ -1652,10 +2840,10 @@ async function createViteMiddleware(options) {
1652
2840
  return sourceFile.relPath;
1653
2841
  });
1654
2842
  const bundleScripts = [];
1655
- if (await isDirectory(path8.join(rootDir, "bundles"))) {
2843
+ if (await isDirectory(path12.join(rootDir, "bundles"))) {
1656
2844
  const bundleFiles = await glob4("bundles/*", { cwd: rootDir });
1657
2845
  bundleFiles.forEach((file) => {
1658
- const parts = path8.parse(file);
2846
+ const parts = path12.parse(file);
1659
2847
  if (isJsFile(parts.base)) {
1660
2848
  bundleScripts.push(file);
1661
2849
  }
@@ -1672,7 +2860,7 @@ async function createViteMiddleware(options) {
1672
2860
  optimizeDeps
1673
2861
  });
1674
2862
  function isInElementsDir(changedFilePath) {
1675
- const filePath = path8.resolve(changedFilePath);
2863
+ const filePath = path12.resolve(changedFilePath);
1676
2864
  const elementsDirs = getElementsDirs(rootConfig, pods);
1677
2865
  return elementsDirs.some((dirPath) => filePath.startsWith(dirPath));
1678
2866
  }
@@ -1691,7 +2879,7 @@ async function createViteMiddleware(options) {
1691
2879
  const viteMiddleware = async (req, res, next) => {
1692
2880
  try {
1693
2881
  req.viteServer = viteServer;
1694
- const renderModulePath = path8.resolve(__dirname3, "./render.js");
2882
+ const renderModulePath = path12.resolve(__dirname3, "./render.js");
1695
2883
  const render = await viteServer.ssrLoadModule(
1696
2884
  renderModulePath
1697
2885
  );
@@ -1715,7 +2903,7 @@ function rootPublicDirMiddleware(options) {
1715
2903
  handler = sirv(publicDir, sirvOptions);
1716
2904
  }, 1e3);
1717
2905
  function isInPublicDir(changedFilePath) {
1718
- const filePath = path8.resolve(changedFilePath);
2906
+ const filePath = path12.resolve(changedFilePath);
1719
2907
  return filePath.startsWith(publicDir);
1720
2908
  }
1721
2909
  const watcher = options.viteServer.watcher;
@@ -1747,7 +2935,7 @@ function rootDevServer404Middleware() {
1747
2935
  }
1748
2936
  if (req.renderer) {
1749
2937
  const url = req.path;
1750
- const ext = path8.extname(url);
2938
+ const ext = path12.extname(url);
1751
2939
  if (!ext) {
1752
2940
  const renderer = req.renderer;
1753
2941
  const data = await renderer.renderDevServer404(req);
@@ -1765,7 +2953,7 @@ function rootDevServer500Middleware() {
1765
2953
  console.error(String(err.stack || err));
1766
2954
  if (req.renderer) {
1767
2955
  const url = req.path;
1768
- const ext = path8.extname(url);
2956
+ const ext = path12.extname(url);
1769
2957
  if (!ext) {
1770
2958
  const renderer = req.renderer;
1771
2959
  const data = await renderer.renderDevServer500(req, err);
@@ -1791,7 +2979,7 @@ function debounce(fn, timeout) {
1791
2979
 
1792
2980
  // src/cli/gae-deploy.ts
1793
2981
  import { execSync } from "node:child_process";
1794
- import fs5 from "node:fs";
2982
+ import fs9 from "node:fs";
1795
2983
  import yaml from "js-yaml";
1796
2984
  async function gaeDeploy(appDir, options) {
1797
2985
  if (!appDir) {
@@ -1805,10 +2993,10 @@ async function gaeDeploy(appDir, options) {
1805
2993
  }
1806
2994
  process.chdir(appDir);
1807
2995
  const appYamlPath = "app.yaml";
1808
- if (!fs5.existsSync(appYamlPath)) {
2996
+ if (!fs9.existsSync(appYamlPath)) {
1809
2997
  throw new Error(`[gae-deplopy] Missing: ${appYamlPath}`);
1810
2998
  }
1811
- const appYaml = yaml.load(fs5.readFileSync(appYamlPath, "utf8"));
2999
+ const appYaml = yaml.load(fs9.readFileSync(appYamlPath, "utf8"));
1812
3000
  const service = appYaml.service;
1813
3001
  if (!service) {
1814
3002
  throw new Error(
@@ -1834,7 +3022,7 @@ async function gaeDeploy(appDir, options) {
1834
3022
  { stdio: "inherit" }
1835
3023
  );
1836
3024
  if (backupAppYaml) {
1837
- fs5.copyFileSync(backupAppYaml, "app.yaml");
3025
+ fs9.copyFileSync(backupAppYaml, "app.yaml");
1838
3026
  }
1839
3027
  if (options?.healthcheckUrl) {
1840
3028
  const healthcheckPassed = await testHealth(
@@ -1921,8 +3109,8 @@ function testIsEnvPlaceholder(envValue) {
1921
3109
  }
1922
3110
  function updateAppYamlEnv(appYamlPath, appYaml) {
1923
3111
  const backupAppYaml = `${appYamlPath}.bak`;
1924
- fs5.copyFileSync(appYamlPath, backupAppYaml);
1925
- let content = fs5.readFileSync(appYamlPath, "utf8");
3112
+ fs9.copyFileSync(appYamlPath, backupAppYaml);
3113
+ let content = fs9.readFileSync(appYamlPath, "utf8");
1926
3114
  const envVars = appYaml.env_variables;
1927
3115
  Object.entries(envVars).forEach(([envVar, envValue]) => {
1928
3116
  if (testIsEnvPlaceholder(envValue)) {
@@ -1935,7 +3123,7 @@ function updateAppYamlEnv(appYamlPath, appYaml) {
1935
3123
  }
1936
3124
  }
1937
3125
  });
1938
- fs5.writeFileSync(appYamlPath, content, "utf8");
3126
+ fs9.writeFileSync(appYamlPath, content, "utf8");
1939
3127
  return backupAppYaml;
1940
3128
  }
1941
3129
  function getTimestamp() {
@@ -1959,29 +3147,29 @@ function testManagedVersion(service, appInfo, prefix) {
1959
3147
  }
1960
3148
 
1961
3149
  // src/cli/preview.ts
1962
- import path9 from "node:path";
3150
+ import path13 from "node:path";
1963
3151
  import compression from "compression";
1964
3152
  import cookieParser2 from "cookie-parser";
1965
3153
  import { default as express2 } from "express";
1966
- import { dim as dim5 } from "kleur/colors";
3154
+ import { dim as dim6 } from "kleur/colors";
1967
3155
  import sirv2 from "sirv";
1968
3156
  async function preview(rootProjectDir, options) {
1969
3157
  process.env.NODE_ENV = "development";
1970
- const rootDir = path9.resolve(rootProjectDir || process.cwd());
3158
+ const rootDir = path13.resolve(rootProjectDir || process.cwd());
1971
3159
  const server = await createPreviewServer({ rootDir });
1972
3160
  const port = parseInt(process.env.PORT || "4007");
1973
3161
  const host = options?.host || "localhost";
1974
3162
  console.log();
1975
- console.log(`${dim5("\u2503")} project: ${rootDir}`);
1976
- console.log(`${dim5("\u2503")} server: http://${host}:${port}`);
1977
- console.log(`${dim5("\u2503")} mode: preview`);
3163
+ console.log(`${dim6("\u2503")} project: ${rootDir}`);
3164
+ console.log(`${dim6("\u2503")} server: http://${host}:${port}`);
3165
+ console.log(`${dim6("\u2503")} mode: preview`);
1978
3166
  console.log();
1979
3167
  server.listen(port, host);
1980
3168
  }
1981
3169
  async function createPreviewServer(options) {
1982
3170
  const rootDir = options.rootDir;
1983
3171
  const rootConfig = await loadBundledConfig(rootDir, { command: "preview" });
1984
- const distDir = path9.join(rootDir, "dist");
3172
+ const distDir = path13.join(rootDir, "dist");
1985
3173
  const server = express2();
1986
3174
  server.disable("x-powered-by");
1987
3175
  server.use(compression());
@@ -2007,7 +3195,7 @@ async function createPreviewServer(options) {
2007
3195
  if (rootConfig.server?.headers) {
2008
3196
  server.use(headersMiddleware({ rootConfig }));
2009
3197
  }
2010
- const publicDir = path9.join(distDir, "html");
3198
+ const publicDir = path13.join(distDir, "html");
2011
3199
  server.use(sirv2(publicDir, { dev: false }));
2012
3200
  server.use(trailingSlashMiddleware({ rootConfig }));
2013
3201
  server.use(rootPreviewServerMiddleware());
@@ -2026,20 +3214,20 @@ async function createPreviewServer(options) {
2026
3214
  }
2027
3215
  async function rootPreviewRendererMiddleware(options) {
2028
3216
  const { distDir, rootConfig } = options;
2029
- const render = await import(path9.join(distDir, "server/render.js"));
2030
- const manifestPath = path9.join(distDir, ".root/manifest.json");
2031
- if (!await fileExists(manifestPath)) {
3217
+ const render = await import(path13.join(distDir, "server/render.js"));
3218
+ const manifestPath2 = path13.join(distDir, ".root/manifest.json");
3219
+ if (!await fileExists(manifestPath2)) {
2032
3220
  throw new Error(
2033
- `could not find ${manifestPath}. run \`root build\` before \`root preview\`.`
3221
+ `could not find ${manifestPath2}. run \`root build\` before \`root preview\`.`
2034
3222
  );
2035
3223
  }
2036
- const elementGraphJsonPath = path9.join(distDir, ".root/elements.json");
3224
+ const elementGraphJsonPath = path13.join(distDir, ".root/elements.json");
2037
3225
  if (!await fileExists(elementGraphJsonPath)) {
2038
3226
  throw new Error(
2039
3227
  `could not find ${elementGraphJsonPath}. run \`root build\` before \`root preview\`.`
2040
3228
  );
2041
3229
  }
2042
- const rootManifest = await loadJson(manifestPath);
3230
+ const rootManifest = await loadJson(manifestPath2);
2043
3231
  const assetMap = BuildAssetMap.fromRootManifest(rootConfig, rootManifest);
2044
3232
  const elementGraphJson = await loadJson(elementGraphJsonPath);
2045
3233
  const elementGraph = ElementGraph.fromJson(elementGraphJson);
@@ -2073,7 +3261,7 @@ function rootPreviewServer404Middleware() {
2073
3261
  console.error(`\u2753 404 ${req.originalUrl}`);
2074
3262
  if (req.renderer) {
2075
3263
  const url = req.path;
2076
- const ext = path9.extname(url);
3264
+ const ext = path13.extname(url);
2077
3265
  if (!ext) {
2078
3266
  const renderer = req.renderer;
2079
3267
  const data = await renderer.render404({ currentPath: url });
@@ -2091,7 +3279,7 @@ function rootPreviewServer500Middleware() {
2091
3279
  console.error(String(err.stack || err));
2092
3280
  if (req.renderer) {
2093
3281
  const url = req.path;
2094
- const ext = path9.extname(url);
3282
+ const ext = path13.extname(url);
2095
3283
  if (!ext) {
2096
3284
  const renderer = req.renderer;
2097
3285
  const data = await renderer.renderDevServer500(req, err);
@@ -2105,29 +3293,29 @@ function rootPreviewServer500Middleware() {
2105
3293
  }
2106
3294
 
2107
3295
  // src/cli/start.ts
2108
- import path10 from "node:path";
3296
+ import path14 from "node:path";
2109
3297
  import compression2 from "compression";
2110
3298
  import cookieParser3 from "cookie-parser";
2111
3299
  import { default as express3 } from "express";
2112
- import { dim as dim6 } from "kleur/colors";
3300
+ import { dim as dim7 } from "kleur/colors";
2113
3301
  import sirv3 from "sirv";
2114
3302
  async function start(rootProjectDir, options) {
2115
3303
  process.env.NODE_ENV = "production";
2116
- const rootDir = path10.resolve(rootProjectDir || process.cwd());
3304
+ const rootDir = path14.resolve(rootProjectDir || process.cwd());
2117
3305
  const server = await createProdServer({ rootDir });
2118
3306
  const port = parseInt(process.env.PORT || "4007");
2119
3307
  const host = options?.host || "localhost";
2120
3308
  console.log();
2121
- console.log(`${dim6("\u2503")} project: ${rootDir}`);
2122
- console.log(`${dim6("\u2503")} server: http://${host}:${port}`);
2123
- console.log(`${dim6("\u2503")} mode: production`);
3309
+ console.log(`${dim7("\u2503")} project: ${rootDir}`);
3310
+ console.log(`${dim7("\u2503")} server: http://${host}:${port}`);
3311
+ console.log(`${dim7("\u2503")} mode: production`);
2124
3312
  console.log();
2125
3313
  server.listen(port, host);
2126
3314
  }
2127
3315
  async function createProdServer(options) {
2128
3316
  const rootDir = options.rootDir;
2129
3317
  const rootConfig = await loadBundledConfig(rootDir, { command: "start" });
2130
- const distDir = path10.join(rootDir, "dist");
3318
+ const distDir = path14.join(rootDir, "dist");
2131
3319
  const server = express3();
2132
3320
  server.disable("x-powered-by");
2133
3321
  server.use(compression2());
@@ -2153,7 +3341,7 @@ async function createProdServer(options) {
2153
3341
  if (rootConfig.server?.headers) {
2154
3342
  server.use(headersMiddleware({ rootConfig }));
2155
3343
  }
2156
- const publicDir = path10.join(distDir, "html");
3344
+ const publicDir = path14.join(distDir, "html");
2157
3345
  server.use(sirv3(publicDir, { dev: false }));
2158
3346
  server.use(trailingSlashMiddleware({ rootConfig }));
2159
3347
  server.use(rootProdServerMiddleware());
@@ -2172,20 +3360,20 @@ async function createProdServer(options) {
2172
3360
  }
2173
3361
  async function rootProdRendererMiddleware(options) {
2174
3362
  const { distDir, rootConfig } = options;
2175
- const render = await import(path10.join(distDir, "server/render.js"));
2176
- const manifestPath = path10.join(distDir, ".root/manifest.json");
2177
- if (!await fileExists(manifestPath)) {
3363
+ const render = await import(path14.join(distDir, "server/render.js"));
3364
+ const manifestPath2 = path14.join(distDir, ".root/manifest.json");
3365
+ if (!await fileExists(manifestPath2)) {
2178
3366
  throw new Error(
2179
- `could not find ${manifestPath}. run \`root build\` before \`root start\`.`
3367
+ `could not find ${manifestPath2}. run \`root build\` before \`root start\`.`
2180
3368
  );
2181
3369
  }
2182
- const elementGraphJsonPath = path10.join(distDir, ".root/elements.json");
3370
+ const elementGraphJsonPath = path14.join(distDir, ".root/elements.json");
2183
3371
  if (!await fileExists(elementGraphJsonPath)) {
2184
3372
  throw new Error(
2185
3373
  `could not find ${elementGraphJsonPath}. run \`root build\` before \`root start\`.`
2186
3374
  );
2187
3375
  }
2188
- const rootManifest = await loadJson(manifestPath);
3376
+ const rootManifest = await loadJson(manifestPath2);
2189
3377
  const assetMap = BuildAssetMap.fromRootManifest(rootConfig, rootManifest);
2190
3378
  const elementGraphJson = await loadJson(elementGraphJsonPath);
2191
3379
  const elementGraph = ElementGraph.fromJson(elementGraphJson);
@@ -2219,7 +3407,7 @@ function rootProdServer404Middleware() {
2219
3407
  console.error(`\u2753 404 ${req.originalUrl}`);
2220
3408
  if (req.renderer) {
2221
3409
  const url = req.path;
2222
- const ext = path10.extname(url);
3410
+ const ext = path14.extname(url);
2223
3411
  if (!ext) {
2224
3412
  const renderer = req.renderer;
2225
3413
  const data = await renderer.render404({ currentPath: url });
@@ -2237,7 +3425,7 @@ function rootProdServer500Middleware() {
2237
3425
  console.error(String(err.stack || err));
2238
3426
  if (req.renderer) {
2239
3427
  const url = req.path;
2240
- const ext = path10.extname(url);
3428
+ const ext = path14.extname(url);
2241
3429
  if (!ext) {
2242
3430
  const renderer = req.renderer;
2243
3431
  const data = await renderer.renderError(err);
@@ -2321,6 +3509,7 @@ var CliRunner = class {
2321
3509
  "--host <host>",
2322
3510
  "network address the server should listen on, e.g. 127.0.0.1"
2323
3511
  ).action(start);
3512
+ registerSecretsCommands(program);
2324
3513
  await program.parseAsync(argv);
2325
3514
  }
2326
3515
  };
@@ -2343,4 +3532,4 @@ export {
2343
3532
  createProdServer,
2344
3533
  CliRunner
2345
3534
  };
2346
- //# sourceMappingURL=chunk-JTZWI4MH.js.map
3535
+ //# sourceMappingURL=chunk-VQPDCDKL.js.map