@blinkk/root 3.0.3 → 3.0.4

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