vela 0.10.1 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/bin.ts
4
- import process24 from "node:process";
4
+ import process31 from "node:process";
5
5
  import { fileURLToPath as fileURLToPath2 } from "node:url";
6
6
 
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "vela",
10
- version: "0.10.1",
10
+ version: "0.10.3",
11
11
  type: "module",
12
12
  description: "A CLI for creating and updating SvelteKit projects",
13
13
  license: "MIT",
@@ -93,6 +93,13 @@ var package_default = {
93
93
  ]
94
94
  };
95
95
 
96
+ // src/lib/argv.ts
97
+ function normalizeArgv(argv2) {
98
+ const separator = argv2.indexOf("--");
99
+ if (separator === -1) return argv2;
100
+ return [...argv2.slice(0, separator), ...argv2.slice(separator + 1)];
101
+ }
102
+
96
103
  // src/lib/delegate.ts
97
104
  import fs from "node:fs";
98
105
  import path from "node:path";
@@ -140,20 +147,20 @@ function readLocalCli(pkgPath) {
140
147
  function isRecord(value) {
141
148
  return typeof value === "object" && value !== null;
142
149
  }
143
- function isDelegatable(argv) {
144
- const first = argv.find((arg) => !arg.startsWith("-"));
150
+ function isDelegatable(argv2) {
151
+ const first = argv2.find((arg) => !arg.startsWith("-"));
145
152
  return first === void 0 || !NO_DELEGATE_COMMANDS.has(first);
146
153
  }
147
154
  function delegateToLocalCli(opts) {
148
- const env = opts.env ?? process2.env;
149
- if (env[NO_DELEGATE_ENV]) return null;
150
- const argv = opts.argv ?? process2.argv.slice(2);
151
- if (!isDelegatable(argv)) return null;
155
+ const env2 = opts.env ?? process2.env;
156
+ if (env2[NO_DELEGATE_ENV]) return null;
157
+ const argv2 = opts.argv ?? process2.argv.slice(2);
158
+ if (!isDelegatable(argv2)) return null;
152
159
  const local = findLocalCli(opts.cwd ?? process2.cwd(), opts.selfPath);
153
160
  if (!local || local.version === opts.selfVersion) return null;
154
- const result = spawnSync(process2.execPath, [local.binPath, ...argv], {
161
+ const result = spawnSync(process2.execPath, [local.binPath, ...argv2], {
155
162
  stdio: "inherit",
156
- env: { ...env, [NO_DELEGATE_ENV]: "1" }
163
+ env: { ...env2, [NO_DELEGATE_ENV]: "1" }
157
164
  });
158
165
  if (result.error) return null;
159
166
  if (result.signal) return 1;
@@ -161,11 +168,12 @@ function delegateToLocalCli(opts) {
161
168
  }
162
169
 
163
170
  // src/program.ts
164
- import process23 from "node:process";
165
- import * as p29 from "@clack/prompts";
166
- import { Command as Command73 } from "commander";
167
- import dotenv from "dotenv";
168
- import pc7 from "picocolors";
171
+ import process30 from "node:process";
172
+ import * as p42 from "@clack/prompts";
173
+ import { Command as Command87 } from "commander";
174
+ import nodePath from "node:path";
175
+ import dotenv2 from "dotenv";
176
+ import pc20 from "picocolors";
169
177
 
170
178
  // src/lib/help.ts
171
179
  import pc from "picocolors";
@@ -342,8 +350,8 @@ function mergePackageJson(user, template) {
342
350
  }
343
351
  return { merged, added, conflicts, replaced };
344
352
  }
345
- function readPackageJson(path36) {
346
- return JSON.parse(fs2.readFileSync(path36, "utf8"));
353
+ function readPackageJson(path43) {
354
+ return JSON.parse(fs2.readFileSync(path43, "utf8"));
347
355
  }
348
356
  var PACKAGE_NAME_PLACEHOLDER = /~TODO~/g;
349
357
  var APP_NAME_PLACEHOLDER = /~APP_NAME~/g;
@@ -356,12 +364,12 @@ function fillTemplatePlaceholders(raw, values) {
356
364
  function escapeSingleQuoted(value) {
357
365
  return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
358
366
  }
359
- function readTemplatePackageJson(path36, values) {
360
- const raw = fillTemplatePlaceholders(fs2.readFileSync(path36, "utf8"), values);
367
+ function readTemplatePackageJson(path43, values) {
368
+ const raw = fillTemplatePlaceholders(fs2.readFileSync(path43, "utf8"), values);
361
369
  return JSON.parse(raw);
362
370
  }
363
- function writePackageJson(path36, pkg) {
364
- fs2.writeFileSync(path36, JSON.stringify(pkg, null, " ") + "\n");
371
+ function writePackageJson(path43, pkg) {
372
+ fs2.writeFileSync(path43, JSON.stringify(pkg, null, " ") + "\n");
365
373
  }
366
374
  function toValidPackageName(name) {
367
375
  return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z0-9~.-]+/g, "-");
@@ -623,13 +631,14 @@ import fs6 from "node:fs";
623
631
  import net from "node:net";
624
632
  import path5 from "node:path";
625
633
  import process5 from "node:process";
634
+ import { createRequire } from "node:module";
626
635
  import { spawn } from "node:child_process";
627
636
  import PocketBase from "pocketbase";
628
637
  import { detect as detect2 } from "package-manager-detector";
629
638
  import { resolveCommand } from "package-manager-detector/commands";
630
639
  import { x } from "tinyexec";
631
640
  function findFreePort(host = "localhost") {
632
- return new Promise((resolve, reject) => {
641
+ return new Promise((resolve2, reject) => {
633
642
  const server = net.createServer();
634
643
  server.unref();
635
644
  server.once("error", reject);
@@ -638,13 +647,13 @@ function findFreePort(host = "localhost") {
638
647
  server.close((err) => {
639
648
  if (err) reject(err);
640
649
  else if (!addr) reject(new Error("Failed to obtain a free port"));
641
- else resolve(addr.port);
650
+ else resolve2(addr.port);
642
651
  });
643
652
  });
644
653
  });
645
654
  }
646
655
  function waitForReadyOrExit(proc, url, maxAttempts = 40) {
647
- return new Promise((resolve, reject) => {
656
+ return new Promise((resolve2, reject) => {
648
657
  let settled = false;
649
658
  const onExit = (code, signal) => {
650
659
  if (settled) return;
@@ -664,7 +673,7 @@ function waitForReadyOrExit(proc, url, maxAttempts = 40) {
664
673
  if (settled) return;
665
674
  settled = true;
666
675
  proc.removeListener("exit", onExit);
667
- resolve();
676
+ resolve2();
668
677
  return;
669
678
  }
670
679
  } catch {
@@ -708,8 +717,8 @@ async function startPocketbaseServe(opts) {
708
717
  } catch (err) {
709
718
  lastErr = err;
710
719
  if (proc.exitCode === null && proc.signalCode === null) {
711
- await new Promise((resolve) => {
712
- proc.once("exit", () => resolve());
720
+ await new Promise((resolve2) => {
721
+ proc.once("exit", () => resolve2());
713
722
  proc.kill();
714
723
  });
715
724
  }
@@ -717,10 +726,10 @@ async function startPocketbaseServe(opts) {
717
726
  }
718
727
  throw lastErr instanceof Error ? lastErr : new Error("Failed to start PocketBase");
719
728
  }
720
- async function authWithRetries(pb, email3, password8, attempts = 3) {
729
+ async function authWithRetries(pb, email3, password10, attempts = 3) {
721
730
  for (let attempt = 1; attempt <= attempts; attempt++) {
722
731
  try {
723
- await pb.collection("_superusers").authWithPassword(email3, password8);
732
+ await pb.collection("_superusers").authWithPassword(email3, password10);
724
733
  return;
725
734
  } catch (e) {
726
735
  if (attempt === attempts) {
@@ -752,14 +761,14 @@ async function withPocketbase(cwd, fn, creds) {
752
761
  const migrationsDir = path5.join(cwd, MIGRATIONS_DIR);
753
762
  const host = "localhost";
754
763
  const email3 = creds?.email ?? process5.env.POCKETBASE_SUPERUSER_EMAIL;
755
- const password8 = creds?.password ?? process5.env.POCKETBASE_SUPERUSER_PASSWORD;
764
+ const password10 = creds?.password ?? process5.env.POCKETBASE_SUPERUSER_PASSWORD;
756
765
  if (!fs6.existsSync(dir)) {
757
766
  throw new Error("PocketBase data directory does not exist");
758
767
  }
759
768
  const metadata = getPocketbaseMetadata(cwd);
760
769
  if (metadata?.pocketbaseUrl) {
761
770
  const pb = new PocketBase(metadata.pocketbaseUrl);
762
- await authWithRetries(pb, email3, password8);
771
+ await authWithRetries(pb, email3, password10);
763
772
  await fn(pb);
764
773
  return;
765
774
  }
@@ -771,13 +780,13 @@ async function withPocketbase(cwd, fn, creds) {
771
780
  });
772
781
  try {
773
782
  const pb = new PocketBase(url);
774
- await authWithRetries(pb, email3, password8);
783
+ await authWithRetries(pb, email3, password10);
775
784
  await fn(pb);
776
785
  } finally {
777
786
  proc.kill();
778
787
  }
779
788
  }
780
- async function createSuperuser(cwd, email3, password8) {
789
+ async function createSuperuser(cwd, email3, password10) {
781
790
  const dir = path5.join(cwd, DATA_DIR);
782
791
  const migrationsDir = path5.join(cwd, MIGRATIONS_DIR);
783
792
  fs6.mkdirSync(dir, { recursive: true });
@@ -793,7 +802,7 @@ async function createSuperuser(cwd, email3, password8) {
793
802
  "superuser",
794
803
  "create",
795
804
  email3,
796
- password8
805
+ password10
797
806
  ],
798
807
  "pipe"
799
808
  );
@@ -802,7 +811,7 @@ async function launchPocketbase(cwd, {
802
811
  dir,
803
812
  migrationsDir,
804
813
  email: email3,
805
- password: password8
814
+ password: password10
806
815
  }) {
807
816
  const host = "localhost";
808
817
  fs6.mkdirSync(dir, { recursive: true });
@@ -818,7 +827,7 @@ async function launchPocketbase(cwd, {
818
827
  "superuser",
819
828
  "create",
820
829
  email3,
821
- password8
830
+ password10
822
831
  ],
823
832
  "pipe"
824
833
  );
@@ -838,6 +847,33 @@ async function launchPocketbase(cwd, {
838
847
  url
839
848
  };
840
849
  }
850
+ function pocketbaseVersion() {
851
+ const require2 = createRequire(import.meta.url);
852
+ const pkg = require2("pocketbase-server/package.json");
853
+ return pkg.version;
854
+ }
855
+ async function ensureSuperuser(cwd) {
856
+ const email3 = process5.env.POCKETBASE_SUPERUSER_EMAIL;
857
+ const password10 = process5.env.POCKETBASE_SUPERUSER_PASSWORD;
858
+ if (!email3 || !password10) return;
859
+ const dir = path5.join(cwd, DATA_DIR);
860
+ if (!fs6.existsSync(dir)) return;
861
+ const { getBinaryPath } = await import("pocketbase-server");
862
+ await x(
863
+ getBinaryPath(),
864
+ [
865
+ "--dir",
866
+ dir,
867
+ "--migrationsDir",
868
+ path5.join(cwd, MIGRATIONS_DIR),
869
+ "superuser",
870
+ "upsert",
871
+ email3,
872
+ password10
873
+ ],
874
+ { nodeOptions: { cwd, stdio: "ignore" } }
875
+ );
876
+ }
841
877
 
842
878
  // src/lib/env.ts
843
879
  import fs7 from "node:fs";
@@ -862,6 +898,35 @@ function writeEnvFile(cwd, vars, comments = []) {
862
898
  for (const [key, value] of Object.entries(vars)) content = addEnvVar(content, key, value);
863
899
  fs7.writeFileSync(envPath, content);
864
900
  }
901
+ function upsertEnvVar(content, key, value) {
902
+ const line = `${key}=${quoteEnvValue(value)}`;
903
+ const pattern = keyPattern(key);
904
+ let replaced = false;
905
+ const lines = content.split("\n").map((existing) => {
906
+ if (replaced || !pattern.test(existing)) return existing;
907
+ replaced = true;
908
+ return existing.trimStart().startsWith("export ") ? `export ${line}` : line;
909
+ });
910
+ return replaced ? lines.join("\n") : appendLine(content, line);
911
+ }
912
+ function removeEnvVar(content, key) {
913
+ const pattern = keyPattern(key);
914
+ const lines = content.split("\n");
915
+ const kept = lines.filter((line) => !pattern.test(line));
916
+ if (kept.length === lines.length) return content;
917
+ return kept.join("\n");
918
+ }
919
+ function keyPattern(key) {
920
+ return new RegExp(`^\\s*(?:export\\s+)?${escapeRegExp(key)}\\s*=`);
921
+ }
922
+ function escapeRegExp(value) {
923
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
924
+ }
925
+ function quoteEnvValue(value) {
926
+ if (/^[A-Za-z0-9_./:@+-]*$/.test(value)) return value;
927
+ const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n");
928
+ return `"${escaped}"`;
929
+ }
865
930
 
866
931
  // src/lib/config-merge.ts
867
932
  import fs9 from "node:fs";
@@ -1121,18 +1186,18 @@ function isVanillaRoutes(cwd) {
1121
1186
 
1122
1187
  // src/lib/result-report.ts
1123
1188
  import * as p3 from "@clack/prompts";
1124
- function reportResult(report) {
1189
+ function reportResult(report4) {
1125
1190
  const sections = [
1126
- ["Files created", report.filesCreated],
1127
- ["Files modified", report.filesModified],
1128
- ["Files deleted", report.filesDeleted],
1129
- ["UI components added", report.componentsAdded],
1130
- ["Packages installed", report.packagesInstalled],
1131
- ["Collections added", report.collectionsAdded],
1132
- ["Records loaded", report.recordsLoaded],
1133
- ["Records cleared", report.recordsCleared]
1191
+ ["Files created", report4.filesCreated],
1192
+ ["Files modified", report4.filesModified],
1193
+ ["Files deleted", report4.filesDeleted],
1194
+ ["UI components added", report4.componentsAdded],
1195
+ ["Packages installed", report4.packagesInstalled],
1196
+ ["Collections added", report4.collectionsAdded],
1197
+ ["Records loaded", report4.recordsLoaded],
1198
+ ["Records cleared", report4.recordsCleared]
1134
1199
  ];
1135
- for (const extra of report.sections ?? []) {
1200
+ for (const extra of report4.sections ?? []) {
1136
1201
  sections.push([extra.label, extra.items]);
1137
1202
  }
1138
1203
  const body = [];
@@ -1143,14 +1208,14 @@ function reportResult(report) {
1143
1208
  for (const item of items) body.push(`- ${item}`);
1144
1209
  }
1145
1210
  if (body.length > 0) {
1146
- p3.log.success(`${report.summary}
1211
+ p3.log.success(`${report4.summary}
1147
1212
 
1148
1213
  ${body.join("\n")}`);
1149
1214
  } else {
1150
- p3.log.success(report.summary);
1215
+ p3.log.success(report4.summary);
1151
1216
  }
1152
- if (report.nextSteps && report.nextSteps.length > 0) {
1153
- p3.note(report.nextSteps.map((s) => `- ${s}`).join("\n"), "Next steps", {
1217
+ if (report4.nextSteps && report4.nextSteps.length > 0) {
1218
+ p3.note(report4.nextSteps.map((s) => `- ${s}`).join("\n"), "Next steps", {
1154
1219
  format: (line) => line
1155
1220
  });
1156
1221
  }
@@ -1240,7 +1305,7 @@ async function blessProject(cwdArg, options) {
1240
1305
  const projectPath = cwdArg ? resolveProjectPath(cwdArg) : (await getWorkspace()).workspaceRootDir;
1241
1306
  assertNotAlreadyBlessed(projectPath);
1242
1307
  const templateDir = findProjectTemplate(options.template ?? DEFAULT_TEMPLATE).dir;
1243
- const { email: email3, password: password8 } = await p4.group(
1308
+ const { email: email3, password: password10 } = await p4.group(
1244
1309
  {
1245
1310
  email: () => {
1246
1311
  if (options.email) return Promise.resolve(options.email);
@@ -1281,12 +1346,12 @@ async function blessProject(cwdArg, options) {
1281
1346
  }
1282
1347
  }
1283
1348
  p4.log.step("Initializing PocketBase...");
1284
- await createSuperuser(projectPath, email3, password8);
1349
+ await createSuperuser(projectPath, email3, password10);
1285
1350
  writeEnvFile(
1286
1351
  projectPath,
1287
1352
  {
1288
1353
  POCKETBASE_SUPERUSER_EMAIL: email3,
1289
- POCKETBASE_SUPERUSER_PASSWORD: password8
1354
+ POCKETBASE_SUPERUSER_PASSWORD: password10
1290
1355
  },
1291
1356
  ["PocketBase superuser credentials \u2014 used by `vela` commands"]
1292
1357
  );
@@ -1611,9 +1676,9 @@ async function createProject(cwdArg, options) {
1611
1676
  }
1612
1677
  }
1613
1678
  if (credentials) {
1614
- const { email: email3, password: password8 } = credentials;
1679
+ const { email: email3, password: password10 } = credentials;
1615
1680
  p5.log.step("Initializing PocketBase...");
1616
- await createSuperuser(projectPath, email3, password8);
1681
+ await createSuperuser(projectPath, email3, password10);
1617
1682
  await withPocketbase(
1618
1683
  projectPath,
1619
1684
  async (pb) => {
@@ -1621,13 +1686,13 @@ async function createProject(cwdArg, options) {
1621
1686
  meta: { appName: name, appURL: "http://localhost:5173" }
1622
1687
  });
1623
1688
  },
1624
- { email: email3, password: password8 }
1689
+ { email: email3, password: password10 }
1625
1690
  );
1626
1691
  writeEnvFile(
1627
1692
  projectPath,
1628
1693
  {
1629
1694
  POCKETBASE_SUPERUSER_EMAIL: email3,
1630
- POCKETBASE_SUPERUSER_PASSWORD: password8
1695
+ POCKETBASE_SUPERUSER_PASSWORD: password10
1631
1696
  },
1632
1697
  ["PocketBase superuser credentials \u2014 used by `vela` commands"]
1633
1698
  );
@@ -1681,17 +1746,17 @@ import { bySlug } from "@velastack/patterns";
1681
1746
  function toRelative(root, filePath) {
1682
1747
  return path13.isAbsolute(filePath) ? path13.relative(root, filePath) : filePath;
1683
1748
  }
1684
- async function runPattern(slug, argv, input, report) {
1685
- const pattern = bySlug[slug];
1749
+ async function runPattern(slug2, argv2, input, report4) {
1750
+ const pattern = bySlug[slug2];
1686
1751
  if (!pattern) {
1687
- throw new Error(`Unknown pattern: ${slug}`);
1752
+ throw new Error(`Unknown pattern: ${slug2}`);
1688
1753
  }
1689
1754
  const { workspaceRootDir, features } = await getWorkspace();
1690
- const log18 = p6.taskLog({ title: report.task.title });
1755
+ const log31 = p6.taskLog({ title: report4.task.title });
1691
1756
  let result;
1692
1757
  try {
1693
1758
  result = await pattern.generate({
1694
- argv,
1759
+ argv: argv2,
1695
1760
  env: "runtime",
1696
1761
  root: workspaceRootDir,
1697
1762
  features,
@@ -1707,28 +1772,28 @@ async function runPattern(slug, argv, input, report) {
1707
1772
  });
1708
1773
  return collections2;
1709
1774
  },
1710
- logger: { info: (message) => log18.message(message) }
1775
+ logger: { info: (message) => log31.message(message) }
1711
1776
  });
1712
- log18.success(report.task.success);
1777
+ log31.success(report4.task.success);
1713
1778
  } catch (e) {
1714
- log18.error(report.task.error);
1779
+ log31.error(report4.task.error);
1715
1780
  throw e;
1716
1781
  }
1717
1782
  const rel = (f) => toRelative(workspaceRootDir, f);
1718
1783
  const totalChanges = result.creates.length + result.modifies.length + result.deletes.length + result.components.length + result.packages.length + result.collections.length;
1719
1784
  if (totalChanges === 0) {
1720
- p6.log.info(`${pattern.title ?? slug} produced no changes.`);
1785
+ p6.log.info(`${pattern.title ?? slug2} produced no changes.`);
1721
1786
  return;
1722
1787
  }
1723
1788
  reportResult({
1724
- summary: report.summary ?? `Applied ${pattern.title ?? slug}.`,
1789
+ summary: report4.summary ?? `Applied ${pattern.title ?? slug2}.`,
1725
1790
  filesCreated: result.creates.map((f) => rel(f.path)),
1726
1791
  filesModified: result.modifies.map((f) => rel(f.path)),
1727
1792
  filesDeleted: result.deletes.map((f) => rel(f.path)),
1728
1793
  componentsAdded: result.components,
1729
1794
  packagesInstalled: result.packages,
1730
1795
  collectionsAdded: result.collections.map((c) => c.name),
1731
- nextSteps: report.nextSteps
1796
+ nextSteps: report4.nextSteps
1732
1797
  });
1733
1798
  }
1734
1799
 
@@ -1793,7 +1858,14 @@ function readProjectConfig(workspaceRootDir) {
1793
1858
  function writeProjectConfig(workspaceRootDir, config) {
1794
1859
  const file = projectConfigPath(workspaceRootDir);
1795
1860
  fs15.mkdirSync(path15.dirname(file), { recursive: true });
1796
- fs15.writeFileSync(file, JSON.stringify(config, null, 2) + "\n");
1861
+ let existing = {};
1862
+ if (fs15.existsSync(file)) {
1863
+ try {
1864
+ existing = JSON.parse(fs15.readFileSync(file, "utf8"));
1865
+ } catch {
1866
+ }
1867
+ }
1868
+ fs15.writeFileSync(file, JSON.stringify({ ...existing, ...config }, null, 2) + "\n");
1797
1869
  }
1798
1870
 
1799
1871
  // src/lib/ai-client.ts
@@ -1918,7 +1990,7 @@ function typeArg(field) {
1918
1990
  return null;
1919
1991
  }
1920
1992
  function collectionSpecToArgv(spec) {
1921
- const argv = [spec.name];
1993
+ const argv2 = [spec.name];
1922
1994
  for (const field of spec.fields) {
1923
1995
  const type = typeArg(field);
1924
1996
  if (type === null) {
@@ -1928,9 +2000,9 @@ function collectionSpecToArgv(spec) {
1928
2000
  continue;
1929
2001
  }
1930
2002
  const required = field.required ? "!" : "";
1931
- argv.push(`${field.name}:${type}${required}`);
2003
+ argv2.push(`${field.name}:${type}${required}`);
1932
2004
  }
1933
- return argv;
2005
+ return argv2;
1934
2006
  }
1935
2007
 
1936
2008
  // src/lib/ai-grid.ts
@@ -2083,7 +2155,7 @@ var form = new Command4("form").description("generate a form from a model").argu
2083
2155
  "design the form with AI from a natural-language description (two stages: schema \u2192 layout)"
2084
2156
  ).allowUnknownOption(true).configureHelp(helpConfig).action(
2085
2157
  (model, fields, options) => runCommand(async () => {
2086
- let argv;
2158
+ let argv2;
2087
2159
  let modelName;
2088
2160
  let sidecarPath = null;
2089
2161
  if (options.ai) {
@@ -2100,17 +2172,17 @@ var form = new Command4("form").description("generate a form from a model").argu
2100
2172
  p11.cancel("Aborted before any files were written.");
2101
2173
  return;
2102
2174
  }
2103
- argv = specToArgv(stage.model);
2175
+ argv2 = specToArgv(stage.model);
2104
2176
  modelName = stage.model.name;
2105
2177
  sidecarPath = writeLayoutSidecar(stage.workspaceRootDir, modelName, layout);
2106
2178
  } else {
2107
2179
  if (!model) {
2108
2180
  throw new Error("Missing required argument: model. Pass a model name or use --ai.");
2109
2181
  }
2110
- argv = [model, ...fields];
2182
+ argv2 = [model, ...fields];
2111
2183
  modelName = model;
2112
2184
  }
2113
- const slug = options.remote ? "generate-form-remote" : "generate-form";
2185
+ const slug2 = options.remote ? "generate-form-remote" : "generate-form";
2114
2186
  const nextSteps = [
2115
2187
  "Edit the form fields and validation in the generated +page.svelte.",
2116
2188
  "Run `vela dev` to preview the form in the browser."
@@ -2121,8 +2193,8 @@ var form = new Command4("form").description("generate a form from a model").argu
2121
2193
  );
2122
2194
  }
2123
2195
  await runPattern(
2124
- slug,
2125
- argv,
2196
+ slug2,
2197
+ argv2,
2126
2198
  { route: options.route },
2127
2199
  {
2128
2200
  summary: `Created ${modelName} form.`,
@@ -2142,7 +2214,7 @@ import { Command as Command5 } from "commander";
2142
2214
  import * as p12 from "@clack/prompts";
2143
2215
  var schema = new Command5("schema").description("generate a schema from a model").argument("[model]", "model name").argument("[fields...]", "field definitions").option("--ai <description>", "design the schema with AI from a natural-language description").allowUnknownOption(true).configureHelp(helpConfig).action(
2144
2216
  (model, fields, options) => runCommand(async () => {
2145
- let argv;
2217
+ let argv2;
2146
2218
  let modelName;
2147
2219
  if (options.ai) {
2148
2220
  if (model) {
@@ -2153,18 +2225,18 @@ var schema = new Command5("schema").description("generate a schema from a model"
2153
2225
  p12.cancel("Aborted before any files were written.");
2154
2226
  return;
2155
2227
  }
2156
- argv = specToArgv(stage.model);
2228
+ argv2 = specToArgv(stage.model);
2157
2229
  modelName = stage.model.name;
2158
2230
  } else {
2159
2231
  if (!model) {
2160
2232
  throw new Error("Missing required argument: model. Pass a model name or use --ai.");
2161
2233
  }
2162
- argv = [model, ...fields];
2234
+ argv2 = [model, ...fields];
2163
2235
  modelName = model;
2164
2236
  }
2165
2237
  await runPattern(
2166
2238
  "generate-schema",
2167
- argv,
2239
+ argv2,
2168
2240
  {},
2169
2241
  {
2170
2242
  summary: `Created ${modelName} schema.`,
@@ -2219,7 +2291,7 @@ var scaffold = new Command7("scaffold").description("generate a full CRUD scaffo
2219
2291
  "design the scaffold with AI from a natural-language description (two stages: schema \u2192 layout)"
2220
2292
  ).allowUnknownOption(true).configureHelp(helpConfig).action(
2221
2293
  (model, fields, options) => runCommand(async () => {
2222
- let argv;
2294
+ let argv2;
2223
2295
  let modelName;
2224
2296
  let sidecarPath = null;
2225
2297
  if (options.ai) {
@@ -2236,17 +2308,17 @@ var scaffold = new Command7("scaffold").description("generate a full CRUD scaffo
2236
2308
  p13.cancel("Aborted before any files were written.");
2237
2309
  return;
2238
2310
  }
2239
- argv = specToArgv(stage.model);
2311
+ argv2 = specToArgv(stage.model);
2240
2312
  modelName = stage.model.name;
2241
2313
  sidecarPath = writeLayoutSidecar(stage.workspaceRootDir, modelName, layout);
2242
2314
  } else {
2243
2315
  if (!model) {
2244
2316
  throw new Error("Missing required argument: model. Pass a model name or use --ai.");
2245
2317
  }
2246
- argv = [model, ...fields];
2318
+ argv2 = [model, ...fields];
2247
2319
  modelName = model;
2248
2320
  }
2249
- const slug = options.remote ? "generate-scaffold-remote" : "generate-scaffold";
2321
+ const slug2 = options.remote ? "generate-scaffold-remote" : "generate-scaffold";
2250
2322
  const nextSteps = [
2251
2323
  `Run \`vela fixtures generate\` to create 10 ${modelName} records for development.`,
2252
2324
  "Run `vela dev` and visit the generated route to use the scaffold.",
@@ -2258,8 +2330,8 @@ var scaffold = new Command7("scaffold").description("generate a full CRUD scaffo
2258
2330
  );
2259
2331
  }
2260
2332
  await runPattern(
2261
- slug,
2262
- argv,
2333
+ slug2,
2334
+ argv2,
2263
2335
  { route: options.route },
2264
2336
  {
2265
2337
  summary: `Created ${modelName} scaffold.`,
@@ -2553,7 +2625,7 @@ async function ensureKey(envVar, message) {
2553
2625
  async function promptPassword(message) {
2554
2626
  const value = await p14.password({
2555
2627
  message,
2556
- validate: (v4) => v4 && v4.length ? void 0 : "Required"
2628
+ validate: (v9) => v9 && v9.length ? void 0 : "Required"
2557
2629
  });
2558
2630
  if (p14.isCancel(value)) {
2559
2631
  p14.cancel("Operation cancelled.");
@@ -2600,24 +2672,24 @@ var s3 = new Command18("s3").description("enable S3 file storage").configureHelp
2600
2672
  endpoint: () => p15.text({
2601
2673
  message: "S3 endpoint URL",
2602
2674
  initialValue: "https://s3.amazonaws.com",
2603
- validate: (v4) => v4 ? void 0 : "Endpoint is required"
2675
+ validate: (v9) => v9 ? void 0 : "Endpoint is required"
2604
2676
  }),
2605
2677
  accessKey: () => p15.text({
2606
2678
  message: "S3 access key",
2607
- validate: (v4) => v4 ? void 0 : "Access key is required"
2679
+ validate: (v9) => v9 ? void 0 : "Access key is required"
2608
2680
  }),
2609
2681
  secret: () => p15.password({
2610
2682
  message: "S3 secret key",
2611
- validate: (v4) => v4 ? void 0 : "Secret key is required"
2683
+ validate: (v9) => v9 ? void 0 : "Secret key is required"
2612
2684
  }),
2613
2685
  bucket: () => p15.text({
2614
2686
  message: "S3 bucket name",
2615
- validate: (v4) => v4 ? void 0 : "Bucket name is required"
2687
+ validate: (v9) => v9 ? void 0 : "Bucket name is required"
2616
2688
  }),
2617
2689
  region: () => p15.text({
2618
2690
  message: "S3 region",
2619
2691
  initialValue: "us-east-1",
2620
- validate: (v4) => v4 ? void 0 : "Region is required"
2692
+ validate: (v9) => v9 ? void 0 : "Region is required"
2621
2693
  })
2622
2694
  },
2623
2695
  {
@@ -2671,14 +2743,14 @@ var smtp = new Command19("smtp").description("configure SMTP for transactional e
2671
2743
  host: () => p16.text({
2672
2744
  message: "SMTP host",
2673
2745
  placeholder: "smtp.example.com",
2674
- validate: (v4) => v4 ? void 0 : "Host is required"
2746
+ validate: (v9) => v9 ? void 0 : "Host is required"
2675
2747
  }),
2676
2748
  port: () => p16.text({
2677
2749
  message: "SMTP port",
2678
2750
  initialValue: "587",
2679
- validate: (v4) => {
2680
- if (!v4) return "Port is required";
2681
- const n = Number(v4);
2751
+ validate: (v9) => {
2752
+ if (!v9) return "Port is required";
2753
+ const n = Number(v9);
2682
2754
  if (!Number.isInteger(n) || n <= 0 || n > 65535) return "Invalid port";
2683
2755
  return void 0;
2684
2756
  }
@@ -3039,7 +3111,7 @@ var smtp2 = new Command32("smtp").description("disable SMTP configuration").conf
3039
3111
  var disable = new Command33("disable").description("disable features").configureHelp(helpConfig).addCommand(auth2).addCommand(api2).addCommand(apiKeys2).addCommand(backend2).addCommand(contentNegotiation2).addCommand(i18n2).addCommand(teams2).addCommand(payments2).addCommand(s32).addCommand(smtp2);
3040
3112
 
3041
3113
  // src/commands/destroy.ts
3042
- import { Command as Command38 } from "commander";
3114
+ import { Command as Command39 } from "commander";
3043
3115
 
3044
3116
  // src/commands/destroy/form.ts
3045
3117
  import { Command as Command34 } from "commander";
@@ -3047,7 +3119,7 @@ import { Command as Command34 } from "commander";
3047
3119
  // src/commands/destroy/_shared.ts
3048
3120
  import process12 from "node:process";
3049
3121
  import * as p18 from "@clack/prompts";
3050
- async function runDestroy(slug, model, confirmMessage, report, flags) {
3122
+ async function runDestroy(slug2, model, confirmMessage, report4, flags) {
3051
3123
  if (!flags.yes) {
3052
3124
  const ok = await p18.confirm({ message: confirmMessage, initialValue: false });
3053
3125
  if (p18.isCancel(ok) || !ok) {
@@ -3055,7 +3127,7 @@ async function runDestroy(slug, model, confirmMessage, report, flags) {
3055
3127
  process12.exit(0);
3056
3128
  }
3057
3129
  }
3058
- await runPattern(slug, [model], { destructive: true, route: flags.route }, report);
3130
+ await runPattern(slug2, [model], { destructive: true, route: flags.route }, report4);
3059
3131
  }
3060
3132
 
3061
3133
  // src/commands/destroy/form.ts
@@ -3154,197 +3226,1110 @@ var scaffold2 = new Command37("scaffold").description("destroy a scaffold genera
3154
3226
  )
3155
3227
  );
3156
3228
 
3157
- // src/commands/destroy.ts
3158
- var destroy = new Command38("destroy").description("destroy scaffolding").configureHelp(helpConfig).addCommand(form2).addCommand(schema2).addCommand(resource2).addCommand(scaffold2);
3159
-
3160
- // src/commands/ui.ts
3161
- import { Command as Command41 } from "commander";
3229
+ // src/commands/destroy/deployment.ts
3230
+ import process15 from "node:process";
3231
+ import { Command as Command38 } from "commander";
3232
+ import * as p20 from "@clack/prompts";
3233
+ import pc6 from "picocolors";
3162
3234
 
3163
- // src/commands/ui/add.ts
3164
- import { Command as Command39 } from "commander";
3165
- import { exec as exec2 } from "tinyexec";
3166
- import { detect as detect4, resolveCommand as resolveCommand4 } from "package-manager-detector";
3167
- var add = new Command39("add").description("add ui components").argument("<components...>", "the components to add").configureHelp(helpConfig).action(
3168
- (components) => runCommand(async () => {
3169
- const { workspaceRootDir } = await getWorkspace();
3170
- const packageManager = (await detect4({ cwd: workspaceRootDir }))?.name ?? "npm";
3171
- const resolved = resolveCommand4(packageManager, "execute", [
3172
- "shadcn-svelte",
3173
- "add",
3174
- ...components
3175
- ]);
3176
- if (!resolved) {
3177
- throw new Error(`Unable to resolve execute command for ${packageManager}`);
3178
- }
3179
- const args = [...resolved.args];
3180
- if (packageManager === "npm") args.unshift("--yes");
3181
- try {
3182
- await exec2(resolved.command, args, {
3183
- nodeOptions: { cwd: workspaceRootDir, stdio: "inherit" },
3184
- throwOnError: true
3185
- });
3186
- } catch (error) {
3187
- const typed = error;
3188
- throw new Error(
3189
- `Failed to execute '${resolved.command} ${args.join(" ")}': ${typed.message}`,
3190
- { cause: typed.output }
3191
- );
3192
- }
3193
- reportResult({
3194
- summary: `Added ${components.length} UI component(s).`,
3195
- componentsAdded: components,
3196
- nextSteps: [
3197
- `Import a component with: import { Button } from '$lib/components/ui/button';`,
3198
- "Tweak styling in src/lib/components/ui/<component>/*.svelte.",
3199
- "Run `vela ui base <color>` to change the palette (slate, gray, zinc, stone, neutral)."
3200
- ]
3201
- });
3202
- }, "Failed to add UI components.")
3203
- );
3235
+ // src/lib/server-command.ts
3236
+ import process14 from "node:process";
3237
+ import * as v5 from "valibot";
3238
+ import * as p19 from "@clack/prompts";
3239
+ import pc5 from "picocolors";
3204
3240
 
3205
- // src/commands/ui/base.ts
3241
+ // src/lib/deploy-config.ts
3206
3242
  import fs17 from "node:fs";
3207
3243
  import path17 from "node:path";
3208
- import { Command as Command40, InvalidArgumentError } from "commander";
3209
- var VALID_COLORS = ["slate", "gray", "zinc", "stone", "neutral"];
3210
- function findThemeFile(color) {
3211
- const candidate = path17.join(templatesDir(), "ui", "css", `${color}.css`);
3212
- if (!fs17.existsSync(candidate)) {
3213
- throw new Error(`Theme file not found for color: ${color}`);
3244
+ import crypto from "node:crypto";
3245
+ import { pathToFileURL } from "node:url";
3246
+ var CONFIG_BASENAMES = [
3247
+ "velastack.config.ts",
3248
+ "velastack.config.js",
3249
+ "velastack.config.mjs",
3250
+ "velastack.config.json"
3251
+ ];
3252
+ function findConfigFile(workspaceRootDir) {
3253
+ for (const name of CONFIG_BASENAMES) {
3254
+ const file = path17.join(workspaceRootDir, name);
3255
+ if (fs17.existsSync(file)) return file;
3214
3256
  }
3215
- return candidate;
3257
+ return null;
3216
3258
  }
3217
- function extractSelectors(css) {
3218
- const rootMatch = css.match(/:root\s*{[^}]*}/);
3219
- const darkMatch = css.match(/\.dark\s*{[^}]*}/);
3220
- if (!rootMatch || !darkMatch) {
3221
- throw new Error("Invalid theme file: missing :root or .dark selector");
3259
+ async function loadDeployConfig(workspaceRootDir) {
3260
+ const file = findConfigFile(workspaceRootDir);
3261
+ if (!file) return {};
3262
+ if (file.endsWith(".json")) {
3263
+ return JSON.parse(fs17.readFileSync(file, "utf8"));
3264
+ }
3265
+ const url = file.endsWith(".ts") ? await transpileToTemp(file) : pathToFileURL(file).href;
3266
+ try {
3267
+ const mod = await import(url);
3268
+ const config = mod.default;
3269
+ if (!config || typeof config !== "object") {
3270
+ throw new Error(`${path17.basename(file)} must export a config object as its default export.`);
3271
+ }
3272
+ return config;
3273
+ } finally {
3274
+ if (url !== pathToFileURL(file).href) fs17.rmSync(new URL(url), { force: true });
3222
3275
  }
3223
- return { root: rootMatch[0], dark: darkMatch[0] };
3224
3276
  }
3225
- function applyTheme(appCss, selectors) {
3226
- let out = appCss;
3227
- out = out.match(/:root\s*{[^}]*}/) ? out.replace(/:root\s*{[^}]*}/, selectors.root) : `${selectors.root}
3228
-
3229
- ${out}`;
3230
- out = out.match(/\.dark\s*{[^}]*}/) ? out.replace(/\.dark\s*{[^}]*}/, selectors.dark) : `${out}
3231
-
3232
- ${selectors.dark}`;
3233
- return out;
3277
+ async function transpileToTemp(file) {
3278
+ const { ts } = await import("ts-morph");
3279
+ const source = fs17.readFileSync(file, "utf8");
3280
+ const { outputText } = ts.transpileModule(source, {
3281
+ compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 }
3282
+ });
3283
+ const temp = path17.join(
3284
+ path17.dirname(file),
3285
+ `.velastack.config.${crypto.randomBytes(4).toString("hex")}.mjs`
3286
+ );
3287
+ fs17.writeFileSync(temp, outputText);
3288
+ return pathToFileURL(temp).href;
3234
3289
  }
3235
- var base = new Command40("base").description("change the base color").argument("<color>", "base color to use", (value) => {
3236
- if (!VALID_COLORS.includes(value)) {
3237
- throw new InvalidArgumentError(`Valid colors are: ${VALID_COLORS.join(", ")}`);
3290
+ function projectFilePath(workspaceRootDir) {
3291
+ return path17.join(workspaceRootDir, ".vela", "project.json");
3292
+ }
3293
+ function readProjectFile(workspaceRootDir) {
3294
+ const file = projectFilePath(workspaceRootDir);
3295
+ if (!fs17.existsSync(file)) return {};
3296
+ try {
3297
+ return JSON.parse(fs17.readFileSync(file, "utf8"));
3298
+ } catch {
3299
+ return {};
3300
+ }
3301
+ }
3302
+ function writeProjectFile(workspaceRootDir, data) {
3303
+ const file = projectFilePath(workspaceRootDir);
3304
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
3305
+ fs17.writeFileSync(file, JSON.stringify(data, null, 2) + "\n");
3306
+ }
3307
+ function resolveAppIdentity(workspaceRootDir, config = {}) {
3308
+ const project = readProjectFile(workspaceRootDir);
3309
+ const name = config.project ?? project.projectName ?? defaultProjectName(workspaceRootDir);
3310
+ if (project.appId) return { appId: project.appId, name };
3311
+ const appId = project.projectId ?? `${slug(name)}-${crypto.randomBytes(4).toString("hex")}`;
3312
+ writeProjectFile(workspaceRootDir, {
3313
+ ...project,
3314
+ appId,
3315
+ projectName: project.projectName ?? name
3316
+ });
3317
+ return { appId, name };
3318
+ }
3319
+ function readAppIdentity(workspaceRootDir, config = {}) {
3320
+ const project = readProjectFile(workspaceRootDir);
3321
+ const appId = project.appId ?? project.projectId;
3322
+ if (!appId) return null;
3323
+ return {
3324
+ appId,
3325
+ name: config.project ?? project.projectName ?? defaultProjectName(workspaceRootDir)
3326
+ };
3327
+ }
3328
+ function defaultProjectName(workspaceRootDir) {
3329
+ try {
3330
+ const pkg = readPackageJson(path17.join(workspaceRootDir, "package.json"));
3331
+ if (typeof pkg.name === "string" && pkg.name.trim()) return pkg.name.trim();
3332
+ } catch {
3238
3333
  }
3239
- return value;
3240
- }).configureHelp(helpConfig).action(
3241
- (color) => runCommand(async () => {
3242
- const { workspaceRootDir } = await getWorkspace();
3243
- const appCssPath = path17.join(workspaceRootDir, "src", "app.css");
3244
- if (!fs17.existsSync(appCssPath)) {
3245
- throw new Error(`Could not find ${path17.relative(workspaceRootDir, appCssPath)}`);
3246
- }
3247
- const themeContent = fs17.readFileSync(findThemeFile(color), "utf8");
3248
- const selectors = extractSelectors(themeContent);
3249
- const appCss = fs17.readFileSync(appCssPath, "utf8");
3250
- fs17.writeFileSync(appCssPath, applyTheme(appCss, selectors));
3251
- reportResult({
3252
- summary: `Set base color to ${color}.`,
3253
- filesModified: [path17.relative(workspaceRootDir, appCssPath)],
3254
- nextSteps: [
3255
- "Run your dev server to preview the new palette.",
3256
- "Tweak individual CSS variables in src/app.css if you want to customize the theme further."
3257
- ]
3258
- });
3259
- }, "Failed to change base color.")
3260
- );
3334
+ return path17.basename(workspaceRootDir);
3335
+ }
3336
+ function slug(value) {
3337
+ return value.toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "app";
3338
+ }
3339
+ function readBinding(workspaceRootDir, envTag) {
3340
+ const project = readProjectFile(workspaceRootDir);
3341
+ const bound = project.targets?.[envTag];
3342
+ if (bound?.server) return bound;
3343
+ const legacy = project.deployments?.[envTag];
3344
+ if (legacy?.target) return { server: legacy.target, domain: legacy.domain };
3345
+ return null;
3346
+ }
3347
+ function readBindings(workspaceRootDir) {
3348
+ const project = readProjectFile(workspaceRootDir);
3349
+ const bindings = {};
3350
+ for (const [envTag, legacy] of Object.entries(project.deployments ?? {})) {
3351
+ if (legacy?.target) bindings[envTag] = { server: legacy.target, domain: legacy.domain };
3352
+ }
3353
+ for (const [envTag, bound] of Object.entries(project.targets ?? {})) {
3354
+ if (bound?.server) bindings[envTag] = bound;
3355
+ }
3356
+ return bindings;
3357
+ }
3358
+ function writeBinding(workspaceRootDir, envTag, binding) {
3359
+ const project = readProjectFile(workspaceRootDir);
3360
+ writeProjectFile(workspaceRootDir, {
3361
+ ...project,
3362
+ targets: { ...project.targets, [envTag]: binding }
3363
+ });
3364
+ }
3261
3365
 
3262
- // src/commands/ui.ts
3263
- var ui = new Command41("ui").description("generate ui components").configureHelp(helpConfig).addCommand(add).addCommand(base);
3366
+ // src/lib/instance.ts
3367
+ var PROD_ENV = "prod";
3368
+ var MAX_SEGMENT = 48;
3369
+ function normalizeEnvTag(tag) {
3370
+ const raw = (tag ?? PROD_ENV).trim();
3371
+ if (!raw) return PROD_ENV;
3372
+ const normalized = raw.toLowerCase().replace(/\//g, "--").replace(/[^a-z0-9-]+/g, "-").replace(/-{3,}/g, "--").replace(/^-+|-+$/g, "");
3373
+ if (!normalized) throw new Error(`Invalid environment tag: ${tag}`);
3374
+ if (normalized === "local") {
3375
+ throw new Error("`local` is not a deployable environment \u2014 it is the copy on this machine.");
3376
+ }
3377
+ return normalized === "production" ? PROD_ENV : normalized;
3378
+ }
3379
+ function branchToEnvTag(branch) {
3380
+ const slug2 = branch.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SEGMENT);
3381
+ return `preview--${slug2}`;
3382
+ }
3383
+ function instanceId(appId, envTag = PROD_ENV) {
3384
+ const env2 = normalizeEnvTag(envTag);
3385
+ return env2 === PROD_ENV ? appId : `${appId}--${env2}`;
3386
+ }
3387
+ function isProd(envTag) {
3388
+ return normalizeEnvTag(envTag) === PROD_ENV;
3389
+ }
3390
+ function releaseId(date = /* @__PURE__ */ new Date()) {
3391
+ return date.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
3392
+ }
3264
3393
 
3265
- // src/commands/legal.ts
3266
- import { Command as Command44 } from "commander";
3394
+ // src/lib/ssh-options.ts
3395
+ import * as v4 from "valibot";
3396
+ var SSH_OPTION_SCHEMA = {
3397
+ identity: v4.optional(v4.string()),
3398
+ sshPort: v4.optional(v4.string()),
3399
+ acceptHostKeys: v4.optional(v4.boolean())
3400
+ };
3401
+ function addSshOptions(command) {
3402
+ return command.option("-i, --identity <file>", "SSH private key to authenticate with").option("--ssh-port <port>", "SSH port").option("--accept-host-keys", "trust an unknown host key on first connect (CI)");
3403
+ }
3404
+ function sshOptionsFrom(options) {
3405
+ return {
3406
+ identityFile: options.identity,
3407
+ port: options.sshPort ? Number(options.sshPort) : void 0,
3408
+ acceptNewHostKeys: options.acceptHostKeys
3409
+ };
3410
+ }
3267
3411
 
3268
- // src/commands/legal/terms.ts
3412
+ // src/lib/ssh.ts
3269
3413
  import fs18 from "node:fs";
3414
+ import os2 from "node:os";
3270
3415
  import path18 from "node:path";
3271
- import { Command as Command42 } from "commander";
3272
- import * as p20 from "@clack/prompts";
3273
-
3274
- // src/commands/legal/shared.ts
3416
+ import crypto2 from "node:crypto";
3275
3417
  import process13 from "node:process";
3276
- import * as p19 from "@clack/prompts";
3277
- var sharedFields = {
3278
- websiteUrl: () => p19.text({
3279
- message: "What is your website URL?",
3280
- placeholder: "http://www.mysite.com",
3281
- validate: (value) => {
3282
- if (!value || !value.startsWith("http")) {
3283
- return "Please enter a valid URL starting with http or https";
3284
- }
3418
+ import { spawn as spawn2 } from "node:child_process";
3419
+ var RemoteCommandError = class extends Error {
3420
+ exitCode;
3421
+ stdout;
3422
+ stderr;
3423
+ constructor(message, result) {
3424
+ super(message);
3425
+ this.name = "RemoteCommandError";
3426
+ this.exitCode = result.exitCode;
3427
+ this.stdout = result.stdout;
3428
+ this.stderr = result.stderr;
3429
+ }
3430
+ };
3431
+ var SshSession = class {
3432
+ target;
3433
+ elevation = "none";
3434
+ options;
3435
+ controlPath = null;
3436
+ constructor(target, options = {}) {
3437
+ this.target = target;
3438
+ this.options = options;
3439
+ }
3440
+ sshArgs() {
3441
+ const args = [];
3442
+ if (this.controlPath) args.push("-o", `ControlPath=${this.controlPath}`);
3443
+ if (this.options.identityFile) {
3444
+ args.push("-i", this.options.identityFile, "-o", "IdentitiesOnly=yes");
3285
3445
  }
3286
- }),
3287
- websiteName: () => p19.text({
3288
- message: "What is your website name?",
3289
- placeholder: "My Site",
3290
- validate: (value) => {
3291
- if (!value) {
3292
- return "Please enter a website name";
3293
- }
3446
+ if (this.options.port) args.push("-p", String(this.options.port));
3447
+ if (this.options.acceptNewHostKeys) args.push("-o", "StrictHostKeyChecking=accept-new");
3448
+ args.push("-o", "BatchMode=yes");
3449
+ return args;
3450
+ }
3451
+ /** The `ssh` invocation rsync should use, as a single shell word list. */
3452
+ rsyncShell() {
3453
+ const parts = ["ssh", ...this.sshArgs()];
3454
+ return parts.map(shellQuote).join(" ");
3455
+ }
3456
+ async open() {
3457
+ if (this.controlPath) return;
3458
+ const dir = path18.join(os2.tmpdir(), "vela-ssh");
3459
+ fs18.mkdirSync(dir, { recursive: true, mode: 448 });
3460
+ const socket = path18.join(dir, `${crypto2.randomBytes(6).toString("hex")}.sock`);
3461
+ this.controlPath = socket;
3462
+ const args = [
3463
+ ...this.sshArgs(),
3464
+ "-o",
3465
+ "ControlMaster=yes",
3466
+ "-o",
3467
+ "ControlPersist=60",
3468
+ "-N",
3469
+ "-f",
3470
+ this.target
3471
+ ];
3472
+ const result = await spawnCapture("ssh", args);
3473
+ if (result.exitCode !== 0) {
3474
+ this.controlPath = null;
3475
+ throw new Error(
3476
+ `Could not connect to ${this.target} over SSH.
3477
+
3478
+ ${result.stderr.trim() || result.stdout.trim()}`
3479
+ );
3294
3480
  }
3295
- }),
3296
- entityType: () => p19.select({
3297
- message: "Entity type",
3298
- options: [
3299
- {
3300
- value: "business",
3301
- label: "I'm a Business",
3302
- hint: "e.g. Corporation, Limited Liability Company, Non-profit, Partnership, Sole Proprietor"
3303
- },
3304
- { value: "individual", label: "I'm an Individual" }
3305
- ]
3306
- }),
3307
- businessName: ({ results }) => results?.entityType === "business" ? p19.text({
3308
- message: "What is the name of the business?",
3309
- placeholder: "My Company LLC",
3310
- validate: (value) => {
3311
- if (!value) {
3312
- return "Please enter a business name";
3313
- }
3481
+ }
3482
+ async close() {
3483
+ if (!this.controlPath) return;
3484
+ const socket = this.controlPath;
3485
+ this.controlPath = null;
3486
+ await spawnCapture("ssh", ["-o", `ControlPath=${socket}`, "-O", "exit", this.target]);
3487
+ }
3488
+ /**
3489
+ * Run a bash program on the server. The program is piped over stdin, so it can
3490
+ * safely embed anything — quoting, newlines, base64 blobs — and `args` arrive
3491
+ * as ordinary positional parameters.
3492
+ */
3493
+ async script(source, opts = {}) {
3494
+ const shell = this.elevation === "sudo" ? "sudo -n bash -s" : "bash -s";
3495
+ const remote = [shell, "--", ...(opts.args ?? []).map(shellQuote)].join(" ");
3496
+ const args = [...this.sshArgs(), this.target, remote];
3497
+ const result = await spawnCapture("ssh", args, {
3498
+ stdin: `set -euo pipefail
3499
+ ${source}`,
3500
+ stream: opts.stream
3501
+ });
3502
+ if (opts.check !== false && result.exitCode !== 0) {
3503
+ throw new RemoteCommandError(remoteFailure(this.target, result), result);
3314
3504
  }
3315
- }) : void 0,
3316
- businessAddress: ({ results }) => results?.entityType === "business" ? p19.text({
3317
- message: "What is the address of the business?",
3318
- placeholder: "1 Cupertino, CA 95014",
3319
- validate: (value) => {
3320
- if (!value) {
3321
- return "Please enter a business address";
3322
- }
3505
+ return result;
3506
+ }
3507
+ /** Attach the terminal to a remote command — used by `vela logs -f`. */
3508
+ async interactive(command) {
3509
+ const args = [...this.sshArgs(), "-t", this.target, command.map(shellQuote).join(" ")];
3510
+ return await new Promise((resolve2) => {
3511
+ const child = spawn2("ssh", args, { stdio: "inherit" });
3512
+ child.on("close", (code) => resolve2(code ?? 1));
3513
+ });
3514
+ }
3515
+ /**
3516
+ * Write a file on the server atomically, with an explicit mode.
3517
+ *
3518
+ * The payload rides inside the piped script as base64, so it never lands in
3519
+ * argv (visible in `ps`) or in a shell history, and binary-unsafe characters
3520
+ * are not an issue.
3521
+ */
3522
+ async writeFile(remotePath, content, mode = "0644") {
3523
+ const payload = Buffer.from(content, "utf8").toString("base64");
3524
+ await this.script(
3525
+ `dest="$1"; mode="$2"
3526
+ mkdir -p "$(dirname "$dest")"
3527
+ tmp=$(mktemp "$(dirname "$dest")/.vela.XXXXXX")
3528
+ printf %s ${payload} | base64 -d > "$tmp"
3529
+ chmod "$mode" "$tmp"
3530
+ mv -f "$tmp" "$dest"`,
3531
+ { args: [remotePath, mode] }
3532
+ );
3533
+ }
3534
+ /**
3535
+ * Tunnel a local port to a port on the server, through the connection that is
3536
+ * already open. Used to point a local build at the target's database.
3537
+ */
3538
+ async forwardLocalPort(localPort, remoteHost, remotePort) {
3539
+ if (!this.controlPath) throw new Error("Cannot forward a port without an open session.");
3540
+ const spec = `${localPort}:${remoteHost}:${remotePort}`;
3541
+ const result = await spawnCapture("ssh", [
3542
+ ...this.sshArgs(),
3543
+ "-O",
3544
+ "forward",
3545
+ "-L",
3546
+ spec,
3547
+ this.target
3548
+ ]);
3549
+ if (result.exitCode !== 0) {
3550
+ throw new Error(
3551
+ `Could not forward port ${spec} to ${this.target}.
3552
+
3553
+ ${result.stderr.trim()}`
3554
+ );
3323
3555
  }
3324
- }) : void 0,
3325
- country: () => p19.text({
3326
- message: "Enter the country",
3327
- validate: (value) => {
3328
- if (!value) {
3329
- return "Please enter a country";
3330
- }
3556
+ }
3557
+ async cancelForward(localPort, remoteHost, remotePort) {
3558
+ if (!this.controlPath) return;
3559
+ await spawnCapture("ssh", [
3560
+ ...this.sshArgs(),
3561
+ "-O",
3562
+ "cancel",
3563
+ "-L",
3564
+ `${localPort}:${remoteHost}:${remotePort}`,
3565
+ this.target
3566
+ ]);
3567
+ }
3568
+ async readFile(remotePath) {
3569
+ const result = await this.script(`cat "$1" 2>/dev/null || exit 44`, {
3570
+ args: [remotePath],
3571
+ check: false
3572
+ });
3573
+ if (result.exitCode === 44) return null;
3574
+ if (result.exitCode !== 0)
3575
+ throw new RemoteCommandError(remoteFailure(this.target, result), result);
3576
+ return result.stdout;
3577
+ }
3578
+ /** rsync a local directory (contents) into a remote directory. */
3579
+ async uploadDir(localDir, remoteDir, extraArgs = []) {
3580
+ const src = localDir.endsWith("/") ? localDir : `${localDir}/`;
3581
+ const args = [
3582
+ "-az",
3583
+ "--delete",
3584
+ "-e",
3585
+ this.rsyncShell(),
3586
+ ...this.rsyncPath(),
3587
+ ...extraArgs,
3588
+ src,
3589
+ `${this.target}:${remoteDir}`
3590
+ ];
3591
+ const result = await spawnCapture("rsync", args);
3592
+ if (result.exitCode !== 0) {
3593
+ throw new Error(`rsync to ${this.target}:${remoteDir} failed.
3594
+
3595
+ ${result.stderr.trim()}`);
3331
3596
  }
3332
- }),
3333
- state: () => p19.text({
3334
- message: "Enter the state",
3335
- validate: (value) => {
3336
- if (!value) {
3337
- return "Please enter a state";
3338
- }
3597
+ }
3598
+ rsyncPath() {
3599
+ return this.elevation === "sudo" ? ["--rsync-path=sudo -n rsync"] : [];
3600
+ }
3601
+ /** rsync one or more local paths to a remote directory. */
3602
+ async upload(localPaths, remoteDir, extraArgs = []) {
3603
+ const args = [
3604
+ "-az",
3605
+ "-e",
3606
+ this.rsyncShell(),
3607
+ ...this.rsyncPath(),
3608
+ ...extraArgs,
3609
+ ...localPaths,
3610
+ `${this.target}:${remoteDir}`
3611
+ ];
3612
+ const result = await spawnCapture("rsync", args);
3613
+ if (result.exitCode !== 0) {
3614
+ throw new Error(`rsync to ${this.target}:${remoteDir} failed.
3615
+
3616
+ ${result.stderr.trim()}`);
3339
3617
  }
3340
- })
3341
- };
3342
- var onCancel = () => {
3343
- p19.cancel("Operation cancelled.");
3344
- process13.exit(0);
3618
+ }
3619
+ /**
3620
+ * Decide once whether remote commands need `sudo`, and fail with something
3621
+ * actionable if they do but cannot get it.
3622
+ */
3623
+ async detectElevation() {
3624
+ const uid = await this.script("id -u", { check: false });
3625
+ if (uid.stdout.trim() === "0") {
3626
+ this.elevation = "none";
3627
+ return this.elevation;
3628
+ }
3629
+ const sudo = await this.script("sudo -n true", { check: false });
3630
+ if (sudo.exitCode !== 0) {
3631
+ throw new Error(
3632
+ `${this.target} connects as a non-root user without passwordless sudo.
3633
+
3634
+ Use an SSH target that logs in as root, or give the user NOPASSWD sudo.`
3635
+ );
3636
+ }
3637
+ this.elevation = "sudo";
3638
+ return this.elevation;
3639
+ }
3640
+ };
3641
+ function remoteFailure(target, result) {
3642
+ const detail = (result.stderr.trim() || result.stdout.trim()).split("\n").slice(-12).join("\n");
3643
+ return `Remote command failed on ${target} (exit ${result.exitCode}).
3644
+
3645
+ ${detail}`;
3646
+ }
3647
+ async function withSsh(target, options, fn) {
3648
+ const session = new SshSession(target, options);
3649
+ await session.open();
3650
+ try {
3651
+ return await fn(session);
3652
+ } finally {
3653
+ await session.close();
3654
+ }
3655
+ }
3656
+ function shellQuote(value) {
3657
+ if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
3658
+ return `'${value.replaceAll("'", `'\\''`)}'`;
3659
+ }
3660
+ function spawnCapture(command, args, opts = {}) {
3661
+ return new Promise((resolve2, reject) => {
3662
+ const stdio = [opts.stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"];
3663
+ const child = spawn2(command, args, {
3664
+ stdio,
3665
+ cwd: opts.cwd,
3666
+ env: opts.env ? { ...process13.env, ...opts.env } : void 0
3667
+ });
3668
+ let stdout = "";
3669
+ let stderr = "";
3670
+ child.stdout?.on("data", (chunk) => {
3671
+ stdout += chunk;
3672
+ if (opts.streamStdout) process13.stdout.write(chunk);
3673
+ });
3674
+ child.stderr?.on("data", (chunk) => {
3675
+ stderr += chunk;
3676
+ if (opts.stream) process13.stderr.write(chunk);
3677
+ });
3678
+ child.on("error", reject);
3679
+ child.on("close", (code) => resolve2({ stdout, stderr, exitCode: code ?? 1 }));
3680
+ if (opts.stdin !== void 0) {
3681
+ child.stdin.end(opts.stdin);
3682
+ }
3683
+ });
3684
+ }
3685
+
3686
+ // src/lib/remote.ts
3687
+ import path19 from "node:path";
3688
+ var VELA_ROOT = "/var/lib/vela";
3689
+ var VELA_ETC = "/etc/vela";
3690
+ var SCRIPTS_DIR = `${VELA_ROOT}/scripts`;
3691
+ var PROVISIONED_MARKER = `${VELA_ETC}/provisioned`;
3692
+ function serverTemplatesDir() {
3693
+ return path19.join(templatesDir(), "server");
3694
+ }
3695
+ async function syncServerScripts(session) {
3696
+ await session.script(`mkdir -p "$1" && chmod 0755 "$1"`, { args: [SCRIPTS_DIR] });
3697
+ await session.uploadDir(serverTemplatesDir(), SCRIPTS_DIR, ["--chmod=D755,F755"]);
3698
+ await session.script(`chown -R root:root "$1"`, { args: [SCRIPTS_DIR] });
3699
+ }
3700
+ async function runServerScript(session, name, opts = {}) {
3701
+ const result = await session.script(`script="$1"; shift; exec "$script" "$@"`, {
3702
+ args: [`${SCRIPTS_DIR}/${name}`, ...opts.args ?? []],
3703
+ stream: opts.stream
3704
+ });
3705
+ return parseResult(result);
3706
+ }
3707
+ function parseResult(result) {
3708
+ const line = result.stdout.split("\n").reverse().find((l) => l.startsWith("VELA_RESULT "));
3709
+ if (!line) return null;
3710
+ try {
3711
+ return JSON.parse(line.slice("VELA_RESULT ".length));
3712
+ } catch {
3713
+ return null;
3714
+ }
3715
+ }
3716
+ async function readServerInfo(session) {
3717
+ const raw = await session.readFile(PROVISIONED_MARKER);
3718
+ if (!raw) return null;
3719
+ try {
3720
+ return JSON.parse(raw);
3721
+ } catch {
3722
+ return null;
3723
+ }
3724
+ }
3725
+ async function requireProvisioned(session) {
3726
+ const info = await readServerInfo(session);
3727
+ if (!info) {
3728
+ throw new Error(
3729
+ `${session.target} has not been provisioned for vela yet.
3730
+
3731
+ Run \`vela provision ${session.target}\` first.`
3732
+ );
3733
+ }
3734
+ return info;
3735
+ }
3736
+ async function readInstanceStates(session, instance) {
3737
+ const result = await runServerScript(session, "status.sh", {
3738
+ args: instance ? [instance] : []
3739
+ });
3740
+ return Array.isArray(result) ? result : [];
3741
+ }
3742
+ var remotePaths = {
3743
+ app: (instance) => `${VELA_ROOT}/apps/${instance}`,
3744
+ releases: (instance) => `${VELA_ROOT}/apps/${instance}/releases`,
3745
+ release: (instance, release) => `${VELA_ROOT}/apps/${instance}/releases/${release}`,
3746
+ env: (instance) => `${VELA_ETC}/apps/${instance}/env`,
3747
+ runtimeEnv: (instance) => `${VELA_ETC}/apps/${instance}/runtime.env`,
3748
+ caddy: (instance) => `${VELA_ETC}/caddy/${instance}.caddy`,
3749
+ webUnit: (instance) => `vela-web@${instance}.service`,
3750
+ pbUnit: (instance) => `vela-pb@${instance}.service`
3751
+ };
3752
+
3753
+ // src/lib/remote-env.ts
3754
+ import fs19 from "node:fs";
3755
+ import dotenv from "dotenv";
3756
+ var KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
3757
+ function isValidKey(key) {
3758
+ return KEY_PATTERN.test(key);
3759
+ }
3760
+ function parseEnv(content) {
3761
+ const result = {};
3762
+ for (const line of content.split("\n")) {
3763
+ const trimmed = line.trim();
3764
+ if (!trimmed || trimmed.startsWith("#")) continue;
3765
+ const eq = trimmed.indexOf("=");
3766
+ if (eq < 1) continue;
3767
+ const key = trimmed.slice(0, eq).trim();
3768
+ if (!isValidKey(key)) continue;
3769
+ result[key] = unquote(trimmed.slice(eq + 1).trim());
3770
+ }
3771
+ return result;
3772
+ }
3773
+ function unquote(raw) {
3774
+ if (!raw.startsWith('"')) return raw;
3775
+ const body = raw.endsWith('"') && raw.length > 1 ? raw.slice(1, -1) : raw.slice(1);
3776
+ let out = "";
3777
+ for (let i = 0; i < body.length; i++) {
3778
+ if (body[i] !== "\\" || i === body.length - 1) {
3779
+ out += body[i];
3780
+ continue;
3781
+ }
3782
+ const next = body[++i];
3783
+ if (next === "n") out += "\n";
3784
+ else if (next === "t") out += " ";
3785
+ else out += next;
3786
+ }
3787
+ return out;
3788
+ }
3789
+ function serializeEnv(env2) {
3790
+ const keys = Object.keys(env2).sort();
3791
+ const lines = [
3792
+ "# Managed by `vela env`. Values are read by systemd at service start.",
3793
+ ...keys.map((key) => `${key}=${quote(env2[key])}`)
3794
+ ];
3795
+ return lines.join("\n") + "\n";
3796
+ }
3797
+ function quote(value) {
3798
+ const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n").replaceAll(" ", "\\t");
3799
+ return `"${escaped}"`;
3800
+ }
3801
+ function readLocalEnvFile(file) {
3802
+ const parsed = dotenv.parse(fs19.readFileSync(file));
3803
+ const result = {};
3804
+ for (const [key, value] of Object.entries(parsed)) {
3805
+ if (isValidKey(key)) result[key] = value;
3806
+ }
3807
+ return result;
3808
+ }
3809
+ async function readRemoteEnv(session, instance) {
3810
+ const content = await session.readFile(remotePaths.env(instance));
3811
+ return content ? parseEnv(content) : {};
3812
+ }
3813
+ async function writeRemoteEnv(session, instance, env2) {
3814
+ const file = remotePaths.env(instance);
3815
+ await session.writeFile(file, serializeEnv(env2), "0600");
3816
+ await session.script(`chown root:root "$1" && chmod 0600 "$1"`, { args: [file] });
3817
+ }
3818
+ var SUPERUSER_KEYS = [
3819
+ "POCKETBASE_SUPERUSER_EMAIL",
3820
+ "POCKETBASE_SUPERUSER_PASSWORD"
3821
+ ];
3822
+ function touchesSuperuser(keys) {
3823
+ for (const key of keys) {
3824
+ if (SUPERUSER_KEYS.includes(key)) return true;
3825
+ }
3826
+ return false;
3827
+ }
3828
+ async function restartInstance(session, instance) {
3829
+ const result = await session.script(
3830
+ `instance="$1"
3831
+ enabled=()
3832
+ for unit in "vela-pb@$instance.service" "vela-web@$instance.service"; do
3833
+ if systemctl is-enabled "$unit" >/dev/null 2>&1; then enabled+=("$unit"); fi
3834
+ done
3835
+ if [ \${#enabled[@]} -eq 0 ]; then echo NOT_DEPLOYED; exit 0; fi
3836
+ systemctl restart "\${enabled[@]}"`,
3837
+ { args: [instance], check: false }
3838
+ );
3839
+ if (result.stdout.includes("NOT_DEPLOYED")) return { deployed: false, restarted: false };
3840
+ if (result.exitCode !== 0) {
3841
+ return { deployed: true, restarted: false, error: result.stderr.trim() };
3842
+ }
3843
+ return { deployed: true, restarted: true };
3844
+ }
3845
+
3846
+ // src/lib/target.ts
3847
+ var LOCAL_TARGET = "local";
3848
+ var PRODUCTION_TARGET = "production";
3849
+ var TargetError = class extends Error {
3850
+ };
3851
+ var SERVER_SHAPED = /@|^\d{1,3}(?:\.\d{1,3}){3}$|\.[a-z]{2,}$/i;
3852
+ var PREVIEW = "preview";
3853
+ var TARGET_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3854
+ function parseTarget(raw, fallback) {
3855
+ const value = raw?.trim();
3856
+ if (!value) {
3857
+ return fallback === "local" ? { kind: "local" } : production();
3858
+ }
3859
+ if (SERVER_SHAPED.test(value)) {
3860
+ throw new TargetError(
3861
+ `${value} looks like a server, and ${bold("-t")} now selects a target rather than a machine.
3862
+
3863
+ Targets are ${bold("local")}, ${bold("production")}, or a name you choose such as ${bold("staging")}.
3864
+ To point a target at a server, pass ${bold("--server")} on \`vela deploy\`.`
3865
+ );
3866
+ }
3867
+ const lower = value.toLowerCase();
3868
+ if (lower === LOCAL_TARGET) return { kind: "local" };
3869
+ if (lower === PREVIEW || lower.startsWith(`${PREVIEW}:`)) {
3870
+ const branch = lower.slice(PREVIEW.length + 1) || void 0;
3871
+ return {
3872
+ kind: "preview",
3873
+ branch,
3874
+ // Held for when previews are built, so the grammar and the instance
3875
+ // naming cannot drift apart in the meantime.
3876
+ envTag: branch ? branchToEnvTag(branch) : PREVIEW
3877
+ };
3878
+ }
3879
+ if (value.includes(":")) {
3880
+ throw new TargetError(
3881
+ `${value} is not a target. Only ${bold("preview")} takes a \`:branch\` suffix.`
3882
+ );
3883
+ }
3884
+ if (!TARGET_NAME.test(lower)) {
3885
+ throw new TargetError(
3886
+ `${value} is not a target name.
3887
+
3888
+ Use lowercase letters, digits and single dashes, such as ${bold("staging")}.`
3889
+ );
3890
+ }
3891
+ return { kind: "remote", name: lower, envTag: normalizeEnvTag(lower) };
3892
+ }
3893
+ function production() {
3894
+ return { kind: "remote", name: PRODUCTION_TARGET, envTag: PROD_ENV };
3895
+ }
3896
+ function describeTarget(target) {
3897
+ switch (target.kind) {
3898
+ case "local":
3899
+ return LOCAL_TARGET;
3900
+ case "preview":
3901
+ return target.branch ? `${PREVIEW}:${target.branch}` : PREVIEW;
3902
+ case "remote":
3903
+ return target.name;
3904
+ }
3905
+ }
3906
+ function bold(text16) {
3907
+ return `\`${text16}\``;
3908
+ }
3909
+
3910
+ // src/lib/server-command.ts
3911
+ var SERVER_OPTIONS_SCHEMA = {
3912
+ ...SSH_OPTION_SCHEMA,
3913
+ target: v5.optional(v5.string()),
3914
+ server: v5.optional(v5.string())
3915
+ };
3916
+ var OptionsSchema = v5.object(SERVER_OPTIONS_SCHEMA);
3917
+ function addTargetOptions(command, fallback) {
3918
+ return addSshOptions(command).option("-t, --target <target>", "which copy of the app to act on", fallback).option("--server <ssh>", "server this target runs on \u2014 recorded on first use");
3919
+ }
3920
+ async function withTarget(raw, handlers, run = {}) {
3921
+ const options = parseOptions(OptionsSchema, raw);
3922
+ const target = parseTarget(options.target, "production");
3923
+ const label = run.label ? `vela ${run.label}` : "this command";
3924
+ if (target.kind === "preview") {
3925
+ throw new Error(
3926
+ `Preview targets are not supported yet.
3927
+
3928
+ \`${describeTarget(target)}\` parses, but nothing deploys it: previews need wildcard
3929
+ DNS and certificates that \`vela provision\` does not set up yet.`
3930
+ );
3931
+ }
3932
+ const { workspaceRootDir } = await getWorkspace();
3933
+ const config = await loadDeployConfig(workspaceRootDir);
3934
+ const app = resolveAppIdentity(workspaceRootDir, {
3935
+ ...config,
3936
+ project: run.project ?? config.project
3937
+ });
3938
+ const base2 = {
3939
+ workspaceRootDir,
3940
+ appName: app.name,
3941
+ appId: app.appId,
3942
+ config
3943
+ };
3944
+ if (target.kind === "local") {
3945
+ if (!handlers.local) {
3946
+ throw new Error(
3947
+ `${label} has no local target.` + (run.localHint ? `
3948
+
3949
+ ${run.localHint}` : "")
3950
+ );
3951
+ }
3952
+ await handlers.local({
3953
+ ...base2,
3954
+ kind: "local",
3955
+ envFile: envFilePath(workspaceRootDir)
3956
+ });
3957
+ return;
3958
+ }
3959
+ if (!handlers.remote) {
3960
+ throw new Error(`${label} only acts on ${pc5.cyan("local")}.`);
3961
+ }
3962
+ const binding = await ensureBinding(workspaceRootDir, target, {
3963
+ server: options.server,
3964
+ askDomain: run.askDomain
3965
+ });
3966
+ await withSsh(binding.server, sshOptionsFrom(options), async (session) => {
3967
+ await session.detectElevation();
3968
+ await requireProvisioned(session);
3969
+ await syncServerScripts(session);
3970
+ await handlers.remote({
3971
+ ...base2,
3972
+ kind: "remote",
3973
+ session,
3974
+ instance: instanceId(app.appId, target.envTag),
3975
+ envTag: target.envTag,
3976
+ targetName: target.name,
3977
+ server: binding.server,
3978
+ binding
3979
+ });
3980
+ });
3981
+ }
3982
+ async function ensureBinding(workspaceRootDir, target, request = {}) {
3983
+ const existing = readBinding(workspaceRootDir, target.envTag);
3984
+ const moving = Boolean(request.server && existing && existing.server !== request.server);
3985
+ let server = request.server ?? existing?.server;
3986
+ if (!server) {
3987
+ server = await promptServer(target);
3988
+ }
3989
+ let domain = request.domain ?? existing?.domain;
3990
+ if (!domain && request.askDomain && !request.server) {
3991
+ domain = await promptDomain(target);
3992
+ }
3993
+ const binding = domain ? { server, domain } : { server };
3994
+ if (!existing || existing.server !== server || existing.domain !== domain) {
3995
+ writeBinding(workspaceRootDir, target.envTag, binding);
3996
+ }
3997
+ if (moving) {
3998
+ p19.log.warn(
3999
+ `${pc5.cyan(target.name)} now points at ${pc5.cyan(server)}.
4000
+
4001
+ Whatever is running on ${existing.server} is left there, and this project can no
4002
+ longer reach it. Remove it there first if that was not intended.`
4003
+ );
4004
+ }
4005
+ return binding;
4006
+ }
4007
+ async function promptServer(target) {
4008
+ if (!process14.stdout.isTTY || process14.env.CI) {
4009
+ throw new Error(
4010
+ `${target.name} is not bound to a server yet, and there is no terminal to ask on.
4011
+
4012
+ Pass ${pc5.cyan("--server user@host")}, or run the command once from your own machine
4013
+ and commit ${pc5.cyan(".vela/project.json")}.`
4014
+ );
4015
+ }
4016
+ p19.log.info(`${pc5.cyan(target.name)} is not bound to a server yet.`);
4017
+ const value = await p19.text({
4018
+ message: `Which server should ${target.name} run on?`,
4019
+ placeholder: "root@203.0.113.10",
4020
+ validate: (input) => input?.trim() ? void 0 : "An ssh_config alias, or user@host"
4021
+ });
4022
+ if (p19.isCancel(value)) {
4023
+ p19.cancel("Operation cancelled.");
4024
+ process14.exit(0);
4025
+ }
4026
+ return value.trim();
4027
+ }
4028
+ async function promptDomain(target) {
4029
+ if (!process14.stdout.isTTY || process14.env.CI) return void 0;
4030
+ const value = await p19.text({
4031
+ message: `Which hostname should ${target.name} be served on?`,
4032
+ placeholder: "example.com \u2014 leave blank to decide later"
4033
+ });
4034
+ if (p19.isCancel(value)) {
4035
+ p19.cancel("Operation cancelled.");
4036
+ process14.exit(0);
4037
+ }
4038
+ return value.trim() || void 0;
4039
+ }
4040
+ async function applyRestart(ctx) {
4041
+ const outcome = await restartInstance(ctx.session, ctx.instance);
4042
+ if (!outcome.deployed) return;
4043
+ if (outcome.restarted) {
4044
+ p19.log.success("App restarted");
4045
+ return;
4046
+ }
4047
+ p19.log.error(
4048
+ `App restart failed.
4049
+
4050
+ The new value is stored and will be used the next time the app starts.
4051
+ ${pc5.dim(outcome.error ?? "")}`
4052
+ );
4053
+ }
4054
+ async function applyEnvRestart(ctx, changed) {
4055
+ if (touchesSuperuser(changed)) {
4056
+ p19.log.warn(
4057
+ `PocketBase superuser credentials changed.
4058
+
4059
+ The database still holds the old ones, so the app has not been restarted.
4060
+ Run ${pc5.cyan("vela deploy")} to push them into the database and restart.`
4061
+ );
4062
+ return;
4063
+ }
4064
+ await applyRestart(ctx);
4065
+ }
4066
+ async function withServerSession(raw, fn) {
4067
+ const options = parseOptions(OptionsSchema, raw);
4068
+ let server = options.server;
4069
+ if (!server) {
4070
+ const { workspaceRootDir } = await getWorkspace();
4071
+ const target = parseTarget(options.target, "production");
4072
+ if (target.kind !== "remote") {
4073
+ throw new Error(`${describeTarget(target)} does not run on a server.`);
4074
+ }
4075
+ server = (await ensureBinding(workspaceRootDir, target, { server: options.server })).server;
4076
+ }
4077
+ await withSsh(server, sshOptionsFrom(options), async (session) => {
4078
+ await session.detectElevation();
4079
+ await requireProvisioned(session);
4080
+ await syncServerScripts(session);
4081
+ await fn(session);
4082
+ });
4083
+ }
4084
+ function envFilePath(workspaceRootDir) {
4085
+ return `${workspaceRootDir}/.env`;
4086
+ }
4087
+
4088
+ // src/commands/destroy/deployment.ts
4089
+ var deployment = addTargetOptions(
4090
+ new Command38("deployment").description("remove a deployed environment from its server").configureHelp(helpConfig),
4091
+ "production"
4092
+ ).option("--purge", "also delete the database and uploaded files").option("-y, --yes", "skip the confirmation prompt").action(
4093
+ (raw) => runCommand(async () => {
4094
+ const options = raw;
4095
+ await withTarget(
4096
+ raw,
4097
+ {
4098
+ remote: async (ctx) => {
4099
+ if (!options.yes) {
4100
+ await confirm5(ctx.appName, ctx.targetName, ctx.envTag, options.purge === true);
4101
+ }
4102
+ const result = await runServerScript(ctx.session, "destroy.sh", {
4103
+ args: [ctx.instance, ...options.purge ? ["--purge"] : []],
4104
+ stream: true
4105
+ });
4106
+ p20.log.success(
4107
+ `Removed ${pc6.cyan(`${ctx.appName} (${ctx.targetName})`)} from ${ctx.server}.` + (result?.purged ? "" : `
4108
+
4109
+ The database and uploads are still in the instance's shared directory. Pass ${pc6.cyan("--purge")} to delete them.`)
4110
+ );
4111
+ }
4112
+ },
4113
+ {
4114
+ label: "destroy deployment",
4115
+ localHint: "There is nothing deployed locally to remove."
4116
+ }
4117
+ );
4118
+ }, "Failed to remove the deployment.")
4119
+ );
4120
+ async function confirm5(appName, targetName, envTag, purge) {
4121
+ if (isProd(envTag) || purge) {
4122
+ const answer = await p20.text({
4123
+ message: `This removes ${pc6.cyan(`${appName} (${targetName})`)}${purge ? " and its database" : ""}. Type the app name to confirm`,
4124
+ validate: (value) => value === appName ? void 0 : `Type ${appName} to confirm`
4125
+ });
4126
+ if (p20.isCancel(answer)) {
4127
+ p20.cancel("Operation cancelled.");
4128
+ process15.exit(0);
4129
+ }
4130
+ return;
4131
+ }
4132
+ const ok = await p20.confirm({
4133
+ message: `Remove ${appName} (${targetName})?`,
4134
+ initialValue: false
4135
+ });
4136
+ if (p20.isCancel(ok) || !ok) {
4137
+ p20.cancel("Operation cancelled.");
4138
+ process15.exit(0);
4139
+ }
4140
+ }
4141
+
4142
+ // src/commands/destroy.ts
4143
+ var destroy = new Command39("destroy").description("destroy scaffolding, or a deployment").configureHelp(helpConfig).addCommand(form2).addCommand(schema2).addCommand(resource2).addCommand(scaffold2).addCommand(deployment);
4144
+
4145
+ // src/commands/ui.ts
4146
+ import { Command as Command42 } from "commander";
4147
+
4148
+ // src/commands/ui/add.ts
4149
+ import { Command as Command40 } from "commander";
4150
+ import { exec as exec2 } from "tinyexec";
4151
+ import { detect as detect4, resolveCommand as resolveCommand4 } from "package-manager-detector";
4152
+ var add = new Command40("add").description("add ui components").argument("<components...>", "the components to add").configureHelp(helpConfig).action(
4153
+ (components) => runCommand(async () => {
4154
+ const { workspaceRootDir } = await getWorkspace();
4155
+ const packageManager = (await detect4({ cwd: workspaceRootDir }))?.name ?? "npm";
4156
+ const resolved = resolveCommand4(packageManager, "execute", [
4157
+ "shadcn-svelte",
4158
+ "add",
4159
+ ...components
4160
+ ]);
4161
+ if (!resolved) {
4162
+ throw new Error(`Unable to resolve execute command for ${packageManager}`);
4163
+ }
4164
+ const args = [...resolved.args];
4165
+ if (packageManager === "npm") args.unshift("--yes");
4166
+ try {
4167
+ await exec2(resolved.command, args, {
4168
+ nodeOptions: { cwd: workspaceRootDir, stdio: "inherit" },
4169
+ throwOnError: true
4170
+ });
4171
+ } catch (error) {
4172
+ const typed = error;
4173
+ throw new Error(
4174
+ `Failed to execute '${resolved.command} ${args.join(" ")}': ${typed.message}`,
4175
+ { cause: typed.output }
4176
+ );
4177
+ }
4178
+ reportResult({
4179
+ summary: `Added ${components.length} UI component(s).`,
4180
+ componentsAdded: components,
4181
+ nextSteps: [
4182
+ `Import a component with: import { Button } from '$lib/components/ui/button';`,
4183
+ "Tweak styling in src/lib/components/ui/<component>/*.svelte.",
4184
+ "Run `vela ui base <color>` to change the palette (slate, gray, zinc, stone, neutral)."
4185
+ ]
4186
+ });
4187
+ }, "Failed to add UI components.")
4188
+ );
4189
+
4190
+ // src/commands/ui/base.ts
4191
+ import fs20 from "node:fs";
4192
+ import path20 from "node:path";
4193
+ import { Command as Command41, InvalidArgumentError } from "commander";
4194
+ var VALID_COLORS = ["slate", "gray", "zinc", "stone", "neutral"];
4195
+ function findThemeFile(color) {
4196
+ const candidate = path20.join(templatesDir(), "ui", "css", `${color}.css`);
4197
+ if (!fs20.existsSync(candidate)) {
4198
+ throw new Error(`Theme file not found for color: ${color}`);
4199
+ }
4200
+ return candidate;
4201
+ }
4202
+ function extractSelectors(css) {
4203
+ const rootMatch = css.match(/:root\s*{[^}]*}/);
4204
+ const darkMatch = css.match(/\.dark\s*{[^}]*}/);
4205
+ if (!rootMatch || !darkMatch) {
4206
+ throw new Error("Invalid theme file: missing :root or .dark selector");
4207
+ }
4208
+ return { root: rootMatch[0], dark: darkMatch[0] };
4209
+ }
4210
+ function applyTheme(appCss, selectors) {
4211
+ let out = appCss;
4212
+ out = out.match(/:root\s*{[^}]*}/) ? out.replace(/:root\s*{[^}]*}/, selectors.root) : `${selectors.root}
4213
+
4214
+ ${out}`;
4215
+ out = out.match(/\.dark\s*{[^}]*}/) ? out.replace(/\.dark\s*{[^}]*}/, selectors.dark) : `${out}
4216
+
4217
+ ${selectors.dark}`;
4218
+ return out;
4219
+ }
4220
+ var base = new Command41("base").description("change the base color").argument("<color>", "base color to use", (value) => {
4221
+ if (!VALID_COLORS.includes(value)) {
4222
+ throw new InvalidArgumentError(`Valid colors are: ${VALID_COLORS.join(", ")}`);
4223
+ }
4224
+ return value;
4225
+ }).configureHelp(helpConfig).action(
4226
+ (color) => runCommand(async () => {
4227
+ const { workspaceRootDir } = await getWorkspace();
4228
+ const appCssPath = path20.join(workspaceRootDir, "src", "app.css");
4229
+ if (!fs20.existsSync(appCssPath)) {
4230
+ throw new Error(`Could not find ${path20.relative(workspaceRootDir, appCssPath)}`);
4231
+ }
4232
+ const themeContent = fs20.readFileSync(findThemeFile(color), "utf8");
4233
+ const selectors = extractSelectors(themeContent);
4234
+ const appCss = fs20.readFileSync(appCssPath, "utf8");
4235
+ fs20.writeFileSync(appCssPath, applyTheme(appCss, selectors));
4236
+ reportResult({
4237
+ summary: `Set base color to ${color}.`,
4238
+ filesModified: [path20.relative(workspaceRootDir, appCssPath)],
4239
+ nextSteps: [
4240
+ "Run your dev server to preview the new palette.",
4241
+ "Tweak individual CSS variables in src/app.css if you want to customize the theme further."
4242
+ ]
4243
+ });
4244
+ }, "Failed to change base color.")
4245
+ );
4246
+
4247
+ // src/commands/ui.ts
4248
+ var ui = new Command42("ui").description("generate ui components").configureHelp(helpConfig).addCommand(add).addCommand(base);
4249
+
4250
+ // src/commands/legal.ts
4251
+ import { Command as Command45 } from "commander";
4252
+
4253
+ // src/commands/legal/terms.ts
4254
+ import fs21 from "node:fs";
4255
+ import path21 from "node:path";
4256
+ import { Command as Command43 } from "commander";
4257
+ import * as p22 from "@clack/prompts";
4258
+
4259
+ // src/commands/legal/shared.ts
4260
+ import process16 from "node:process";
4261
+ import * as p21 from "@clack/prompts";
4262
+ var sharedFields = {
4263
+ websiteUrl: () => p21.text({
4264
+ message: "What is your website URL?",
4265
+ placeholder: "http://www.mysite.com",
4266
+ validate: (value) => {
4267
+ if (!value || !value.startsWith("http")) {
4268
+ return "Please enter a valid URL starting with http or https";
4269
+ }
4270
+ }
4271
+ }),
4272
+ websiteName: () => p21.text({
4273
+ message: "What is your website name?",
4274
+ placeholder: "My Site",
4275
+ validate: (value) => {
4276
+ if (!value) {
4277
+ return "Please enter a website name";
4278
+ }
4279
+ }
4280
+ }),
4281
+ entityType: () => p21.select({
4282
+ message: "Entity type",
4283
+ options: [
4284
+ {
4285
+ value: "business",
4286
+ label: "I'm a Business",
4287
+ hint: "e.g. Corporation, Limited Liability Company, Non-profit, Partnership, Sole Proprietor"
4288
+ },
4289
+ { value: "individual", label: "I'm an Individual" }
4290
+ ]
4291
+ }),
4292
+ businessName: ({ results }) => results?.entityType === "business" ? p21.text({
4293
+ message: "What is the name of the business?",
4294
+ placeholder: "My Company LLC",
4295
+ validate: (value) => {
4296
+ if (!value) {
4297
+ return "Please enter a business name";
4298
+ }
4299
+ }
4300
+ }) : void 0,
4301
+ businessAddress: ({ results }) => results?.entityType === "business" ? p21.text({
4302
+ message: "What is the address of the business?",
4303
+ placeholder: "1 Cupertino, CA 95014",
4304
+ validate: (value) => {
4305
+ if (!value) {
4306
+ return "Please enter a business address";
4307
+ }
4308
+ }
4309
+ }) : void 0,
4310
+ country: () => p21.text({
4311
+ message: "Enter the country",
4312
+ validate: (value) => {
4313
+ if (!value) {
4314
+ return "Please enter a country";
4315
+ }
4316
+ }
4317
+ }),
4318
+ state: () => p21.text({
4319
+ message: "Enter the state",
4320
+ validate: (value) => {
4321
+ if (!value) {
4322
+ return "Please enter a state";
4323
+ }
4324
+ }
4325
+ })
4326
+ };
4327
+ var onCancel = () => {
4328
+ p21.cancel("Operation cancelled.");
4329
+ process16.exit(0);
3345
4330
  };
3346
4331
  async function contactMethods(type) {
3347
- const selection = await p19.multiselect({
4332
+ const selection = await p21.multiselect({
3348
4333
  message: `How can users contact you for any questions regarding your ${type === "privacy" ? "Privacy Policy" : "Terms & Conditions"}? Check all that apply`,
3349
4334
  options: [
3350
4335
  { value: "email", label: "By email" },
@@ -3353,11 +4338,11 @@ async function contactMethods(type) {
3353
4338
  { value: "mail", label: "By sending post mail" }
3354
4339
  ]
3355
4340
  });
3356
- if (p19.isCancel(selection)) onCancel();
4341
+ if (p21.isCancel(selection)) onCancel();
3357
4342
  const contact = selection;
3358
4343
  const details = {};
3359
4344
  if (contact.includes("email")) {
3360
- const email3 = await p19.text({
4345
+ const email3 = await p21.text({
3361
4346
  message: "What's the email?",
3362
4347
  placeholder: "office@mycompany.com",
3363
4348
  validate: (value) => {
@@ -3366,11 +4351,11 @@ async function contactMethods(type) {
3366
4351
  }
3367
4352
  }
3368
4353
  });
3369
- if (p19.isCancel(email3)) onCancel();
4354
+ if (p21.isCancel(email3)) onCancel();
3370
4355
  details.email = email3;
3371
4356
  }
3372
4357
  if (contact.includes("page")) {
3373
- const page = await p19.text({
4358
+ const page = await p21.text({
3374
4359
  message: "What's the link?",
3375
4360
  placeholder: "http://www.mycompany.com/contact",
3376
4361
  validate: (value) => {
@@ -3379,11 +4364,11 @@ async function contactMethods(type) {
3379
4364
  }
3380
4365
  }
3381
4366
  });
3382
- if (p19.isCancel(page)) onCancel();
4367
+ if (p21.isCancel(page)) onCancel();
3383
4368
  details.page = page;
3384
4369
  }
3385
4370
  if (contact.includes("phone")) {
3386
- const phone = await p19.text({
4371
+ const phone = await p21.text({
3387
4372
  message: "What's the phone number?",
3388
4373
  placeholder: "408.996.1010",
3389
4374
  validate: (value) => {
@@ -3392,11 +4377,11 @@ async function contactMethods(type) {
3392
4377
  }
3393
4378
  }
3394
4379
  });
3395
- if (p19.isCancel(phone)) onCancel();
4380
+ if (p21.isCancel(phone)) onCancel();
3396
4381
  details.phone = phone;
3397
4382
  }
3398
4383
  if (contact.includes("mail")) {
3399
- const address = await p19.text({
4384
+ const address = await p21.text({
3400
4385
  message: "What's the address?",
3401
4386
  placeholder: "767 Fifth Avenue New York, NY 10153, United States",
3402
4387
  validate: (value) => {
@@ -3405,7 +4390,7 @@ async function contactMethods(type) {
3405
4390
  }
3406
4391
  }
3407
4392
  });
3408
- if (p19.isCancel(address)) onCancel();
4393
+ if (p21.isCancel(address)) onCancel();
3409
4394
  details.address = address;
3410
4395
  }
3411
4396
  return { methods: contact, details };
@@ -3801,7 +4786,7 @@ var generateTermsHtml = (answers) => {
3801
4786
  };
3802
4787
  async function termsAction() {
3803
4788
  const { workspaceRootDir, publicRoutesDir } = await getWorkspace();
3804
- const core = await p20.group(
4789
+ const core = await p22.group(
3805
4790
  {
3806
4791
  websiteUrl: sharedFields.websiteUrl,
3807
4792
  websiteName: sharedFields.websiteName,
@@ -3813,25 +4798,25 @@ async function termsAction() {
3813
4798
  },
3814
4799
  { onCancel }
3815
4800
  );
3816
- const accounts = await p20.select({
4801
+ const accounts = await p22.select({
3817
4802
  message: "Can users create accounts?",
3818
4803
  options: [
3819
4804
  { value: "yes", label: "Yes, users can create accounts" },
3820
4805
  { value: "no", label: "No" }
3821
4806
  ]
3822
4807
  });
3823
- if (p20.isCancel(accounts)) onCancel();
3824
- const userContent = await p20.select({
4808
+ if (p22.isCancel(accounts)) onCancel();
4809
+ const userContent = await p22.select({
3825
4810
  message: "Can users create and/or upload content (ie. text, images)?",
3826
4811
  options: [
3827
4812
  { value: "yes", label: "Yes, users can create and/or upload content" },
3828
4813
  { value: "no", label: "No" }
3829
4814
  ]
3830
4815
  });
3831
- if (p20.isCancel(userContent)) onCancel();
4816
+ if (p22.isCancel(userContent)) onCancel();
3832
4817
  let infringementEmail;
3833
4818
  if (userContent === "yes") {
3834
- const email3 = await p20.text({
4819
+ const email3 = await p22.text({
3835
4820
  message: "What's the email address where you will receive infringements notices?",
3836
4821
  placeholder: "dmca@website.com",
3837
4822
  validate: (value) => {
@@ -3840,10 +4825,10 @@ async function termsAction() {
3840
4825
  }
3841
4826
  }
3842
4827
  });
3843
- if (p20.isCancel(email3)) onCancel();
4828
+ if (p22.isCancel(email3)) onCancel();
3844
4829
  infringementEmail = email3;
3845
4830
  }
3846
- const canBuyGoods = await p20.select({
4831
+ const canBuyGoods = await p22.select({
3847
4832
  message: "Can users buy goods (products, items)?",
3848
4833
  options: [
3849
4834
  {
@@ -3853,28 +4838,28 @@ async function termsAction() {
3853
4838
  { value: "no", label: "No" }
3854
4839
  ]
3855
4840
  });
3856
- if (p20.isCancel(canBuyGoods)) onCancel();
3857
- const subscriptions2 = await p20.select({
4841
+ if (p22.isCancel(canBuyGoods)) onCancel();
4842
+ const subscriptions2 = await p22.select({
3858
4843
  message: "Do you offer subscription plans?",
3859
4844
  options: [
3860
4845
  { value: "yes", label: "Yes, we offer subscription plans" },
3861
4846
  { value: "no", label: "No" }
3862
4847
  ]
3863
4848
  });
3864
- if (p20.isCancel(subscriptions2)) onCancel();
4849
+ if (p22.isCancel(subscriptions2)) onCancel();
3865
4850
  let freeTrial;
3866
4851
  if (subscriptions2 === "yes") {
3867
- const ft = await p20.select({
4852
+ const ft = await p22.select({
3868
4853
  message: "Do you offer a free trial?",
3869
4854
  options: [
3870
4855
  { value: "yes", label: "Yes" },
3871
4856
  { value: "no", label: "No" }
3872
4857
  ]
3873
4858
  });
3874
- if (p20.isCancel(ft)) onCancel();
4859
+ if (p22.isCancel(ft)) onCancel();
3875
4860
  freeTrial = ft;
3876
4861
  }
3877
- const exclusiveContent = await p20.select({
4862
+ const exclusiveContent = await p22.select({
3878
4863
  message: "Do you want to make it clear that your own content & trademarks are your exclusive property?",
3879
4864
  options: [
3880
4865
  {
@@ -3884,24 +4869,24 @@ async function termsAction() {
3884
4869
  { value: "no", label: "No" }
3885
4870
  ]
3886
4871
  });
3887
- if (p20.isCancel(exclusiveContent)) onCancel();
3888
- const feedbackReuse = await p20.select({
4872
+ if (p22.isCancel(exclusiveContent)) onCancel();
4873
+ const feedbackReuse = await p22.select({
3889
4874
  message: "If users provide you feedback & suggestions, do you want to use this feedback without compensation or credits given?",
3890
4875
  options: [
3891
4876
  { value: "yes", label: "Yes, we may implement any feedback or suggestions we receive" },
3892
4877
  { value: "no", label: "No" }
3893
4878
  ]
3894
4879
  });
3895
- if (p20.isCancel(feedbackReuse)) onCancel();
3896
- const promotions = await p20.select({
4880
+ if (p22.isCancel(feedbackReuse)) onCancel();
4881
+ const promotions = await p22.select({
3897
4882
  message: "Do you plan to offer promotions, contests, sweepstakes?",
3898
4883
  options: [
3899
4884
  { value: "yes", label: "Yes, we may offer promotions, contests, sweepstakes" },
3900
4885
  { value: "no", label: "No" }
3901
4886
  ]
3902
4887
  });
3903
- if (p20.isCancel(promotions)) onCancel();
3904
- const mobileAppRaw = await p20.multiselect({
4888
+ if (p22.isCancel(promotions)) onCancel();
4889
+ const mobileAppRaw = await p22.multiselect({
3905
4890
  message: "Is the Service distributed through any mobile app stores? Check all that apply",
3906
4891
  options: [
3907
4892
  { value: "apple", label: "Apple App Store" },
@@ -3909,7 +4894,7 @@ async function termsAction() {
3909
4894
  ],
3910
4895
  required: false
3911
4896
  });
3912
- if (p20.isCancel(mobileAppRaw)) onCancel();
4897
+ if (p22.isCancel(mobileAppRaw)) onCancel();
3913
4898
  const mobileApp = mobileAppRaw ?? [];
3914
4899
  const contact = await contactMethods("terms");
3915
4900
  const html = generateTermsHtml({
@@ -3926,22 +4911,22 @@ async function termsAction() {
3926
4911
  mobileApp,
3927
4912
  contact
3928
4913
  });
3929
- const termsPage = path18.join(
4914
+ const termsPage = path21.join(
3930
4915
  workspaceRootDir,
3931
4916
  publicRoutesDir,
3932
4917
  LEGAL_DIR,
3933
4918
  "terms",
3934
4919
  "+page.svelte"
3935
4920
  );
3936
- const termsPageTs = path18.join(workspaceRootDir, publicRoutesDir, LEGAL_DIR, "terms", "+page.ts");
3937
- fs18.mkdirSync(path18.dirname(termsPage), { recursive: true });
3938
- fs18.writeFileSync(termsPage, html);
3939
- fs18.writeFileSync(
4921
+ const termsPageTs = path21.join(workspaceRootDir, publicRoutesDir, LEGAL_DIR, "terms", "+page.ts");
4922
+ fs21.mkdirSync(path21.dirname(termsPage), { recursive: true });
4923
+ fs21.writeFileSync(termsPage, html);
4924
+ fs21.writeFileSync(
3940
4925
  termsPageTs,
3941
4926
  pageMetaTagsLoader("Terms of Service", `Terms of Service for ${core.websiteName}`)
3942
4927
  );
3943
- const relativeTermsPage = path18.relative(workspaceRootDir, termsPage);
3944
- const relativeTermsPageTs = path18.relative(workspaceRootDir, termsPageTs);
4928
+ const relativeTermsPage = path21.relative(workspaceRootDir, termsPage);
4929
+ const relativeTermsPageTs = path21.relative(workspaceRootDir, termsPageTs);
3945
4930
  reportResult({
3946
4931
  summary: "Generated placeholder terms and conditions.",
3947
4932
  filesCreated: [relativeTermsPage, relativeTermsPageTs],
@@ -3952,13 +4937,13 @@ async function termsAction() {
3952
4937
  ]
3953
4938
  });
3954
4939
  }
3955
- var terms = new Command42("terms").description("generate placeholder terms and conditions").configureHelp(helpConfig).action(() => runCommand(termsAction, "Failed to generate terms and conditions."));
4940
+ var terms = new Command43("terms").description("generate placeholder terms and conditions").configureHelp(helpConfig).action(() => runCommand(termsAction, "Failed to generate terms and conditions."));
3956
4941
 
3957
4942
  // src/commands/legal/privacy.ts
3958
- import fs19 from "node:fs";
3959
- import path19 from "node:path";
3960
- import { Command as Command43 } from "commander";
3961
- import * as p21 from "@clack/prompts";
4943
+ import fs22 from "node:fs";
4944
+ import path22 from "node:path";
4945
+ import { Command as Command44 } from "commander";
4946
+ import * as p23 from "@clack/prompts";
3962
4947
  var mapLabels = {
3963
4948
  personalInfo: {
3964
4949
  email: "Email address",
@@ -4045,9 +5030,9 @@ var compute2 = (a) => {
4045
5030
  });
4046
5031
  const piSelections = a.personalInfo ?? [];
4047
5032
  const piLabels = piSelections.map(
4048
- (v4) => mapLabels.personalInfo[v4] ?? v4
5033
+ (v9) => mapLabels.personalInfo[v9] ?? v9
4049
5034
  );
4050
- const lookup = (map, vs) => (vs ?? []).map((v4) => map[v4] ?? v4);
5035
+ const lookup = (map, vs) => (vs ?? []).map((v9) => map[v9] ?? v9);
4051
5036
  const companyName = a.core?.entityType === "business" ? a.core?.businessName ?? a.core?.websiteName : a.core?.websiteName ?? "Our Company";
4052
5037
  const companyAddress = a.core?.entityType === "business" ? a.core?.businessAddress ?? "" : "";
4053
5038
  const websiteName = a.core?.websiteName ?? "our website";
@@ -4241,7 +5226,7 @@ var sectionShare = (a, c) => {
4241
5226
  };
4242
5227
  var sectionSaleSharing = (a, c) => {
4243
5228
  const sells = a.showAds === "yes" || a.remarketing === "yes";
4244
- const intro = sells ? `<p>For purposes of certain U.S. state privacy laws (including the California Consumer Privacy Act, as amended by the California Privacy Rights Act, and the Virginia, Colorado, Connecticut, Utah, Texas, and Oregon consumer privacy laws), our use of cookies, pixels, and similar advertising and analytics technologies \u2014 including for cross-context behavioral advertising and targeted advertising \u2014 may be considered a "sale" or "share" of personal information. The categories of personal information involved typically include online identifiers, IP addresses, internet or other electronic network activity information, and inferences derived from this information.</p>` : '<p>We do not "sell" your personal information for monetary consideration. We do not "share" your personal information for cross-context behavioral advertising as those terms are defined under the California Privacy Rights Act ("CPRA") or comparable U.S. state privacy laws. We do not knowingly sell or share personal information of consumers under 16 years of age.</p>';
5229
+ const intro3 = sells ? `<p>For purposes of certain U.S. state privacy laws (including the California Consumer Privacy Act, as amended by the California Privacy Rights Act, and the Virginia, Colorado, Connecticut, Utah, Texas, and Oregon consumer privacy laws), our use of cookies, pixels, and similar advertising and analytics technologies \u2014 including for cross-context behavioral advertising and targeted advertising \u2014 may be considered a "sale" or "share" of personal information. The categories of personal information involved typically include online identifiers, IP addresses, internet or other electronic network activity information, and inferences derived from this information.</p>` : '<p>We do not "sell" your personal information for monetary consideration. We do not "share" your personal information for cross-context behavioral advertising as those terms are defined under the California Privacy Rights Act ("CPRA") or comparable U.S. state privacy laws. We do not knowingly sell or share personal information of consumers under 16 years of age.</p>';
4245
5230
  let recipients = "";
4246
5231
  if (sells) {
4247
5232
  const partners = [...c.adsPlatforms, ...c.remarketingPlatforms];
@@ -4253,7 +5238,7 @@ var sectionSaleSharing = (a, c) => {
4253
5238
  const optOut = `<p><strong>Your right to opt out.</strong> You have the right to opt out of the sale or sharing of your personal information at any time. To exercise this right, you may use the controls described in the "Tracking Technologies and Cookies" section, change the privacy settings in your browser or mobile device, contact us using the methods listed in the "Contact Us" section, or send an opt-out preference signal \u2014 we honor the Global Privacy Control ("GPC") signal as a request to opt out of sale and sharing for the browser or device on which the signal is detected.</p>`;
4254
5239
  return titledSection(
4255
5240
  "Sale or Sharing of Personal Information; Targeted Advertising",
4256
- `${intro}${recipients}${optOut}`
5241
+ `${intro3}${recipients}${optOut}`
4257
5242
  );
4258
5243
  };
4259
5244
  var sectionCookies = (a, c) => {
@@ -4292,8 +5277,8 @@ var sectionAutomated = () => titledSection(
4292
5277
  "<p>We do not use your Personal Information to make decisions based solely on automated processing \u2014 including profiling \u2014 that produce legal effects concerning you or similarly significantly affect you, except as may be necessary for entering into or performing a contract with you, where authorized by applicable law, or where you have given your explicit consent. If we ever do, we will provide meaningful information about the logic involved and the significance and envisaged consequences of such processing, and you may request human review of any such decision.</p>"
4293
5278
  );
4294
5279
  var sectionRights = (a, c) => {
4295
- const intro = '<p>This section describes your privacy rights under applicable laws and how to exercise them. Even if a particular section does not apply to you, you may always contact us using the methods in the "Contact Us" section to ask about your privacy choices.</p>';
4296
- let body = intro;
5280
+ const intro3 = '<p>This section describes your privacy rights under applicable laws and how to exercise them. Even if a particular section does not apply to you, you may always contact us using the methods in the "Contact Us" section to ask about your privacy choices.</p>';
5281
+ let body = intro3;
4297
5282
  if (a.usStates === "yes") {
4298
5283
  body += subSection("U.S. State Privacy Rights", usStateRightsBlock(c.companyName));
4299
5284
  }
@@ -4376,12 +5361,12 @@ var generatePrivacyHtml = (answers) => {
4376
5361
  return `<section data-role="content">${sections.join("")}</section>`;
4377
5362
  };
4378
5363
  async function promptWithCustom(message, options, customMessage) {
4379
- const selection = await p21.multiselect({ message, options });
4380
- if (p21.isCancel(selection)) onCancel();
5364
+ const selection = await p23.multiselect({ message, options });
5365
+ if (p23.isCancel(selection)) onCancel();
4381
5366
  const values = selection;
4382
5367
  if (values.includes("custom")) {
4383
- const custom = await p21.text({ message: customMessage });
4384
- if (p21.isCancel(custom)) onCancel();
5368
+ const custom = await p23.text({ message: customMessage });
5369
+ if (p23.isCancel(custom)) onCancel();
4385
5370
  const idx = values.indexOf("custom");
4386
5371
  if (idx !== -1) values.splice(idx, 1);
4387
5372
  if (custom) values.push(String(custom));
@@ -4390,7 +5375,7 @@ async function promptWithCustom(message, options, customMessage) {
4390
5375
  }
4391
5376
  async function privacyAction() {
4392
5377
  const { workspaceRootDir, publicRoutesDir } = await getWorkspace();
4393
- const core = await p21.group(
5378
+ const core = await p23.group(
4394
5379
  {
4395
5380
  websiteUrl: sharedFields.websiteUrl,
4396
5381
  websiteName: sharedFields.websiteName,
@@ -4402,7 +5387,7 @@ async function privacyAction() {
4402
5387
  },
4403
5388
  { onCancel }
4404
5389
  );
4405
- const personalInfo = await p21.multiselect({
5390
+ const personalInfo = await p23.multiselect({
4406
5391
  message: "What kind of personal information do you collect from users? Check all that apply",
4407
5392
  options: [
4408
5393
  { value: "email", label: "Email address" },
@@ -4417,16 +5402,16 @@ async function privacyAction() {
4417
5402
  ],
4418
5403
  required: false
4419
5404
  });
4420
- if (p21.isCancel(personalInfo)) onCancel();
5405
+ if (p23.isCancel(personalInfo)) onCancel();
4421
5406
  const contact = await contactMethods("privacy");
4422
- const tracking = await p21.select({
5407
+ const tracking = await p23.select({
4423
5408
  message: "Do you use tracking and/or analytics tools, such as Google Analytics?",
4424
5409
  options: [
4425
5410
  { value: "yes", label: "Yes, we use Google Analytics or other related tools" },
4426
5411
  { value: "no", label: "No" }
4427
5412
  ]
4428
5413
  });
4429
- if (p21.isCancel(tracking)) onCancel();
5414
+ if (p23.isCancel(tracking)) onCancel();
4430
5415
  let trackingTools;
4431
5416
  if (tracking === "yes") {
4432
5417
  trackingTools = await promptWithCustom(
@@ -4445,7 +5430,7 @@ async function privacyAction() {
4445
5430
  "Enter your custom tracking/analytics tool name"
4446
5431
  );
4447
5432
  }
4448
- const sendEmails = await p21.select({
5433
+ const sendEmails = await p23.select({
4449
5434
  message: "Do you send emails to users?",
4450
5435
  options: [
4451
5436
  {
@@ -4455,7 +5440,7 @@ async function privacyAction() {
4455
5440
  { value: "no", label: "No" }
4456
5441
  ]
4457
5442
  });
4458
- if (p21.isCancel(sendEmails)) onCancel();
5443
+ if (p23.isCancel(sendEmails)) onCancel();
4459
5444
  let emailPlatforms;
4460
5445
  if (sendEmails === "yes") {
4461
5446
  emailPlatforms = await promptWithCustom(
@@ -4470,14 +5455,14 @@ async function privacyAction() {
4470
5455
  "Enter your custom email platform"
4471
5456
  );
4472
5457
  }
4473
- const showAds = await p21.select({
5458
+ const showAds = await p23.select({
4474
5459
  message: "Do you show ads?",
4475
5460
  options: [
4476
5461
  { value: "yes", label: "Yes, we show ads" },
4477
5462
  { value: "no", label: "No" }
4478
5463
  ]
4479
5464
  });
4480
- if (p21.isCancel(showAds)) onCancel();
5465
+ if (p23.isCancel(showAds)) onCancel();
4481
5466
  let adsPlatforms;
4482
5467
  if (showAds === "yes") {
4483
5468
  adsPlatforms = await promptWithCustom(
@@ -4500,7 +5485,7 @@ async function privacyAction() {
4500
5485
  "Enter your custom ads platform"
4501
5486
  );
4502
5487
  }
4503
- const canPay = await p21.select({
5488
+ const canPay = await p23.select({
4504
5489
  message: "Can users pay for products or services?",
4505
5490
  options: [
4506
5491
  { value: "yes", label: "Yes, users can pay for our products/services" },
@@ -4510,7 +5495,7 @@ async function privacyAction() {
4510
5495
  }
4511
5496
  ]
4512
5497
  });
4513
- if (p21.isCancel(canPay)) onCancel();
5498
+ if (p23.isCancel(canPay)) onCancel();
4514
5499
  let paymentProcessors;
4515
5500
  if (canPay === "yes") {
4516
5501
  paymentProcessors = await promptWithCustom(
@@ -4541,14 +5526,14 @@ async function privacyAction() {
4541
5526
  "Enter your custom payment processor/method"
4542
5527
  );
4543
5528
  }
4544
- const remarketing = await p21.select({
5529
+ const remarketing = await p23.select({
4545
5530
  message: "Do you use remarketing services for marketing & advertising purposes?",
4546
5531
  options: [
4547
5532
  { value: "yes", label: "Yes, we use remarketing services to advertise our business" },
4548
5533
  { value: "no", label: "No" }
4549
5534
  ]
4550
5535
  });
4551
- if (p21.isCancel(remarketing)) onCancel();
5536
+ if (p23.isCancel(remarketing)) onCancel();
4552
5537
  let remarketingPlatforms;
4553
5538
  if (remarketing === "yes") {
4554
5539
  remarketingPlatforms = await promptWithCustom(
@@ -4567,7 +5552,7 @@ async function privacyAction() {
4567
5552
  "Enter your custom remarketing platform"
4568
5553
  );
4569
5554
  }
4570
- const providersRaw = await p21.multiselect({
5555
+ const providersRaw = await p23.multiselect({
4571
5556
  message: "Select if you use any of the following providers",
4572
5557
  options: [
4573
5558
  { value: "recaptcha", label: "Invisible reCAPTCHA" },
@@ -4578,16 +5563,16 @@ async function privacyAction() {
4578
5563
  ],
4579
5564
  required: false
4580
5565
  });
4581
- if (p21.isCancel(providersRaw)) onCancel();
5566
+ if (p23.isCancel(providersRaw)) onCancel();
4582
5567
  const providers = providersRaw;
4583
5568
  if (providers.includes("custom")) {
4584
- const custom = await p21.text({ message: "Enter your custom provider" });
4585
- if (p21.isCancel(custom)) onCancel();
5569
+ const custom = await p23.text({ message: "Enter your custom provider" });
5570
+ if (p23.isCancel(custom)) onCancel();
4586
5571
  const idx = providers.indexOf("custom");
4587
5572
  if (idx !== -1) providers.splice(idx, 1);
4588
5573
  if (custom) providers.push(String(custom));
4589
5574
  }
4590
- const usStates = await p21.select({
5575
+ const usStates = await p23.select({
4591
5576
  message: "Include U.S. state privacy rights (CCPA/CPRA, VCDPA, CPA, CTDPA, UCPA, TX, OR, etc.)?",
4592
5577
  options: [
4593
5578
  {
@@ -4598,55 +5583,55 @@ async function privacyAction() {
4598
5583
  ],
4599
5584
  initialValue: "yes"
4600
5585
  });
4601
- if (p21.isCancel(usStates)) onCancel();
4602
- const gdpr = await p21.select({
5586
+ if (p23.isCancel(usStates)) onCancel();
5587
+ const gdpr = await p23.select({
4603
5588
  message: "Do you want your Privacy Policy to include GDPR / UK GDPR wording?",
4604
5589
  options: [
4605
5590
  { value: "yes", label: "Yes. Include GDPR rights for EEA, UK, and Swiss residents" },
4606
5591
  { value: "no", label: "No" }
4607
5592
  ]
4608
5593
  });
4609
- if (p21.isCancel(gdpr)) onCancel();
5594
+ if (p23.isCancel(gdpr)) onCancel();
4610
5595
  let facebookFanPage = "no";
4611
5596
  const facebookDetails = { name: "", url: "" };
4612
5597
  if (gdpr === "yes") {
4613
- const fan = await p21.select({
5598
+ const fan = await p23.select({
4614
5599
  message: "Do you have a Facebook Fan Page?",
4615
5600
  options: [
4616
5601
  { value: "yes", label: "Yes, we have a Facebook Fan Page" },
4617
5602
  { value: "no", label: "No" }
4618
5603
  ]
4619
5604
  });
4620
- if (p21.isCancel(fan)) onCancel();
5605
+ if (p23.isCancel(fan)) onCancel();
4621
5606
  facebookFanPage = fan;
4622
5607
  if (facebookFanPage === "yes") {
4623
- const name = await p21.text({
5608
+ const name = await p23.text({
4624
5609
  message: "What is the name of the Facebook Fan Page?",
4625
5610
  placeholder: "My Facebook Page"
4626
5611
  });
4627
- if (p21.isCancel(name)) onCancel();
5612
+ if (p23.isCancel(name)) onCancel();
4628
5613
  facebookDetails.name = name;
4629
- const url = await p21.text({
5614
+ const url = await p23.text({
4630
5615
  message: "What is the URL of the Facebook Fan Page?",
4631
5616
  placeholder: "https://facebook.com/my-facebook-page"
4632
5617
  });
4633
- if (p21.isCancel(url)) onCancel();
5618
+ if (p23.isCancel(url)) onCancel();
4634
5619
  facebookDetails.url = url;
4635
5620
  }
4636
5621
  }
4637
- const kids = await p21.select({
5622
+ const kids = await p23.select({
4638
5623
  message: "Do you collect information from kids under the age of 13?",
4639
5624
  options: [
4640
5625
  { value: "yes", label: "Yes. We collect information from children under the age of 13" },
4641
5626
  { value: "no", label: "No" }
4642
5627
  ]
4643
5628
  });
4644
- if (p21.isCancel(kids)) onCancel();
4645
- const retention = await p21.text({
5629
+ if (p23.isCancel(kids)) onCancel();
5630
+ const retention = await p23.text({
4646
5631
  message: "How long do you retain personal information? (leave blank for default wording)",
4647
5632
  placeholder: "e.g. 12 months after account closure"
4648
5633
  });
4649
- if (p21.isCancel(retention)) onCancel();
5634
+ if (p23.isCancel(retention)) onCancel();
4650
5635
  const html = generatePrivacyHtml({
4651
5636
  core,
4652
5637
  personalInfo: personalInfo ?? [],
@@ -4669,28 +5654,28 @@ async function privacyAction() {
4669
5654
  kids,
4670
5655
  retention
4671
5656
  });
4672
- const privacyPage = path19.join(
5657
+ const privacyPage = path22.join(
4673
5658
  workspaceRootDir,
4674
5659
  publicRoutesDir,
4675
5660
  LEGAL_DIR,
4676
5661
  "privacy",
4677
5662
  "+page.svelte"
4678
5663
  );
4679
- const privacyPageTs = path19.join(
5664
+ const privacyPageTs = path22.join(
4680
5665
  workspaceRootDir,
4681
5666
  publicRoutesDir,
4682
5667
  LEGAL_DIR,
4683
5668
  "privacy",
4684
5669
  "+page.ts"
4685
5670
  );
4686
- fs19.mkdirSync(path19.dirname(privacyPage), { recursive: true });
4687
- fs19.writeFileSync(privacyPage, html);
4688
- fs19.writeFileSync(
5671
+ fs22.mkdirSync(path22.dirname(privacyPage), { recursive: true });
5672
+ fs22.writeFileSync(privacyPage, html);
5673
+ fs22.writeFileSync(
4689
5674
  privacyPageTs,
4690
5675
  pageMetaTagsLoader("Privacy Policy", `Privacy Policy for ${core.websiteName}`)
4691
5676
  );
4692
- const relativePrivacyPage = path19.relative(workspaceRootDir, privacyPage);
4693
- const relativePrivacyPageTs = path19.relative(workspaceRootDir, privacyPageTs);
5677
+ const relativePrivacyPage = path22.relative(workspaceRootDir, privacyPage);
5678
+ const relativePrivacyPageTs = path22.relative(workspaceRootDir, privacyPageTs);
4694
5679
  reportResult({
4695
5680
  summary: "Generated placeholder privacy policy.",
4696
5681
  filesCreated: [relativePrivacyPage, relativePrivacyPageTs],
@@ -4701,23 +5686,23 @@ async function privacyAction() {
4701
5686
  ]
4702
5687
  });
4703
5688
  }
4704
- var privacy = new Command43("privacy").description("generate placeholder privacy policy").configureHelp(helpConfig).action(() => runCommand(privacyAction, "Failed to generate privacy policy."));
5689
+ var privacy = new Command44("privacy").description("generate placeholder privacy policy").configureHelp(helpConfig).action(() => runCommand(privacyAction, "Failed to generate privacy policy."));
4705
5690
 
4706
5691
  // src/commands/legal.ts
4707
- var legal = new Command44("legal").description("generate placeholder legal documents").configureHelp(helpConfig).addCommand(terms).addCommand(privacy);
5692
+ var legal = new Command45("legal").description("generate placeholder legal documents").configureHelp(helpConfig).addCommand(terms).addCommand(privacy);
4708
5693
 
4709
5694
  // src/commands/fixtures.ts
4710
- import { Command as Command50 } from "commander";
5695
+ import { Command as Command51 } from "commander";
4711
5696
 
4712
5697
  // src/commands/fixtures/load.ts
4713
- import fs20 from "node:fs";
4714
- import path20 from "node:path";
4715
- import { Command as Command45 } from "commander";
5698
+ import fs23 from "node:fs";
5699
+ import path23 from "node:path";
5700
+ import { Command as Command46 } from "commander";
4716
5701
  function getFixtureFiles(cwd) {
4717
- const fixturesDir = path20.join(cwd, "data", "fixtures");
4718
- if (!fs20.existsSync(fixturesDir)) return [];
4719
- return fs20.readdirSync(fixturesDir).filter((file) => file.endsWith(".json")).map((file) => path20.join(fixturesDir, file)).sort((a, b) => a.localeCompare(b)).map((fixturePath) => {
4720
- const file = path20.basename(fixturePath);
5702
+ const fixturesDir = path23.join(cwd, "data", "fixtures");
5703
+ if (!fs23.existsSync(fixturesDir)) return [];
5704
+ return fs23.readdirSync(fixturesDir).filter((file) => file.endsWith(".json")).map((file) => path23.join(fixturesDir, file)).sort((a, b) => a.localeCompare(b)).map((fixturePath) => {
5705
+ const file = path23.basename(fixturePath);
4721
5706
  const nameWithoutExt = file.replace(/\.json$/i, "");
4722
5707
  const collectionName = nameWithoutExt.replace(/^\d+[-_]?/, "");
4723
5708
  return { collectionName, fixturePath };
@@ -4738,15 +5723,15 @@ async function loadFixtures(pb, workspaceRootDir) {
4738
5723
  `${collectionName} collection already has fixture records. Run \`vela fixtures reset\` to clear and reload, or \`vela fixtures clear\` first.`
4739
5724
  );
4740
5725
  }
4741
- const items = JSON.parse(fs20.readFileSync(fixturePath, "utf8"));
5726
+ const items = JSON.parse(fs23.readFileSync(fixturePath, "utf8"));
4742
5727
  for (const fixture of items) {
4743
5728
  await pb.collection(collectionName).create(fixture);
4744
5729
  }
4745
- loaded.push(`${path20.relative(workspaceRootDir, fixturePath)} (${items.length} records)`);
5730
+ loaded.push(`${path23.relative(workspaceRootDir, fixturePath)} (${items.length} records)`);
4746
5731
  }
4747
5732
  return loaded;
4748
5733
  }
4749
- var load = new Command45("load").description("load fixtures into the database").configureHelp(helpConfig).action(
5734
+ var load = new Command46("load").description("load fixtures into the database").configureHelp(helpConfig).action(
4750
5735
  () => runCommand(async () => {
4751
5736
  const { workspaceRootDir } = await getWorkspace();
4752
5737
  let loaded = [];
@@ -4772,8 +5757,8 @@ var load = new Command45("load").description("load fixtures into the database").
4772
5757
  );
4773
5758
 
4774
5759
  // src/commands/fixtures/clear.ts
4775
- import path21 from "node:path";
4776
- import { Command as Command46 } from "commander";
5760
+ import path24 from "node:path";
5761
+ import { Command as Command47 } from "commander";
4777
5762
 
4778
5763
  // src/lib/collections.ts
4779
5764
  function dependencyOrder(collections2, startingCollectionId) {
@@ -4835,12 +5820,12 @@ async function clearFixtures(pb, workspaceRootDir) {
4835
5820
  await pb.collection(collectionName).delete(id);
4836
5821
  }
4837
5822
  const fixturePath = fixturePathByName.get(collectionName);
4838
- const label = fixturePath ? path21.relative(workspaceRootDir, fixturePath) : collectionName;
5823
+ const label = fixturePath ? path24.relative(workspaceRootDir, fixturePath) : collectionName;
4839
5824
  cleared.push(`${label} (${records.length} records)`);
4840
5825
  }
4841
5826
  return cleared;
4842
5827
  }
4843
- var clear = new Command46("clear").description("clear loaded fixtures").configureHelp(helpConfig).action(
5828
+ var clear = new Command47("clear").description("clear loaded fixtures").configureHelp(helpConfig).action(
4844
5829
  () => runCommand(async () => {
4845
5830
  const { workspaceRootDir } = await getWorkspace();
4846
5831
  let cleared = [];
@@ -4869,8 +5854,8 @@ var clear = new Command46("clear").description("clear loaded fixtures").configur
4869
5854
  );
4870
5855
 
4871
5856
  // src/commands/fixtures/reset.ts
4872
- import { Command as Command47 } from "commander";
4873
- var reset = new Command47("reset").description("clear and reload fixtures").configureHelp(helpConfig).action(
5857
+ import { Command as Command48 } from "commander";
5858
+ var reset = new Command48("reset").description("clear and reload fixtures").configureHelp(helpConfig).action(
4874
5859
  () => runCommand(async () => {
4875
5860
  const { workspaceRootDir } = await getWorkspace();
4876
5861
  let cleared = [];
@@ -4900,10 +5885,10 @@ var reset = new Command47("reset").description("clear and reload fixtures").conf
4900
5885
  );
4901
5886
 
4902
5887
  // src/commands/fixtures/generate.ts
4903
- import fs21 from "node:fs";
4904
- import path22 from "node:path";
4905
- import { Command as Command48, InvalidArgumentError as InvalidArgumentError2 } from "commander";
4906
- import * as p22 from "@clack/prompts";
5888
+ import fs24 from "node:fs";
5889
+ import path25 from "node:path";
5890
+ import { Command as Command49, InvalidArgumentError as InvalidArgumentError2 } from "commander";
5891
+ import * as p24 from "@clack/prompts";
4907
5892
  import { annotate } from "annotate-json-schema";
4908
5893
  import { createGenerator } from "json-schema-faker";
4909
5894
  import { faker } from "@faker-js/faker";
@@ -4950,10 +5935,10 @@ async function loadCollections(pb) {
4950
5935
  }));
4951
5936
  }
4952
5937
  async function generateFixtureFiles(pb, workspaceRootDir, opts) {
4953
- const fixturesDir = path22.join(workspaceRootDir, "data", "fixtures");
4954
- fs21.mkdirSync(fixturesDir, { recursive: true });
4955
- for (const file of fs21.readdirSync(fixturesDir)) {
4956
- if (file.endsWith(".json")) fs21.unlinkSync(path22.join(fixturesDir, file));
5938
+ const fixturesDir = path25.join(workspaceRootDir, "data", "fixtures");
5939
+ fs24.mkdirSync(fixturesDir, { recursive: true });
5940
+ for (const file of fs24.readdirSync(fixturesDir)) {
5941
+ if (file.endsWith(".json")) fs24.unlinkSync(path25.join(fixturesDir, file));
4957
5942
  }
4958
5943
  if (opts.seed !== void 0) faker.seed(opts.seed);
4959
5944
  const generator = createGenerator({
@@ -5017,13 +6002,13 @@ async function generateFixtureFiles(pb, workspaceRootDir, opts) {
5017
6002
  items.push(record);
5018
6003
  }
5019
6004
  const filename = `${padZeros(fileIndex, 2)}-${collection.name}.json`;
5020
- fs21.writeFileSync(path22.join(fixturesDir, filename), JSON.stringify(items, null, 2));
5021
- writtenFiles.push(`${path22.join("data", "fixtures", filename)} (${items.length} records)`);
6005
+ fs24.writeFileSync(path25.join(fixturesDir, filename), JSON.stringify(items, null, 2));
6006
+ writtenFiles.push(`${path25.join("data", "fixtures", filename)} (${items.length} records)`);
5022
6007
  fileIndex++;
5023
6008
  }
5024
6009
  return { writtenFiles, warnings };
5025
6010
  }
5026
- var generate2 = new Command48("generate").description("generate fixture data").option("-c, --count <count>", "number of records per collection", parseCount, 10).option("-s, --seed <seed>", "seed for deterministic output", parseSeed).option("-f, --force", "overwrite existing fixture files and clear loaded fixtures").configureHelp(helpConfig).action(
6011
+ var generate2 = new Command49("generate").description("generate fixture data").option("-c, --count <count>", "number of records per collection", parseCount, 10).option("-s, --seed <seed>", "seed for deterministic output", parseSeed).option("-f, --force", "overwrite existing fixture files and clear loaded fixtures").configureHelp(helpConfig).action(
5027
6012
  (opts) => runCommand(async () => {
5028
6013
  const { workspaceRootDir } = await getWorkspace();
5029
6014
  const existing = getFixtureFiles(workspaceRootDir);
@@ -5052,7 +6037,7 @@ var generate2 = new Command48("generate").description("generate fixture data").o
5052
6037
  seed: opts.seed
5053
6038
  });
5054
6039
  });
5055
- for (const warning of result.warnings) p22.log.warn(warning);
6040
+ for (const warning of result.warnings) p24.log.warn(warning);
5056
6041
  if (result.writtenFiles.length === 0) {
5057
6042
  reportResult({
5058
6043
  summary: "No eligible collections found to generate fixtures for.",
@@ -5076,8 +6061,8 @@ var generate2 = new Command48("generate").description("generate fixture data").o
5076
6061
  );
5077
6062
 
5078
6063
  // src/commands/fixtures/regen.ts
5079
- import { Command as Command49, InvalidArgumentError as InvalidArgumentError3 } from "commander";
5080
- import * as p23 from "@clack/prompts";
6064
+ import { Command as Command50, InvalidArgumentError as InvalidArgumentError3 } from "commander";
6065
+ import * as p25 from "@clack/prompts";
5081
6066
  function parseCount2(value) {
5082
6067
  const n = parseInt(value, 10);
5083
6068
  if (!Number.isFinite(n) || n <= 0 || n > 999) {
@@ -5092,7 +6077,7 @@ function parseSeed2(value) {
5092
6077
  }
5093
6078
  return n;
5094
6079
  }
5095
- var regen = new Command49("regen").description("clear the database, regenerate fixture files, and reload them").option("-c, --count <count>", "number of records per collection", parseCount2, 10).option("-s, --seed <seed>", "seed for deterministic output", parseSeed2).configureHelp(helpConfig).action(
6080
+ var regen = new Command50("regen").description("clear the database, regenerate fixture files, and reload them").option("-c, --count <count>", "number of records per collection", parseCount2, 10).option("-s, --seed <seed>", "seed for deterministic output", parseSeed2).configureHelp(helpConfig).action(
5096
6081
  (opts) => runCommand(async () => {
5097
6082
  const { workspaceRootDir } = await getWorkspace();
5098
6083
  let cleared = [];
@@ -5106,7 +6091,7 @@ var regen = new Command49("regen").description("clear the database, regenerate f
5106
6091
  });
5107
6092
  loaded = await loadFixtures(pb, workspaceRootDir);
5108
6093
  });
5109
- for (const warning of result.warnings) p23.log.warn(warning);
6094
+ for (const warning of result.warnings) p25.log.warn(warning);
5110
6095
  reportResult({
5111
6096
  summary: `Regenerated fixtures (${loaded.length} collection(s) reloaded).`,
5112
6097
  filesCreated: result.writtenFiles,
@@ -5121,26 +6106,26 @@ var regen = new Command49("regen").description("clear the database, regenerate f
5121
6106
  );
5122
6107
 
5123
6108
  // src/commands/fixtures.ts
5124
- var fixtures = new Command50("fixtures").description("manage fixture data").configureHelp(helpConfig).addCommand(generate2).addCommand(load).addCommand(clear).addCommand(reset).addCommand(regen);
6109
+ var fixtures = new Command51("fixtures").description("manage fixture data").configureHelp(helpConfig).addCommand(generate2).addCommand(load).addCommand(clear).addCommand(reset).addCommand(regen);
5125
6110
 
5126
6111
  // src/commands/seeds.ts
5127
- import { Command as Command54 } from "commander";
6112
+ import { Command as Command55 } from "commander";
5128
6113
 
5129
6114
  // src/commands/seeds/load.ts
5130
- import fs22 from "node:fs";
5131
- import path23 from "node:path";
5132
- import { Command as Command51 } from "commander";
6115
+ import fs25 from "node:fs";
6116
+ import path26 from "node:path";
6117
+ import { Command as Command52 } from "commander";
5133
6118
  function getSeedFiles(cwd) {
5134
- const seedsDir = path23.join(cwd, "data", "seeds");
5135
- if (!fs22.existsSync(seedsDir)) return [];
5136
- return fs22.readdirSync(seedsDir).filter((file) => file.endsWith(".json")).map((file) => path23.join(seedsDir, file)).sort((a, b) => a.localeCompare(b)).map((seedPath) => {
5137
- const file = path23.basename(seedPath);
6119
+ const seedsDir = path26.join(cwd, "data", "seeds");
6120
+ if (!fs25.existsSync(seedsDir)) return [];
6121
+ return fs25.readdirSync(seedsDir).filter((file) => file.endsWith(".json")).map((file) => path26.join(seedsDir, file)).sort((a, b) => a.localeCompare(b)).map((seedPath) => {
6122
+ const file = path26.basename(seedPath);
5138
6123
  const nameWithoutExt = file.replace(/\.json$/i, "");
5139
6124
  const collectionName = nameWithoutExt.replace(/^\d+[-_]?/, "");
5140
6125
  return { collectionName, seedPath };
5141
6126
  });
5142
6127
  }
5143
- var load2 = new Command51("load").description("load seeds into the database").option("-f, --force", "load even if target collections already have records").configureHelp(helpConfig).action(
6128
+ var load2 = new Command52("load").description("load seeds into the database").option("-f, --force", "load even if target collections already have records").configureHelp(helpConfig).action(
5144
6129
  (opts) => runCommand(async () => {
5145
6130
  const { workspaceRootDir } = await getWorkspace();
5146
6131
  const seedFiles = getSeedFiles(workspaceRootDir);
@@ -5164,11 +6149,11 @@ var load2 = new Command51("load").description("load seeds into the database").op
5164
6149
  }
5165
6150
  }
5166
6151
  for (const { collectionName, seedPath } of seedFiles) {
5167
- const seeds2 = JSON.parse(fs22.readFileSync(seedPath, "utf8"));
6152
+ const seeds2 = JSON.parse(fs25.readFileSync(seedPath, "utf8"));
5168
6153
  for (const seed of seeds2) {
5169
6154
  await pb.collection(collectionName).create(seed);
5170
6155
  }
5171
- loaded.push(`${path23.relative(workspaceRootDir, seedPath)} (${seeds2.length} records)`);
6156
+ loaded.push(`${path26.relative(workspaceRootDir, seedPath)} (${seeds2.length} records)`);
5172
6157
  }
5173
6158
  });
5174
6159
  reportResult({
@@ -5183,9 +6168,9 @@ var load2 = new Command51("load").description("load seeds into the database").op
5183
6168
  );
5184
6169
 
5185
6170
  // src/commands/seeds/save.ts
5186
- import fs23 from "node:fs";
5187
- import path24 from "node:path";
5188
- import { Command as Command52 } from "commander";
6171
+ import fs26 from "node:fs";
6172
+ import path27 from "node:path";
6173
+ import { Command as Command53 } from "commander";
5189
6174
  var padZeros2 = (num, length) => num.toString().padStart(length, "0");
5190
6175
  function filterSystemFields(record, systemFieldNames) {
5191
6176
  const out = {};
@@ -5196,18 +6181,18 @@ function filterSystemFields(record, systemFieldNames) {
5196
6181
  }
5197
6182
  return out;
5198
6183
  }
5199
- var save = new Command52("save").description("save the current data as seeds").option("-f, --force", "overwrite existing seed files").configureHelp(helpConfig).action(
6184
+ var save = new Command53("save").description("save the current data as seeds").option("-f, --force", "overwrite existing seed files").configureHelp(helpConfig).action(
5200
6185
  (opts) => runCommand(async () => {
5201
6186
  const { workspaceRootDir } = await getWorkspace();
5202
- const seedsPath = path24.join(workspaceRootDir, "data", "seeds");
6187
+ const seedsPath = path27.join(workspaceRootDir, "data", "seeds");
5203
6188
  const existing = getSeedFiles(workspaceRootDir);
5204
6189
  if (existing.length > 0 && !opts.force) {
5205
6190
  throw new Error("Existing seed files found in data/seeds. Pass --force to overwrite.");
5206
6191
  }
5207
- fs23.mkdirSync(seedsPath, { recursive: true });
6192
+ fs26.mkdirSync(seedsPath, { recursive: true });
5208
6193
  if (opts.force) {
5209
- for (const file of fs23.readdirSync(seedsPath)) {
5210
- if (file.endsWith(".json")) fs23.unlinkSync(path24.join(seedsPath, file));
6194
+ for (const file of fs26.readdirSync(seedsPath)) {
6195
+ if (file.endsWith(".json")) fs26.unlinkSync(path27.join(seedsPath, file));
5211
6196
  }
5212
6197
  }
5213
6198
  const saved = [];
@@ -5236,13 +6221,13 @@ var save = new Command52("save").description("save the current data as seeds").o
5236
6221
  const filtered = records.map(
5237
6222
  (r) => filterSystemFields(r, systemFieldNames)
5238
6223
  );
5239
- const relativeSeedPath = path24.join(
6224
+ const relativeSeedPath = path27.join(
5240
6225
  "data",
5241
6226
  "seeds",
5242
6227
  `${padZeros2(count, 2)}-${collectionName}.json`
5243
6228
  );
5244
- const seedPath = path24.join(workspaceRootDir, relativeSeedPath);
5245
- fs23.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
6229
+ const seedPath = path27.join(workspaceRootDir, relativeSeedPath);
6230
+ fs26.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
5246
6231
  saved.push(`${relativeSeedPath} (${filtered.length} records)`);
5247
6232
  count++;
5248
6233
  }
@@ -5268,21 +6253,21 @@ var save = new Command52("save").description("save the current data as seeds").o
5268
6253
  );
5269
6254
 
5270
6255
  // src/commands/seeds/clear.ts
5271
- import fs24 from "node:fs";
5272
- import path25 from "node:path";
5273
- import { Command as Command53 } from "commander";
6256
+ import fs27 from "node:fs";
6257
+ import path28 from "node:path";
6258
+ import { Command as Command54 } from "commander";
5274
6259
  async function clearSeeds(pb, workspaceRootDir, seedFiles) {
5275
6260
  const cleared = [];
5276
6261
  for (const { collectionName, seedPath } of seedFiles) {
5277
- const seeds2 = JSON.parse(fs24.readFileSync(seedPath, "utf8"));
6262
+ const seeds2 = JSON.parse(fs27.readFileSync(seedPath, "utf8"));
5278
6263
  for (const { id } of seeds2) {
5279
6264
  await pb.collection(collectionName).delete(id);
5280
6265
  }
5281
- cleared.push(`${path25.relative(workspaceRootDir, seedPath)} (${seeds2.length} records)`);
6266
+ cleared.push(`${path28.relative(workspaceRootDir, seedPath)} (${seeds2.length} records)`);
5282
6267
  }
5283
6268
  return cleared;
5284
6269
  }
5285
- var clear2 = new Command53("clear").description("clear seeded records").configureHelp(helpConfig).action(
6270
+ var clear2 = new Command54("clear").description("clear seeded records").configureHelp(helpConfig).action(
5286
6271
  () => runCommand(async () => {
5287
6272
  const { workspaceRootDir } = await getWorkspace();
5288
6273
  const seedFiles = getSeedFiles(workspaceRootDir);
@@ -5313,23 +6298,23 @@ var clear2 = new Command53("clear").description("clear seeded records").configur
5313
6298
  );
5314
6299
 
5315
6300
  // src/commands/seeds.ts
5316
- var seeds = new Command54("seeds").description("manage seed data").configureHelp(helpConfig).addCommand(load2).addCommand(save).addCommand(clear2);
6301
+ var seeds = new Command55("seeds").description("manage seed data").configureHelp(helpConfig).addCommand(load2).addCommand(save).addCommand(clear2);
5317
6302
 
5318
6303
  // src/commands/signup.ts
5319
- import { Command as Command56 } from "commander";
5320
- import * as p25 from "@clack/prompts";
6304
+ import { Command as Command57 } from "commander";
6305
+ import * as p27 from "@clack/prompts";
5321
6306
  import makeFetchCookie2 from "fetch-cookie";
5322
6307
 
5323
6308
  // src/commands/login.ts
5324
- import os2 from "node:os";
5325
- import { Command as Command55 } from "commander";
5326
- import * as p24 from "@clack/prompts";
6309
+ import os3 from "node:os";
6310
+ import { Command as Command56 } from "commander";
6311
+ import * as p26 from "@clack/prompts";
5327
6312
  import makeFetchCookie from "fetch-cookie";
5328
- var login = new Command55("login").description("login to velastack.dev").configureHelp(helpConfig).action(
6313
+ var login = new Command56("login").description("login to velastack.dev").configureHelp(helpConfig).action(
5329
6314
  () => runCommand(async () => {
5330
- const { email: email3, password: password8 } = await p24.group({
5331
- email: () => p24.text({ message: "Email" }),
5332
- password: () => p24.password({ message: "Password" })
6315
+ const { email: email3, password: password10 } = await p26.group({
6316
+ email: () => p26.text({ message: "Email" }),
6317
+ password: () => p26.password({ message: "Password" })
5333
6318
  });
5334
6319
  const fetchCookie = makeFetchCookie(fetch);
5335
6320
  const loginRes = await fetchCookie(`${API_URL}/login`, {
@@ -5341,7 +6326,7 @@ var login = new Command55("login").description("login to velastack.dev").configu
5341
6326
  body: new URLSearchParams({
5342
6327
  type: "password",
5343
6328
  email: email3,
5344
- password: password8
6329
+ password: password10
5345
6330
  }).toString()
5346
6331
  });
5347
6332
  if (!loginRes.headers.get("Set-Cookie")) {
@@ -5351,11 +6336,11 @@ var login = new Command55("login").description("login to velastack.dev").configu
5351
6336
  }
5352
6337
  const apiKey = await issueApiKey(fetchCookie);
5353
6338
  writeConfig({ apiKey });
5354
- p24.log.success("Logged in to velastack.dev");
6339
+ p26.log.success("Logged in to velastack.dev");
5355
6340
  }, "Failed to login.")
5356
6341
  );
5357
6342
  async function issueApiKey(fetchCookie) {
5358
- const label = `CLI - ${os2.hostname()}`;
6343
+ const label = `CLI - ${os3.hostname()}`;
5359
6344
  await fetchCookie(`${API_URL}/api-keys/new`, {
5360
6345
  method: "POST",
5361
6346
  headers: {
@@ -5392,14 +6377,14 @@ function extractApiKey(cookie) {
5392
6377
  }
5393
6378
 
5394
6379
  // src/commands/signup.ts
5395
- var signup = new Command56("signup").description("signup to velastack.dev").configureHelp(helpConfig).action(
6380
+ var signup = new Command57("signup").description("signup to velastack.dev").configureHelp(helpConfig).action(
5396
6381
  () => runCommand(async () => {
5397
- const { email: email3, password: password8, passwordConfirm } = await p25.group({
5398
- email: () => p25.text({ message: "Email" }),
5399
- password: () => p25.password({ message: "Password" }),
5400
- passwordConfirm: () => p25.password({ message: "Confirm password" })
6382
+ const { email: email3, password: password10, passwordConfirm } = await p27.group({
6383
+ email: () => p27.text({ message: "Email" }),
6384
+ password: () => p27.password({ message: "Password" }),
6385
+ passwordConfirm: () => p27.password({ message: "Confirm password" })
5401
6386
  });
5402
- if (password8 !== passwordConfirm) {
6387
+ if (password10 !== passwordConfirm) {
5403
6388
  throw new Error("Passwords do not match.");
5404
6389
  }
5405
6390
  const fetchCookie = makeFetchCookie2(fetch);
@@ -5412,7 +6397,7 @@ var signup = new Command56("signup").description("signup to velastack.dev").conf
5412
6397
  body: new URLSearchParams({
5413
6398
  type: "password",
5414
6399
  email: email3,
5415
- password: password8,
6400
+ password: password10,
5416
6401
  passwordConfirm
5417
6402
  }).toString()
5418
6403
  });
@@ -5421,33 +6406,33 @@ var signup = new Command56("signup").description("signup to velastack.dev").conf
5421
6406
  }
5422
6407
  const apiKey = await issueApiKey(fetchCookie);
5423
6408
  writeConfig({ apiKey });
5424
- p25.log.success("Signed up to velastack.dev");
5425
- p25.log.info("Check your email for a confirmation link.");
6409
+ p27.log.success("Signed up to velastack.dev");
6410
+ p27.log.info("Check your email for a confirmation link.");
5426
6411
  }, "Failed to signup.")
5427
6412
  );
5428
6413
 
5429
6414
  // src/commands/logout.ts
5430
- import { Command as Command57 } from "commander";
5431
- import * as p26 from "@clack/prompts";
5432
- var logout = new Command57("logout").alias("signout").description("logout from velastack.dev").configureHelp(helpConfig).action(
6415
+ import { Command as Command58 } from "commander";
6416
+ import * as p28 from "@clack/prompts";
6417
+ var logout = new Command58("logout").alias("signout").description("logout from velastack.dev").configureHelp(helpConfig).action(
5433
6418
  () => runCommand(() => {
5434
6419
  if (!readConfig()) {
5435
- p26.log.info("Not logged in");
6420
+ p28.log.info("Not logged in");
5436
6421
  return;
5437
6422
  }
5438
6423
  clearConfig();
5439
- p26.log.success("Logged out of velastack.dev");
6424
+ p28.log.success("Logged out of velastack.dev");
5440
6425
  })
5441
6426
  );
5442
6427
 
5443
6428
  // src/commands/whoami.ts
5444
- import { Command as Command58 } from "commander";
5445
- import * as p27 from "@clack/prompts";
5446
- var whoami = new Command58("whoami").description("show the current user").configureHelp(helpConfig).action(
6429
+ import { Command as Command59 } from "commander";
6430
+ import * as p29 from "@clack/prompts";
6431
+ var whoami = new Command59("whoami").description("show the current user").configureHelp(helpConfig).action(
5447
6432
  () => runCommand(async () => {
5448
6433
  const apiKey = readConfig()?.apiKey;
5449
6434
  if (!apiKey) {
5450
- p27.log.info("Not logged in. Run `vela login` to login.");
6435
+ p29.log.info("Not logged in. Run `vela login` to login.");
5451
6436
  return;
5452
6437
  }
5453
6438
  const res = await fetch(`${API_URL}/api/collections/users/records`, {
@@ -5455,31 +6440,31 @@ var whoami = new Command58("whoami").description("show the current user").config
5455
6440
  });
5456
6441
  const data = await res.json();
5457
6442
  if (!data.items.length) throw new Error("No user found. Run `vela login` to login.");
5458
- p27.log.success(`Logged in as ${data.items[0].email}`);
6443
+ p29.log.success(`Logged in as ${data.items[0].email}`);
5459
6444
  })
5460
6445
  );
5461
6446
 
5462
6447
  // src/commands/migrate.ts
5463
- import { Command as Command64 } from "commander";
6448
+ import { Command as Command65 } from "commander";
5464
6449
 
5465
6450
  // src/commands/migrate/up.ts
5466
- import { Command as Command59 } from "commander";
6451
+ import { Command as Command60 } from "commander";
5467
6452
 
5468
6453
  // src/lib/migrate.ts
5469
- import path26 from "node:path";
5470
- import process14 from "node:process";
6454
+ import path29 from "node:path";
6455
+ import process17 from "node:process";
5471
6456
  import { x as x2 } from "tinyexec";
5472
6457
  async function runPocketbaseMigrate(args) {
5473
- const cwd = process14.cwd();
6458
+ const cwd = process17.cwd();
5474
6459
  const { getBinaryPath } = await import("pocketbase-server");
5475
6460
  const binaryPath = getBinaryPath();
5476
6461
  await x2(
5477
6462
  binaryPath,
5478
6463
  [
5479
6464
  "--dir",
5480
- path26.join(cwd, DATA_DIR),
6465
+ path29.join(cwd, DATA_DIR),
5481
6466
  "--migrationsDir",
5482
- path26.join(cwd, MIGRATIONS_DIR),
6467
+ path29.join(cwd, MIGRATIONS_DIR),
5483
6468
  "migrate",
5484
6469
  ...args
5485
6470
  ],
@@ -5502,10 +6487,10 @@ async function runMigrateUp() {
5502
6487
  ]
5503
6488
  });
5504
6489
  }
5505
- var up = new Command59("up").description("apply all pending migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations."));
6490
+ var up = new Command60("up").description("apply all pending migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations."));
5506
6491
 
5507
6492
  // src/commands/migrate/down.ts
5508
- import { Command as Command60, InvalidArgumentError as InvalidArgumentError4 } from "commander";
6493
+ import { Command as Command61, InvalidArgumentError as InvalidArgumentError4 } from "commander";
5509
6494
  function parseSteps(value) {
5510
6495
  const n = parseInt(value, 10);
5511
6496
  if (!Number.isFinite(n) || n <= 0) {
@@ -5513,7 +6498,7 @@ function parseSteps(value) {
5513
6498
  }
5514
6499
  return n;
5515
6500
  }
5516
- var down = new Command60("down").alias("rollback").description("revert the last N applied migrations").argument("[number]", "how many migrations to revert", parseSteps, 1).configureHelp(helpConfig).action(
6501
+ var down = new Command61("down").alias("rollback").description("revert the last N applied migrations").argument("[number]", "how many migrations to revert", parseSteps, 1).configureHelp(helpConfig).action(
5517
6502
  (n) => runCommand(async () => {
5518
6503
  await runPocketbaseMigrate(["down", String(n)]);
5519
6504
  reportResult({
@@ -5527,20 +6512,20 @@ var down = new Command60("down").alias("rollback").description("revert the last
5527
6512
  );
5528
6513
 
5529
6514
  // src/commands/migrate/create.ts
5530
- import fs25 from "node:fs";
5531
- import path27 from "node:path";
5532
- import process15 from "node:process";
5533
- import { Command as Command61 } from "commander";
5534
- var create2 = new Command61("create").alias("new").description("create a new blank migration").argument("<name>", "migration name (snake_case)").configureHelp(helpConfig).action(
6515
+ import fs28 from "node:fs";
6516
+ import path30 from "node:path";
6517
+ import process18 from "node:process";
6518
+ import { Command as Command62 } from "commander";
6519
+ var create2 = new Command62("create").alias("new").description("create a new blank migration").argument("<name>", "migration name (snake_case)").configureHelp(helpConfig).action(
5535
6520
  (name) => runCommand(async () => {
5536
- const cwd = process15.cwd();
6521
+ const cwd = process18.cwd();
5537
6522
  const before = listMigrationFiles(cwd);
5538
6523
  await runPocketbaseMigrate(["create", name]);
5539
6524
  const after = listMigrationFiles(cwd);
5540
6525
  const added = [...after].filter((f) => !before.has(f));
5541
6526
  reportResult({
5542
6527
  summary: `Created blank migration ${name}.`,
5543
- filesCreated: added.map((f) => path27.join(MIGRATIONS_DIR, f)),
6528
+ filesCreated: added.map((f) => path30.join(MIGRATIONS_DIR, f)),
5544
6529
  nextSteps: [
5545
6530
  `Open the new file in ${MIGRATIONS_DIR}/ and fill in the up/down handlers.`,
5546
6531
  "Run `vela migrate up` to apply the migration once the handlers are written."
@@ -5549,19 +6534,19 @@ var create2 = new Command61("create").alias("new").description("create a new bla
5549
6534
  }, "Failed to create migration.")
5550
6535
  );
5551
6536
  function listMigrationFiles(cwd) {
5552
- const dir = path27.join(cwd, MIGRATIONS_DIR);
5553
- if (!fs25.existsSync(dir)) return /* @__PURE__ */ new Set();
5554
- return new Set(fs25.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
6537
+ const dir = path30.join(cwd, MIGRATIONS_DIR);
6538
+ if (!fs28.existsSync(dir)) return /* @__PURE__ */ new Set();
6539
+ return new Set(fs28.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
5555
6540
  }
5556
6541
 
5557
6542
  // src/commands/migrate/collections.ts
5558
- import fs26 from "node:fs";
5559
- import path28 from "node:path";
5560
- import process16 from "node:process";
5561
- import { Command as Command62 } from "commander";
5562
- var collections = new Command62("collections").alias("snapshot").description("snapshot local collections into a new migration").configureHelp(helpConfig).action(
6543
+ import fs29 from "node:fs";
6544
+ import path31 from "node:path";
6545
+ import process19 from "node:process";
6546
+ import { Command as Command63 } from "commander";
6547
+ var collections = new Command63("collections").alias("snapshot").description("snapshot local collections into a new migration").configureHelp(helpConfig).action(
5563
6548
  () => runCommand(async () => {
5564
- const cwd = process16.cwd();
6549
+ const cwd = process19.cwd();
5565
6550
  const before = listMigrationFiles2(cwd);
5566
6551
  await runPocketbaseMigrate(["collections"]);
5567
6552
  const after = listMigrationFiles2(cwd);
@@ -5574,7 +6559,7 @@ var collections = new Command62("collections").alias("snapshot").description("sn
5574
6559
  }
5575
6560
  reportResult({
5576
6561
  summary: "Snapshotted local collections into a new migration.",
5577
- filesCreated: added.map((f) => path28.join(MIGRATIONS_DIR, f)),
6562
+ filesCreated: added.map((f) => path31.join(MIGRATIONS_DIR, f)),
5578
6563
  nextSteps: [
5579
6564
  `Review the generated snapshot in ${MIGRATIONS_DIR}/.`,
5580
6565
  "Commit the snapshot so teammates pick up the new schema.",
@@ -5584,14 +6569,14 @@ var collections = new Command62("collections").alias("snapshot").description("sn
5584
6569
  }, "Failed to snapshot collections.")
5585
6570
  );
5586
6571
  function listMigrationFiles2(cwd) {
5587
- const dir = path28.join(cwd, MIGRATIONS_DIR);
5588
- if (!fs26.existsSync(dir)) return /* @__PURE__ */ new Set();
5589
- return new Set(fs26.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
6572
+ const dir = path31.join(cwd, MIGRATIONS_DIR);
6573
+ if (!fs29.existsSync(dir)) return /* @__PURE__ */ new Set();
6574
+ return new Set(fs29.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
5590
6575
  }
5591
6576
 
5592
6577
  // src/commands/migrate/history-sync.ts
5593
- import { Command as Command63 } from "commander";
5594
- var historySync = new Command63("history-sync").description("drop _migrations rows whose files no longer exist").configureHelp(helpConfig).action(
6578
+ import { Command as Command64 } from "commander";
6579
+ var historySync = new Command64("history-sync").description("drop _migrations rows whose files no longer exist").configureHelp(helpConfig).action(
5595
6580
  () => runCommand(async () => {
5596
6581
  await runPocketbaseMigrate(["history-sync"]);
5597
6582
  reportResult({
@@ -5602,15 +6587,15 @@ var historySync = new Command63("history-sync").description("drop _migrations ro
5602
6587
  );
5603
6588
 
5604
6589
  // src/commands/migrate.ts
5605
- var migrate = new Command64("migrate").description("manage database migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations.")).addCommand(up).addCommand(down).addCommand(create2).addCommand(collections).addCommand(historySync);
6590
+ var migrate = new Command65("migrate").description("manage database migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations.")).addCommand(up).addCommand(down).addCommand(create2).addCommand(collections).addCommand(historySync);
5606
6591
 
5607
6592
  // src/commands/dev.ts
5608
- import fs27 from "node:fs";
5609
- import path29 from "node:path";
5610
- import process17 from "node:process";
6593
+ import fs30 from "node:fs";
6594
+ import path32 from "node:path";
6595
+ import process20 from "node:process";
5611
6596
  import { performance } from "node:perf_hooks";
5612
- import { Command as Command65, InvalidArgumentError as InvalidArgumentError5 } from "commander";
5613
- import pc5 from "picocolors";
6597
+ import { Command as Command66, InvalidArgumentError as InvalidArgumentError5 } from "commander";
6598
+ import pc7 from "picocolors";
5614
6599
  import PocketBase2 from "pocketbase";
5615
6600
  function parsePort(value) {
5616
6601
  const port = Number(value);
@@ -5619,74 +6604,80 @@ function parsePort(value) {
5619
6604
  }
5620
6605
  return port;
5621
6606
  }
5622
- var dev = new Command65("dev").description("start the development server").option("--open", "open the app in a browser once the server is ready").option("--host [host]", "expose the server on the network").option("--port <port>", "port to listen on", parsePort).configureHelp(helpConfig).action(async (options) => {
5623
- const cwd = process17.cwd();
6607
+ var dev = new Command66("dev").description("start the development server").option("--open [path]", "open the app in a browser once the server is ready").option("--host [host]", "expose the server on the network").option("--port <port>", "port to listen on", parsePort).option("--strictPort", "exit if the port is already in use instead of taking the next one").option("--cors", "enable CORS").option("--force", "re-bundle dependencies, ignoring the optimizer cache").configureHelp(helpConfig).action(async (options) => {
6608
+ const cwd = process20.cwd();
5624
6609
  const startTime = performance.now();
5625
6610
  const { createServer, version } = await import("vite");
5626
- const viteMetadataDir = path29.join(cwd, "node_modules", ".vite");
5627
- const viteMetadataFile = path29.join(viteMetadataDir, "_pocketbase_metadata.json");
6611
+ const viteMetadataDir = path32.join(cwd, "node_modules", ".vite");
6612
+ const viteMetadataFile = path32.join(viteMetadataDir, "_pocketbase_metadata.json");
5628
6613
  let pbProc;
5629
6614
  const backend3 = hasBackend(cwd);
5630
- const needsStart = backend3 && !process17.env.POCKETBASE_URL;
6615
+ const needsStart = backend3 && !process20.env.POCKETBASE_URL;
5631
6616
  const cleanup = () => {
5632
6617
  if (pbProc?.pid) pbProc.kill();
5633
- if (fs27.existsSync(viteMetadataFile)) fs27.rmSync(viteMetadataFile);
6618
+ if (fs30.existsSync(viteMetadataFile)) fs30.rmSync(viteMetadataFile);
5634
6619
  };
5635
6620
  if (needsStart) {
5636
- const dataDir = path29.join(cwd, DATA_DIR);
6621
+ const dataDir = path32.join(cwd, DATA_DIR);
5637
6622
  const started = await startPocketbaseServe({
5638
6623
  dataDir,
5639
6624
  migrationsDir: MIGRATIONS_DIR,
5640
- hooksDir: path29.join(dataDir, "hooks"),
6625
+ hooksDir: path32.join(dataDir, "hooks"),
5641
6626
  dev: true,
5642
6627
  stdio: "pipe"
5643
6628
  });
5644
6629
  pbProc = started.proc;
5645
- process17.env.POCKETBASE_URL = started.url;
5646
- pbProc.stdout?.pipe(process17.stdout);
5647
- pbProc.stderr?.pipe(process17.stderr);
6630
+ process20.env.POCKETBASE_URL = started.url;
6631
+ pbProc.stdout?.pipe(process20.stdout);
6632
+ pbProc.stderr?.pipe(process20.stderr);
5648
6633
  pbProc.on("error", (err) => console.error("PocketBase error:", err));
5649
6634
  pbProc.on("exit", (code) => console.log(`PocketBase exited with code ${code}`));
5650
- process17.on("exit", cleanup);
5651
- process17.on("SIGINT", () => {
6635
+ process20.on("exit", cleanup);
6636
+ process20.on("SIGINT", () => {
5652
6637
  cleanup();
5653
- process17.exit(0);
6638
+ process20.exit(0);
5654
6639
  });
5655
6640
  }
5656
6641
  const serverOptions = {};
5657
6642
  if (options.open !== void 0) serverOptions.open = options.open;
5658
6643
  if (options.host !== void 0) serverOptions.host = options.host;
5659
6644
  if (options.port !== void 0) serverOptions.port = options.port;
5660
- const server = await createServer({ server: serverOptions });
6645
+ if (options.cors !== void 0) serverOptions.cors = options.cors;
6646
+ if (options.strictPort !== void 0) serverOptions.strictPort = options.strictPort;
6647
+ const inlineConfig = {
6648
+ server: serverOptions
6649
+ };
6650
+ if (options.force !== void 0) inlineConfig.forceOptimizeDeps = options.force;
6651
+ const server = await createServer(inlineConfig);
5661
6652
  server.httpServer?.on("listening", async () => {
5662
6653
  if (!backend3) return;
5663
6654
  const { address, port: vitePort } = server.httpServer.address();
5664
6655
  const viteHost = address === "::1" ? "localhost" : address;
5665
- await fs27.promises.mkdir(viteMetadataDir, { recursive: true });
5666
- await fs27.promises.writeFile(
6656
+ await fs30.promises.mkdir(viteMetadataDir, { recursive: true });
6657
+ await fs30.promises.writeFile(
5667
6658
  viteMetadataFile,
5668
6659
  JSON.stringify({
5669
- pocketbaseUrl: process17.env.POCKETBASE_URL,
6660
+ pocketbaseUrl: process20.env.POCKETBASE_URL,
5670
6661
  vitePort,
5671
6662
  viteHost
5672
6663
  })
5673
6664
  );
5674
- const pb = new PocketBase2(process17.env.POCKETBASE_URL);
6665
+ const pb = new PocketBase2(process20.env.POCKETBASE_URL);
5675
6666
  await pb.collection("_superusers").authWithPassword(
5676
- process17.env.POCKETBASE_SUPERUSER_EMAIL,
5677
- process17.env.POCKETBASE_SUPERUSER_PASSWORD
6667
+ process20.env.POCKETBASE_SUPERUSER_EMAIL,
6668
+ process20.env.POCKETBASE_SUPERUSER_PASSWORD
5678
6669
  );
5679
6670
  await pb.settings.update({ meta: { appURL: `http://${viteHost}:${vitePort}` } });
5680
6671
  await startWatchingTypes(cwd, pb);
5681
6672
  });
5682
6673
  await server.listen();
5683
- const hasExistingLogs = process17.stdout.bytesWritten > 0 || process17.stderr.bytesWritten > 0;
5684
- const startupDurationString = pc5.dim(
5685
- `ready in ${pc5.reset(pc5.bold(Math.ceil(performance.now() - startTime)))} ms`
6674
+ const hasExistingLogs = process20.stdout.bytesWritten > 0 || process20.stderr.bytesWritten > 0;
6675
+ const startupDurationString = pc7.dim(
6676
+ `ready in ${pc7.reset(pc7.bold(Math.ceil(performance.now() - startTime)))} ms`
5686
6677
  );
5687
6678
  server.config.logger.info(
5688
6679
  `
5689
- ${pc5.green(`${pc5.bold("VITE")} v${version}`)} ${startupDurationString}
6680
+ ${pc7.green(`${pc7.bold("VITE")} v${version}`)} ${startupDurationString}
5690
6681
  `,
5691
6682
  { clear: !hasExistingLogs }
5692
6683
  );
@@ -5695,18 +6686,18 @@ var dev = new Command65("dev").description("start the development server").optio
5695
6686
  });
5696
6687
  async function startWatchingTypes(cwd, pb) {
5697
6688
  const { processTypes } = await import("@velastack/pocketbase-codegen");
5698
- const typesDir = path29.resolve(cwd, ".svelte-kit", "types");
5699
- const pocketbaseDir = path29.join(typesDir, "pocketbase");
5700
- const pocketbaseTypes = path29.join(pocketbaseDir, "$types.d.ts");
6689
+ const typesDir = path32.resolve(cwd, ".svelte-kit", "types");
6690
+ const pocketbaseDir = path32.join(typesDir, "pocketbase");
6691
+ const pocketbaseTypes = path32.join(pocketbaseDir, "$types.d.ts");
5701
6692
  const regenerate = () => processTypes(pb, typesDir).catch(() => {
5702
6693
  });
5703
6694
  await regenerate();
5704
6695
  void (async () => {
5705
6696
  for (; ; ) {
5706
6697
  try {
5707
- await fs27.promises.mkdir(pocketbaseDir, { recursive: true });
5708
- for await (const event of fs27.promises.watch(pocketbaseDir)) {
5709
- if (event.eventType === "rename" && event.filename === "$types.d.ts" && !fs27.existsSync(pocketbaseTypes)) {
6698
+ await fs30.promises.mkdir(pocketbaseDir, { recursive: true });
6699
+ for await (const event of fs30.promises.watch(pocketbaseDir)) {
6700
+ if (event.eventType === "rename" && event.filename === "$types.d.ts" && !fs30.existsSync(pocketbaseTypes)) {
5710
6701
  setTimeout(regenerate, 100);
5711
6702
  }
5712
6703
  }
@@ -5718,117 +6709,540 @@ async function startWatchingTypes(cwd, pb) {
5718
6709
  }
5719
6710
 
5720
6711
  // src/commands/build.ts
5721
- import path30 from "node:path";
5722
- import process18 from "node:process";
5723
- import { Command as Command66 } from "commander";
6712
+ import path33 from "node:path";
6713
+ import process21 from "node:process";
6714
+ import { Command as Command67 } from "commander";
5724
6715
  import { x as x3 } from "tinyexec";
5725
6716
  import { detect as detect5 } from "package-manager-detector";
5726
6717
  import { resolveCommand as resolveCommand5 } from "package-manager-detector/commands";
5727
- var build = new Command66("build").description("build the app").configureHelp(helpConfig).action(async () => {
5728
- process18.env.VITE_BUILD = "true";
5729
- const cwd = process18.cwd();
6718
+ var build = new Command67("build").description("build the app").configureHelp(helpConfig).action(async () => {
6719
+ process21.env.VITE_BUILD = "true";
6720
+ const cwd = process21.cwd();
6721
+ let pbProc;
6722
+ const needsStart = hasBackend(cwd) && !process21.env.POCKETBASE_URL;
6723
+ const cleanup = () => {
6724
+ if (pbProc?.pid) pbProc.kill();
6725
+ };
6726
+ if (needsStart) {
6727
+ await ensureSuperuser(cwd);
6728
+ const dataDir = path33.join(cwd, DATA_DIR);
6729
+ const started = await startPocketbaseServe({
6730
+ dataDir,
6731
+ migrationsDir: MIGRATIONS_DIR,
6732
+ hooksDir: path33.join(dataDir, "hooks"),
6733
+ dev: true
6734
+ });
6735
+ pbProc = started.proc;
6736
+ process21.env.POCKETBASE_URL = started.url;
6737
+ process21.on("exit", cleanup);
6738
+ process21.on("SIGINT", () => {
6739
+ cleanup();
6740
+ process21.exit(0);
6741
+ });
6742
+ }
6743
+ try {
6744
+ const pm = (await detect5({ cwd }))?.name ?? "npm";
6745
+ const resolved = resolveCommand5(pm, "execute", ["vite", "build"]);
6746
+ const args = resolved.args.slice();
6747
+ if (pm === "npm") args.unshift("--yes");
6748
+ await x3(resolved.command, args, {
6749
+ nodeOptions: { cwd, stdio: "inherit" },
6750
+ throwOnError: true
6751
+ });
6752
+ } finally {
6753
+ cleanup();
6754
+ }
6755
+ });
6756
+
6757
+ // src/commands/preview.ts
6758
+ import path34 from "node:path";
6759
+ import process22 from "node:process";
6760
+ import { Command as Command68 } from "commander";
6761
+ import { x as x4 } from "tinyexec";
6762
+ import { detect as detect6 } from "package-manager-detector";
6763
+ import { resolveCommand as resolveCommand6 } from "package-manager-detector/commands";
6764
+ var preview = new Command68("preview").description("preview the built app").configureHelp(helpConfig).action(async () => {
6765
+ const cwd = process22.cwd();
5730
6766
  let pbProc;
5731
- const needsStart = hasBackend(cwd) && !process18.env.POCKETBASE_URL;
6767
+ const needsStart = hasBackend(cwd) && !process22.env.POCKETBASE_URL;
5732
6768
  const cleanup = () => {
5733
6769
  if (pbProc?.pid) pbProc.kill();
5734
6770
  };
5735
6771
  if (needsStart) {
5736
- const dataDir = path30.join(cwd, DATA_DIR);
6772
+ const dataDir = path34.join(cwd, DATA_DIR);
5737
6773
  const started = await startPocketbaseServe({
5738
6774
  dataDir,
5739
6775
  migrationsDir: MIGRATIONS_DIR,
5740
- hooksDir: path30.join(dataDir, "hooks"),
6776
+ hooksDir: path34.join(dataDir, "hooks"),
5741
6777
  dev: true
5742
6778
  });
5743
6779
  pbProc = started.proc;
5744
- process18.env.POCKETBASE_URL = started.url;
5745
- process18.on("exit", cleanup);
5746
- process18.on("SIGINT", () => {
6780
+ process22.env.POCKETBASE_URL = started.url;
6781
+ process22.on("exit", cleanup);
6782
+ process22.on("SIGINT", () => {
5747
6783
  cleanup();
5748
- process18.exit(0);
6784
+ process22.exit(0);
6785
+ });
6786
+ }
6787
+ try {
6788
+ const pm = (await detect6({ cwd }))?.name ?? "npm";
6789
+ const resolved = resolveCommand6(pm, "execute", ["vite", "preview"]);
6790
+ const args = resolved.args.slice();
6791
+ if (pm === "npm") args.unshift("--yes");
6792
+ await x4(resolved.command, args, {
6793
+ nodeOptions: { cwd, stdio: "inherit" },
6794
+ throwOnError: true
6795
+ });
6796
+ } finally {
6797
+ cleanup();
6798
+ }
6799
+ });
6800
+
6801
+ // src/commands/sync.ts
6802
+ import path35 from "node:path";
6803
+ import { Command as Command69 } from "commander";
6804
+ var sync = new Command69("sync").description("sync types from the database").configureHelp(helpConfig).action(
6805
+ () => runCommand(async () => {
6806
+ const { workspaceRootDir } = await getWorkspace();
6807
+ const typesDir = path35.join(workspaceRootDir, ".svelte-kit", "types");
6808
+ const { processTypes } = await import("@velastack/pocketbase-codegen");
6809
+ await withPocketbase(workspaceRootDir, async (pb) => {
6810
+ await processTypes(pb, typesDir);
6811
+ });
6812
+ console.log("types synced");
6813
+ }, "Failed to sync types.")
6814
+ );
6815
+
6816
+ // src/commands/provision.ts
6817
+ import { Command as Command70 } from "commander";
6818
+ import * as p30 from "@clack/prompts";
6819
+ import pc8 from "picocolors";
6820
+ import * as v6 from "valibot";
6821
+ var OptionsSchema2 = v6.object({
6822
+ ...SSH_OPTION_SCHEMA,
6823
+ pbVersion: v6.optional(v6.string()),
6824
+ nodeMajor: v6.optional(v6.string())
6825
+ });
6826
+ var provision = addSshOptions(
6827
+ new Command70("provision").description("prepare a server to host vela apps").argument("<target>", "SSH target \u2014 an alias from ~/.ssh/config, or user@host").configureHelp(helpConfig)
6828
+ ).option("--pb-version <version>", "PocketBase version to install").option("--node-major <version>", "Node.js major version to install", "22").action(
6829
+ (target, raw) => runCommand(async () => {
6830
+ const options = parseOptions(OptionsSchema2, raw);
6831
+ const pbVersion = options.pbVersion ?? pocketbaseVersion();
6832
+ p30.intro(pc8.bgCyan(pc8.black(" vela provision ")));
6833
+ p30.log.info(`Target ${pc8.cyan(target)}`);
6834
+ await withSsh(target, sshOptionsFrom(options), async (session) => {
6835
+ await session.detectElevation();
6836
+ const existing = await readServerInfo(session);
6837
+ if (existing) {
6838
+ p30.log.info(
6839
+ `Already provisioned by vela ${existing.cliVersion} on ${existing.provisionedAt}. Bringing it up to date.`
6840
+ );
6841
+ }
6842
+ p30.log.step("Uploading server scripts");
6843
+ await syncServerScripts(session);
6844
+ p30.log.step("Running provision");
6845
+ const result = await runServerScript(session, "provision.sh", {
6846
+ args: [
6847
+ "--pb-version",
6848
+ pbVersion,
6849
+ "--node-major",
6850
+ options.nodeMajor ?? "22",
6851
+ "--cli-version",
6852
+ package_default.version
6853
+ ],
6854
+ stream: true
6855
+ });
6856
+ p30.log.success(
6857
+ `${target} is ready.
6858
+
6859
+ Node ${result?.node ?? "installed"}
6860
+ Caddy ${result?.caddy ?? "installed"}
6861
+ PocketBase ${result?.pocketbase ?? pbVersion}`
6862
+ );
5749
6863
  });
6864
+ p30.outro(`Deploy with ${pc8.cyan(`vela deploy --server ${target}`)}`);
6865
+ }, "Failed to provision.")
6866
+ );
6867
+
6868
+ // src/commands/deploy.ts
6869
+ import path38 from "node:path";
6870
+ import fs33 from "node:fs";
6871
+ import { Command as Command71 } from "commander";
6872
+ import * as p31 from "@clack/prompts";
6873
+ import pc9 from "picocolors";
6874
+ import * as v7 from "valibot";
6875
+
6876
+ // src/lib/pocketbase-settings.ts
6877
+ import fs31 from "node:fs";
6878
+ import path36 from "node:path";
6879
+ import process23 from "node:process";
6880
+ import PocketBase4 from "pocketbase";
6881
+
6882
+ // src/lib/remote-pocketbase.ts
6883
+ import PocketBase3 from "pocketbase";
6884
+ async function openRemoteDatabase(session, instance) {
6885
+ const [state] = await readInstanceStates(session, instance);
6886
+ const pbPort = state?.pbPort;
6887
+ if (!pbPort) {
6888
+ throw new Error(
6889
+ `${instance} has no deployed database yet.
6890
+
6891
+ Run \`vela deploy\` first \u2014 the database is created by the first deploy.`
6892
+ );
6893
+ }
6894
+ const env2 = await readRemoteEnv(session, instance);
6895
+ const email3 = env2.POCKETBASE_SUPERUSER_EMAIL;
6896
+ const password10 = env2.POCKETBASE_SUPERUSER_PASSWORD;
6897
+ if (!email3 || !password10) {
6898
+ throw new Error(
6899
+ `${instance} has no PocketBase superuser credentials in its environment.
6900
+
6901
+ A deploy creates them. Deploy again to repair this instance.`
6902
+ );
6903
+ }
6904
+ const localPort = await findFreePort("127.0.0.1");
6905
+ await session.forwardLocalPort(localPort, "127.0.0.1", pbPort);
6906
+ return {
6907
+ url: `http://127.0.0.1:${localPort}`,
6908
+ email: email3,
6909
+ password: password10,
6910
+ close: () => session.cancelForward(localPort, "127.0.0.1", pbPort)
6911
+ };
6912
+ }
6913
+ async function withRemotePocketbase(session, instance, fn) {
6914
+ const db = await openRemoteDatabase(session, instance);
6915
+ try {
6916
+ const pb = new PocketBase3(db.url);
6917
+ await authWithRetries(pb, db.email, db.password);
6918
+ return await fn(pb);
6919
+ } finally {
6920
+ await db.close();
6921
+ }
6922
+ }
6923
+
6924
+ // src/lib/pocketbase-settings.ts
6925
+ var COPIED_KEYS = ["appName", "senderName", "senderAddress"];
6926
+ async function readLocalMeta(cwd) {
6927
+ const dataDir = path36.join(cwd, DATA_DIR);
6928
+ if (!fs31.existsSync(dataDir)) return null;
6929
+ const email3 = process23.env.POCKETBASE_SUPERUSER_EMAIL;
6930
+ const password10 = process23.env.POCKETBASE_SUPERUSER_PASSWORD;
6931
+ if (!email3 || !password10) return null;
6932
+ let proc;
6933
+ try {
6934
+ const started = await startPocketbaseServe({
6935
+ dataDir,
6936
+ migrationsDir: MIGRATIONS_DIR,
6937
+ hooksDir: path36.join(dataDir, "hooks")
6938
+ });
6939
+ proc = started.proc;
6940
+ const pb = new PocketBase4(started.url);
6941
+ await authWithRetries(pb, email3, password10);
6942
+ const settings = await pb.settings.getAll();
6943
+ return settings.meta ?? null;
6944
+ } catch {
6945
+ return null;
6946
+ } finally {
6947
+ proc?.kill();
6948
+ }
6949
+ }
6950
+ async function seedRemoteMeta(session, instance, local, appURL) {
6951
+ const patch = {};
6952
+ for (const key of COPIED_KEYS) {
6953
+ const value = local[key];
6954
+ if (typeof value === "string" && value.trim()) patch[key] = value;
6955
+ }
6956
+ if (appURL) patch.appURL = appURL;
6957
+ if (Object.keys(patch).length === 0) return [];
6958
+ await withRemotePocketbase(session, instance, async (pb) => {
6959
+ const settings = await pb.settings.getAll();
6960
+ await pb.settings.update({ meta: { ...settings.meta ?? {}, ...patch } });
6961
+ });
6962
+ return Object.keys(patch);
6963
+ }
6964
+
6965
+ // src/lib/artifact.ts
6966
+ import fs32 from "node:fs";
6967
+ import path37 from "node:path";
6968
+ import { detect as detect7 } from "package-manager-detector";
6969
+ import { resolveCommand as resolveCommand7 } from "package-manager-detector/commands";
6970
+ var DEFAULT_OUTPUT_DIR = "build";
6971
+ var BuildError = class extends Error {
6972
+ };
6973
+ async function runBuild(cwd, buildCommand, env2) {
6974
+ if (buildCommand) {
6975
+ const result2 = await spawnCapture("bash", ["-lc", buildCommand], {
6976
+ cwd,
6977
+ stream: true,
6978
+ streamStdout: true,
6979
+ env: env2
6980
+ });
6981
+ if (result2.exitCode !== 0)
6982
+ throw new BuildError(`\`${buildCommand}\` exited ${result2.exitCode}.`);
6983
+ return;
6984
+ }
6985
+ const pm = (await detect7({ cwd }))?.name ?? "npm";
6986
+ const resolved = resolveCommand7(pm, "run", ["build"]);
6987
+ if (!resolved) throw new BuildError(`Could not work out how to run a build with ${pm}.`);
6988
+ const result = await spawnCapture(resolved.command, resolved.args, {
6989
+ cwd,
6990
+ stream: true,
6991
+ streamStdout: true,
6992
+ env: env2
6993
+ });
6994
+ if (result.exitCode !== 0) {
6995
+ throw new BuildError(
6996
+ `\`${resolved.command} ${resolved.args.join(" ")}\` exited ${result.exitCode}.`
6997
+ );
5750
6998
  }
6999
+ }
7000
+ function collectArtifact(cwd, config = {}) {
7001
+ const outputDir = config.outputDir ?? DEFAULT_OUTPUT_DIR;
7002
+ const entries = [];
7003
+ const add2 = (rel, remoteDir = "") => {
7004
+ const localPath = path37.join(cwd, rel);
7005
+ if (fs32.existsSync(localPath)) entries.push({ localPath, remoteDir });
7006
+ };
7007
+ const buildPath = path37.join(cwd, outputDir);
7008
+ if (!fs32.existsSync(path37.join(buildPath, "index.js"))) {
7009
+ throw new BuildError(
7010
+ `No ${outputDir}/index.js after the build.
7011
+
7012
+ Deploying to a server needs @sveltejs/adapter-node. Install it and set it as
7013
+ the adapter in your Vite or Svelte config, then build again.`
7014
+ );
7015
+ }
7016
+ entries.push({ localPath: buildPath, remoteDir: "" });
7017
+ add2("package.json");
7018
+ add2("package-lock.json");
7019
+ add2(".npmrc");
7020
+ add2(MIGRATIONS_DIR);
7021
+ const hooks = path37.join(cwd, DATA_DIR, "hooks");
7022
+ if (fs32.existsSync(hooks)) entries.push({ localPath: hooks, remoteDir: "hooks" });
7023
+ for (const extra of config.include ?? []) add2(extra);
7024
+ return entries;
7025
+ }
7026
+ async function gitSha(cwd) {
7027
+ const result = await spawnCapture("git", ["rev-parse", "HEAD"], { cwd });
7028
+ return result.exitCode === 0 ? result.stdout.trim() : "";
7029
+ }
7030
+
7031
+ // src/commands/deploy.ts
7032
+ var OptionsSchema3 = v7.object({
7033
+ ...SSH_OPTION_SCHEMA,
7034
+ env: v7.optional(v7.string()),
7035
+ project: v7.optional(v7.string()),
7036
+ remoteDb: v7.optional(v7.boolean()),
7037
+ domain: v7.optional(v7.string()),
7038
+ healthPath: v7.optional(v7.string()),
7039
+ keep: v7.optional(v7.string()),
7040
+ pbVersion: v7.optional(v7.string()),
7041
+ build: v7.optional(v7.boolean())
7042
+ });
7043
+ var deploy = addTargetOptions(
7044
+ new Command71("deploy").description("deploy the app").configureHelp(helpConfig),
7045
+ "production"
7046
+ ).option("--project <name>", "override the project name").option("--domain <hosts>", "hostname(s) to serve on, comma separated").option("--health-path <path>", "path the health check requests").option("--keep <count>", "how many old releases to keep on the server").option("--pb-version <version>", "PocketBase version to run").option("--no-build", "deploy the existing build output without rebuilding").option(
7047
+ "--remote-db",
7048
+ "render the build against the database on the server, over an SSH tunnel \u2014 needed when pages are prerendered from data"
7049
+ ).action(
7050
+ (raw) => runCommand(async () => {
7051
+ const options = parseOptions(OptionsSchema3, raw);
7052
+ const backend3 = hasBackend();
7053
+ const release = releaseId();
7054
+ p31.intro(pc9.bgCyan(pc9.black(" vela deploy ")));
7055
+ await withTarget(
7056
+ raw,
7057
+ {
7058
+ remote: async (ctx) => {
7059
+ const { session, instance, workspaceRootDir, config } = ctx;
7060
+ p31.log.info(
7061
+ `${pc9.cyan(ctx.appName)} ${pc9.dim("\u2192")} ${pc9.cyan(ctx.targetName)} ${pc9.dim(`(${ctx.server})`)}`
7062
+ );
7063
+ const [existing] = await readInstanceStates(session, instance);
7064
+ const domain = options.domain ?? ctx.binding.domain ?? config.deploy?.domain ?? existing?.domain ?? "";
7065
+ const remoteDb = options.remoteDb ?? config.deploy?.buildAgainstRemote ?? false;
7066
+ if (options.build !== false) {
7067
+ let buildEnv = {};
7068
+ let tunnel = null;
7069
+ if (backend3 && remoteDb) {
7070
+ tunnel = await openDatabaseTunnel(session, instance, existing);
7071
+ buildEnv = tunnel.env;
7072
+ p31.log.info(
7073
+ `Building against the ${pc9.cyan(ctx.targetName)} database on ${ctx.server} ${pc9.dim(`(port ${tunnel.pbPort})`)}`
7074
+ );
7075
+ } else if (backend3) {
7076
+ await ensureSuperuser(workspaceRootDir);
7077
+ }
7078
+ p31.log.step("Building");
7079
+ try {
7080
+ await runBuild(workspaceRootDir, config.deploy?.buildCommand, buildEnv);
7081
+ } finally {
7082
+ if (tunnel) await tunnel.close();
7083
+ }
7084
+ }
7085
+ const entries = collectArtifact(workspaceRootDir, config.deploy ?? {});
7086
+ const sha = await gitSha(workspaceRootDir);
7087
+ p31.log.step(`Uploading release ${pc9.dim(release)}`);
7088
+ await uploadRelease(session, instance, release, entries);
7089
+ p31.log.step("Activating");
7090
+ const result = await runServerScript(session, "apply.sh", {
7091
+ args: [
7092
+ instance,
7093
+ release,
7094
+ "--name",
7095
+ ctx.appName,
7096
+ "--app-id",
7097
+ ctx.appId,
7098
+ "--env",
7099
+ ctx.envTag,
7100
+ "--domain",
7101
+ domain,
7102
+ "--health-path",
7103
+ options.healthPath ?? config.deploy?.healthCheckPath ?? "/",
7104
+ "--keep",
7105
+ options.keep ?? String(config.deploy?.keepReleases ?? 5),
7106
+ "--backend",
7107
+ backend3 ? "1" : "0",
7108
+ "--pb-version",
7109
+ options.pbVersion ?? config.deploy?.pocketbaseVersion ?? pocketbaseVersion(),
7110
+ "--git-sha",
7111
+ sha
7112
+ ],
7113
+ stream: true
7114
+ });
7115
+ writeBinding(workspaceRootDir, ctx.envTag, {
7116
+ server: ctx.server,
7117
+ domain: domain || void 0
7118
+ });
7119
+ if (!existing) await reportEmptyEnvironment(session, instance, workspaceRootDir);
7120
+ const url = result?.url ?? "";
7121
+ p31.log.success(
7122
+ `Deployed ${pc9.cyan(ctx.appName)} ${pc9.dim(release)}
7123
+
7124
+ URL ${url}
7125
+ Port ${result?.webPort ?? "?"}${backend3 ? ` (PocketBase ${result?.pbPort ?? "?"})` : ""}`
7126
+ );
7127
+ if (result?.superuserCreated) {
7128
+ await copyLocalBranding(
7129
+ session,
7130
+ instance,
7131
+ workspaceRootDir,
7132
+ domain ? result?.url ?? "" : ""
7133
+ );
7134
+ p31.log.info(
7135
+ `Created the PocketBase superuser this app authenticates as.
7136
+
7137
+ Its credentials are stored in the environment on the server. To use
7138
+ your own instead, ${pc9.cyan("vela env set POCKETBASE_SUPERUSER_PASSWORD")}
7139
+ and deploy again.`
7140
+ );
7141
+ }
7142
+ if (!domain) {
7143
+ p31.log.warn(
7144
+ `No domain configured, so nothing is proxied to this app yet.
7145
+ Redeploy with ${pc9.cyan("--domain example.com")} once DNS points at ${ctx.server}.`
7146
+ );
7147
+ }
7148
+ }
7149
+ },
7150
+ { project: options.project, askDomain: true, label: "deploy" }
7151
+ );
7152
+ p31.outro(`${pc9.cyan("vela status")} to see what is running`);
7153
+ }, "Failed to deploy.")
7154
+ );
7155
+ async function copyLocalBranding(session, instance, workspaceRootDir, appURL) {
7156
+ const local = await readLocalMeta(workspaceRootDir);
7157
+ if (!local) return;
5751
7158
  try {
5752
- const pm = (await detect5({ cwd }))?.name ?? "npm";
5753
- const resolved = resolveCommand5(pm, "execute", ["vite", "build"]);
5754
- const args = resolved.args.slice();
5755
- if (pm === "npm") args.unshift("--yes");
5756
- await x3(resolved.command, args, {
5757
- nodeOptions: { cwd, stdio: "inherit" },
5758
- throwOnError: true
5759
- });
5760
- } finally {
5761
- cleanup();
7159
+ const copied = await seedRemoteMeta(session, instance, local, appURL);
7160
+ if (copied.length === 0) return;
7161
+ const outcome = await restartInstance(session, instance);
7162
+ if (outcome.deployed && !outcome.restarted) {
7163
+ p31.log.warn(
7164
+ `Copied ${copied.join(", ")}, but the app did not restart to pick them up.
7165
+ ${pc9.dim(outcome.error ?? "")}`
7166
+ );
7167
+ return;
7168
+ }
7169
+ p31.log.success(`Copied ${copied.join(", ")} from this project's database`);
7170
+ } catch (err) {
7171
+ p31.log.warn(
7172
+ `Could not copy this project's PocketBase settings across.
7173
+ Set them in the admin panel instead. ${pc9.dim(String(err))}`
7174
+ );
5762
7175
  }
5763
- });
7176
+ }
7177
+ async function openDatabaseTunnel(session, instance, state) {
7178
+ const pbPort = state?.pbPort;
7179
+ if (!pbPort) {
7180
+ throw new Error(
7181
+ `--remote-db needs an existing deployment to build against, and ${instance} has not been deployed yet.
5764
7182
 
5765
- // src/commands/preview.ts
5766
- import path31 from "node:path";
5767
- import process19 from "node:process";
5768
- import { Command as Command67 } from "commander";
5769
- import { x as x4 } from "tinyexec";
5770
- import { detect as detect6 } from "package-manager-detector";
5771
- import { resolveCommand as resolveCommand6 } from "package-manager-detector/commands";
5772
- var preview = new Command67("preview").description("preview the built app").configureHelp(helpConfig).action(async () => {
5773
- const cwd = process19.cwd();
5774
- let pbProc;
5775
- const needsStart = hasBackend(cwd) && !process19.env.POCKETBASE_URL;
5776
- const cleanup = () => {
5777
- if (pbProc?.pid) pbProc.kill();
7183
+ Deploy once without it, then turn it on.`
7184
+ );
7185
+ }
7186
+ const remoteEnv = await readRemoteEnv(session, instance);
7187
+ const email3 = remoteEnv.POCKETBASE_SUPERUSER_EMAIL;
7188
+ const password10 = remoteEnv.POCKETBASE_SUPERUSER_PASSWORD;
7189
+ if (!email3 || !password10) {
7190
+ throw new Error(
7191
+ `--remote-db renders the build as the superuser of ${instance}, and that environment has no credentials for one.
7192
+
7193
+ Set them with \`vela env set POCKETBASE_SUPERUSER_EMAIL\` and \`vela env set POCKETBASE_SUPERUSER_PASSWORD\`.`
7194
+ );
7195
+ }
7196
+ const localPort = await findFreePort("127.0.0.1");
7197
+ await session.forwardLocalPort(localPort, "127.0.0.1", pbPort);
7198
+ return {
7199
+ pbPort,
7200
+ env: {
7201
+ POCKETBASE_URL: `http://127.0.0.1:${localPort}`,
7202
+ POCKETBASE_SUPERUSER_EMAIL: email3,
7203
+ POCKETBASE_SUPERUSER_PASSWORD: password10
7204
+ },
7205
+ close: () => session.cancelForward(localPort, "127.0.0.1", pbPort)
5778
7206
  };
5779
- if (needsStart) {
5780
- const dataDir = path31.join(cwd, DATA_DIR);
5781
- const started = await startPocketbaseServe({
5782
- dataDir,
5783
- migrationsDir: MIGRATIONS_DIR,
5784
- hooksDir: path31.join(dataDir, "hooks"),
5785
- dev: true
5786
- });
5787
- pbProc = started.proc;
5788
- process19.env.POCKETBASE_URL = started.url;
5789
- process19.on("exit", cleanup);
5790
- process19.on("SIGINT", () => {
5791
- cleanup();
5792
- process19.exit(0);
5793
- });
7207
+ }
7208
+ async function uploadRelease(session, instance, release, entries) {
7209
+ const dir = remotePaths.release(instance, release);
7210
+ await session.script(`mkdir -p "$1"`, { args: [dir] });
7211
+ const byTarget = /* @__PURE__ */ new Map();
7212
+ for (const entry of entries) {
7213
+ const target = entry.remoteDir ? `${dir}/${entry.remoteDir}` : dir;
7214
+ const source = entry.remoteDir && isDirectory(entry.localPath) ? `${entry.localPath}/` : entry.localPath;
7215
+ byTarget.set(target, [...byTarget.get(target) ?? [], source]);
7216
+ }
7217
+ for (const [target, sources] of byTarget) {
7218
+ if (target !== dir) await session.script(`mkdir -p "$1"`, { args: [target] });
7219
+ await session.upload(sources, target);
5794
7220
  }
7221
+ }
7222
+ function isDirectory(target) {
5795
7223
  try {
5796
- const pm = (await detect6({ cwd }))?.name ?? "npm";
5797
- const resolved = resolveCommand6(pm, "execute", ["vite", "preview"]);
5798
- const args = resolved.args.slice();
5799
- if (pm === "npm") args.unshift("--yes");
5800
- await x4(resolved.command, args, {
5801
- nodeOptions: { cwd, stdio: "inherit" },
5802
- throwOnError: true
5803
- });
5804
- } finally {
5805
- cleanup();
7224
+ return fs33.statSync(target).isDirectory();
7225
+ } catch {
7226
+ return false;
5806
7227
  }
5807
- });
5808
-
5809
- // src/commands/sync.ts
5810
- import path32 from "node:path";
5811
- import { Command as Command68 } from "commander";
5812
- var sync = new Command68("sync").description("sync types from the database").configureHelp(helpConfig).action(
5813
- () => runCommand(async () => {
5814
- const { workspaceRootDir } = await getWorkspace();
5815
- const typesDir = path32.join(workspaceRootDir, ".svelte-kit", "types");
5816
- const { processTypes } = await import("@velastack/pocketbase-codegen");
5817
- await withPocketbase(workspaceRootDir, async (pb) => {
5818
- await processTypes(pb, typesDir);
5819
- });
5820
- console.log("types synced");
5821
- }, "Failed to sync types.")
5822
- );
7228
+ }
7229
+ async function reportEmptyEnvironment(session, instance, workspaceRootDir) {
7230
+ const remote = await readRemoteEnv(session, instance);
7231
+ if (Object.keys(remote).length > 0) return;
7232
+ if (!fs33.existsSync(path38.join(workspaceRootDir, ".env"))) return;
7233
+ p31.log.warn(
7234
+ `This app has no production environment variables yet.
5823
7235
 
5824
- // src/commands/provision.ts
5825
- var provision = stubCommand("provision", "provision cloud resources");
7236
+ Local ${pc9.cyan(".env")} values are not uploaded by a deploy. Set them with
7237
+ ${pc9.cyan("vela env set KEY")}, or copy a file across with ${pc9.cyan("vela env import .env.production")}.`
7238
+ );
7239
+ }
5826
7240
 
5827
- // src/commands/deploy.ts
5828
- import path33 from "node:path";
5829
- import process20 from "node:process";
5830
- import { Command as Command69 } from "commander";
5831
- import * as p28 from "@clack/prompts";
7241
+ // src/commands/link.ts
7242
+ import path39 from "node:path";
7243
+ import process24 from "node:process";
7244
+ import { Command as Command72 } from "commander";
7245
+ import * as p32 from "@clack/prompts";
5832
7246
 
5833
7247
  // src/lib/velastack-api.ts
5834
7248
  async function apiFetch(apiKey, pathAndQuery, init) {
@@ -5877,14 +7291,14 @@ async function createProject2(apiKey, args) {
5877
7291
  });
5878
7292
  }
5879
7293
 
5880
- // src/commands/deploy.ts
7294
+ // src/commands/link.ts
5881
7295
  var CREATE_NEW = "__new__";
5882
- var deploy = new Command69("deploy").description("deploy the app").configureHelp(helpConfig).action(() => runCommand(linkProject, "Failed to deploy."));
7296
+ var link = new Command72("link").description("link this project to a velastack.dev project").configureHelp(helpConfig).action(() => runCommand(linkProject, "Failed to link the project."));
5883
7297
  async function linkProject() {
5884
7298
  const { workspaceRootDir } = await getWorkspace();
5885
7299
  const existing = readProjectConfig(workspaceRootDir);
5886
7300
  if (existing) {
5887
- p28.log.success(`Linked to ${existing.projectName}.`);
7301
+ p32.log.success(`Linked to ${existing.projectName}.`);
5888
7302
  return;
5889
7303
  }
5890
7304
  const apiKey = requireApiKey();
@@ -5911,10 +7325,10 @@ async function linkProject() {
5911
7325
  projectName = created.name;
5912
7326
  }
5913
7327
  writeProjectConfig(workspaceRootDir, { projectId, teamId, projectName });
5914
- p28.log.success(`Linked to ${projectName}.`);
7328
+ p32.log.success(`Linked to ${projectName}.`);
5915
7329
  }
5916
7330
  async function pickExistingProject(projects) {
5917
- const choice = await p28.select({
7331
+ const choice = await p32.select({
5918
7332
  message: "Select a project",
5919
7333
  options: [
5920
7334
  ...projects.map((pr) => ({
@@ -5924,79 +7338,655 @@ async function pickExistingProject(projects) {
5924
7338
  { value: CREATE_NEW, label: "Create a new project" }
5925
7339
  ]
5926
7340
  });
5927
- if (p28.isCancel(choice)) {
5928
- p28.cancel("Operation cancelled.");
5929
- process20.exit(0);
7341
+ if (p32.isCancel(choice)) {
7342
+ p32.cancel("Operation cancelled.");
7343
+ process24.exit(0);
5930
7344
  }
5931
7345
  return choice;
5932
7346
  }
5933
7347
  async function pickTeam(teams3) {
5934
7348
  if (teams3.length === 1) return teams3[0];
5935
- const choice = await p28.select({
7349
+ const choice = await p32.select({
5936
7350
  message: "Select a team",
5937
7351
  options: teams3.map((team) => ({
5938
7352
  value: team.id,
5939
7353
  label: team.is_personal ? `${team.name} (personal)` : team.name
5940
7354
  }))
5941
7355
  });
5942
- if (p28.isCancel(choice)) {
5943
- p28.cancel("Operation cancelled.");
5944
- process20.exit(0);
7356
+ if (p32.isCancel(choice)) {
7357
+ p32.cancel("Operation cancelled.");
7358
+ process24.exit(0);
5945
7359
  }
5946
7360
  return teams3.find((team) => team.id === choice);
5947
7361
  }
5948
7362
  async function promptProjectName(workspaceRootDir) {
5949
- const defaultValue = defaultProjectName(workspaceRootDir);
5950
- const value = await p28.text({
7363
+ const defaultValue = defaultProjectName2(workspaceRootDir);
7364
+ const value = await p32.text({
5951
7365
  message: "Project name",
5952
7366
  defaultValue,
5953
7367
  initialValue: defaultValue,
5954
7368
  placeholder: defaultValue,
5955
- validate: (v4) => !v4?.trim() ? "Required" : void 0
7369
+ validate: (v9) => !v9?.trim() ? "Required" : void 0
5956
7370
  });
5957
- if (p28.isCancel(value)) {
5958
- p28.cancel("Operation cancelled.");
5959
- process20.exit(0);
7371
+ if (p32.isCancel(value)) {
7372
+ p32.cancel("Operation cancelled.");
7373
+ process24.exit(0);
5960
7374
  }
5961
7375
  return value.trim();
5962
7376
  }
5963
- function defaultProjectName(workspaceRootDir) {
7377
+ function defaultProjectName2(workspaceRootDir) {
5964
7378
  try {
5965
- const pkg = readPackageJson(path33.join(workspaceRootDir, "package.json"));
7379
+ const pkg = readPackageJson(path39.join(workspaceRootDir, "package.json"));
5966
7380
  const name = pkg.name;
5967
7381
  if (typeof name === "string" && name.trim()) return name.trim();
5968
7382
  } catch {
5969
7383
  }
5970
- return path33.basename(workspaceRootDir);
7384
+ return path39.basename(workspaceRootDir);
7385
+ }
7386
+
7387
+ // src/commands/env.ts
7388
+ import { Command as Command77 } from "commander";
7389
+
7390
+ // src/commands/env/list.ts
7391
+ import { Command as Command73 } from "commander";
7392
+ import * as p34 from "@clack/prompts";
7393
+ import pc11 from "picocolors";
7394
+
7395
+ // src/lib/local-env.ts
7396
+ import fs34 from "node:fs";
7397
+ import * as p33 from "@clack/prompts";
7398
+ import pc10 from "picocolors";
7399
+ function readLocalEnv(envFile) {
7400
+ if (!fs34.existsSync(envFile)) return {};
7401
+ return readLocalEnvFile(envFile);
7402
+ }
7403
+ function editLocalEnv(envFile, edit) {
7404
+ const before = fs34.existsSync(envFile) ? fs34.readFileSync(envFile, "utf8") : "";
7405
+ const after = edit(before);
7406
+ if (after !== before) fs34.writeFileSync(envFile, after);
7407
+ }
7408
+ function setLocalEnv(envFile, key, value) {
7409
+ editLocalEnv(envFile, (content) => upsertEnvVar(content, key, value));
7410
+ }
7411
+ function unsetLocalEnv(envFile, key) {
7412
+ editLocalEnv(envFile, (content) => removeEnvVar(content, key));
7413
+ }
7414
+ async function applyLocalEnvChange(ctx, changed) {
7415
+ if (touchesSuperuser(changed)) {
7416
+ const env2 = readLocalEnv(ctx.envFile);
7417
+ const email3 = env2.POCKETBASE_SUPERUSER_EMAIL;
7418
+ const password10 = env2.POCKETBASE_SUPERUSER_PASSWORD;
7419
+ if (email3 && password10) {
7420
+ process.env.POCKETBASE_SUPERUSER_EMAIL = email3;
7421
+ process.env.POCKETBASE_SUPERUSER_PASSWORD = password10;
7422
+ await ensureSuperuser(ctx.workspaceRootDir);
7423
+ p33.log.success("Local superuser updated to match");
7424
+ } else {
7425
+ p33.log.warn(
7426
+ `The local database still has the old superuser.
7427
+ Set both ${pc10.cyan("POCKETBASE_SUPERUSER_EMAIL")} and ${pc10.cyan("POCKETBASE_SUPERUSER_PASSWORD")} to reconcile it.`
7428
+ );
7429
+ }
7430
+ }
7431
+ if (getPocketbaseMetadata(ctx.workspaceRootDir)) {
7432
+ p33.log.info(`Restart ${pc10.cyan("vela dev")} to pick this up.`);
7433
+ }
7434
+ }
7435
+
7436
+ // src/commands/env/list.ts
7437
+ var envList = addTargetOptions(
7438
+ new Command73("list").description("list environment variable names").configureHelp(helpConfig),
7439
+ "local"
7440
+ ).action(
7441
+ (raw) => runCommand(
7442
+ () => withTarget(
7443
+ raw,
7444
+ {
7445
+ local: async (ctx) => {
7446
+ report(Object.keys(readLocalEnv(ctx.envFile)), `${ctx.appName}, local (.env)`);
7447
+ },
7448
+ remote: async (ctx) => {
7449
+ const env2 = await readRemoteEnv(ctx.session, ctx.instance);
7450
+ report(Object.keys(env2), `${ctx.appName}, ${ctx.targetName}`);
7451
+ }
7452
+ },
7453
+ { label: "env list" }
7454
+ ),
7455
+ "Failed to read the environment."
7456
+ )
7457
+ );
7458
+ function report(keys, where) {
7459
+ if (keys.length === 0) {
7460
+ p34.log.info(`No environment variables configured ${pc11.dim(`(${where})`)}.`);
7461
+ return;
7462
+ }
7463
+ p34.log.info(
7464
+ `Environment ${pc11.dim(`(${where})`)}
7465
+
7466
+ ` + keys.sort().map((key) => ` ${key}`).join("\n")
7467
+ );
7468
+ }
7469
+
7470
+ // src/commands/env/set.ts
7471
+ import process25 from "node:process";
7472
+ import { Command as Command74 } from "commander";
7473
+ import * as p35 from "@clack/prompts";
7474
+ import pc12 from "picocolors";
7475
+ var envSet = addTargetOptions(
7476
+ new Command74("set").description("set an environment variable").argument("<key>", "variable name").argument("[value]", "value \u2014 prompted for, without echo, when omitted").configureHelp(helpConfig),
7477
+ "local"
7478
+ ).action(
7479
+ (key, value, raw) => runCommand(
7480
+ () => withTarget(
7481
+ raw,
7482
+ {
7483
+ local: async (ctx) => {
7484
+ const resolved = await resolveValue(key, value);
7485
+ setLocalEnv(ctx.envFile, key, resolved);
7486
+ p35.log.success(`${key} updated ${pc12.dim("(local)")}`);
7487
+ await applyLocalEnvChange(ctx, [key]);
7488
+ },
7489
+ remote: async (ctx) => {
7490
+ const resolved = await resolveValue(key, value);
7491
+ const env2 = await readRemoteEnv(ctx.session, ctx.instance);
7492
+ await writeRemoteEnv(ctx.session, ctx.instance, { ...env2, [key]: resolved });
7493
+ p35.log.success(`${key} updated ${pc12.dim(`(${ctx.targetName})`)}`);
7494
+ await applyEnvRestart(ctx, [key]);
7495
+ }
7496
+ },
7497
+ { label: "env set" }
7498
+ ),
7499
+ "Failed to set the variable."
7500
+ )
7501
+ );
7502
+ async function resolveValue(key, value) {
7503
+ if (!isValidKey(key)) throw new Error(`${key} is not a valid environment variable name.`);
7504
+ return value ?? await promptValue(key);
7505
+ }
7506
+ async function promptValue(key) {
7507
+ const value = await p35.password({
7508
+ message: `Value for ${pc12.cyan(key)}`,
7509
+ validate: (input) => !input?.length ? "Required" : void 0
7510
+ });
7511
+ if (p35.isCancel(value)) {
7512
+ p35.cancel("Operation cancelled.");
7513
+ process25.exit(0);
7514
+ }
7515
+ return value;
7516
+ }
7517
+
7518
+ // src/commands/env/unset.ts
7519
+ import { Command as Command75 } from "commander";
7520
+ import * as p36 from "@clack/prompts";
7521
+ import pc13 from "picocolors";
7522
+ var envUnset = addTargetOptions(
7523
+ new Command75("unset").description("remove an environment variable").argument("<key>", "variable name").configureHelp(helpConfig),
7524
+ "local"
7525
+ ).action(
7526
+ (key, raw) => runCommand(
7527
+ () => withTarget(
7528
+ raw,
7529
+ {
7530
+ local: async (ctx) => {
7531
+ if (!(key in readLocalEnv(ctx.envFile))) {
7532
+ p36.log.info(`${key} is not set \u2014 nothing to remove.`);
7533
+ return;
7534
+ }
7535
+ unsetLocalEnv(ctx.envFile, key);
7536
+ p36.log.success(`${key} removed ${pc13.dim("(local)")}`);
7537
+ await applyLocalEnvChange(ctx, [key]);
7538
+ },
7539
+ remote: async (ctx) => {
7540
+ const env2 = await readRemoteEnv(ctx.session, ctx.instance);
7541
+ if (!(key in env2)) {
7542
+ p36.log.info(`${key} is not set \u2014 nothing to remove.`);
7543
+ return;
7544
+ }
7545
+ delete env2[key];
7546
+ await writeRemoteEnv(ctx.session, ctx.instance, env2);
7547
+ p36.log.success(`${key} removed ${pc13.dim(`(${ctx.targetName})`)}`);
7548
+ await applyEnvRestart(ctx, [key]);
7549
+ }
7550
+ },
7551
+ { label: "env unset" }
7552
+ ),
7553
+ "Failed to remove the variable."
7554
+ )
7555
+ );
7556
+
7557
+ // src/commands/env/import.ts
7558
+ import fs35 from "node:fs";
7559
+ import path40 from "node:path";
7560
+ import process26 from "node:process";
7561
+ import { Command as Command76 } from "commander";
7562
+ import * as p37 from "@clack/prompts";
7563
+ import pc14 from "picocolors";
7564
+ var envImport = addTargetOptions(
7565
+ new Command76("import").description("merge a dotenv file into the environment").argument("<file>", "dotenv file to read").configureHelp(helpConfig),
7566
+ "local"
7567
+ ).action(
7568
+ (file, raw) => runCommand(
7569
+ () => withTarget(
7570
+ raw,
7571
+ {
7572
+ local: async (ctx) => {
7573
+ const source = resolve(file);
7574
+ if (source === ctx.envFile) {
7575
+ throw new Error(`${file} is the file you would be importing into.`);
7576
+ }
7577
+ const incoming = read(source, file);
7578
+ const keys = Object.keys(incoming);
7579
+ if (keys.length === 0) return;
7580
+ p37.log.step(`Importing ${keys.length} variable(s) from ${pc14.cyan(file)}`);
7581
+ editLocalEnv(
7582
+ ctx.envFile,
7583
+ (content) => keys.reduce((acc, key) => upsertEnvVar(acc, key, incoming[key]), content)
7584
+ );
7585
+ p37.log.success(`${keys.length} variable(s) updated ${pc14.dim("(local)")}`);
7586
+ await applyLocalEnvChange(ctx, keys);
7587
+ },
7588
+ remote: async (ctx) => {
7589
+ const incoming = read(resolve(file), file);
7590
+ const keys = Object.keys(incoming);
7591
+ if (keys.length === 0) return;
7592
+ p37.log.step(`Importing ${keys.length} variable(s) from ${pc14.cyan(file)}`);
7593
+ const existing = await readRemoteEnv(ctx.session, ctx.instance);
7594
+ await writeRemoteEnv(ctx.session, ctx.instance, { ...existing, ...incoming });
7595
+ p37.log.success(`${keys.length} variable(s) updated ${pc14.dim(`(${ctx.targetName})`)}`);
7596
+ await applyEnvRestart(ctx, keys);
7597
+ }
7598
+ },
7599
+ { label: "env import" }
7600
+ ),
7601
+ "Failed to import the environment."
7602
+ )
7603
+ );
7604
+ function resolve(file) {
7605
+ return path40.resolve(process26.cwd(), file);
7606
+ }
7607
+ function read(resolved, shown) {
7608
+ if (!fs35.existsSync(resolved)) throw new Error(`${shown} does not exist.`);
7609
+ const incoming = readLocalEnvFile(resolved);
7610
+ if (Object.keys(incoming).length === 0) p37.log.info(`${shown} has no variables to import.`);
7611
+ return incoming;
7612
+ }
7613
+
7614
+ // src/commands/env.ts
7615
+ var env = new Command77("env").description("manage environment variables, locally or on a target").configureHelp(helpConfig).addCommand(envList).addCommand(envSet).addCommand(envUnset).addCommand(envImport);
7616
+
7617
+ // src/commands/status.ts
7618
+ import { Command as Command78 } from "commander";
7619
+ import * as p38 from "@clack/prompts";
7620
+ import pc15 from "picocolors";
7621
+ var status = addTargetOptions(
7622
+ new Command78("status").description("show what is deployed").configureHelp(helpConfig),
7623
+ "production"
7624
+ ).option("--all", "show every app on the server, not just this project").option("--json", "print raw JSON").action(
7625
+ (raw) => runCommand(async () => {
7626
+ const options = raw;
7627
+ if (options.all) {
7628
+ await withServerSession(raw, async (session) => {
7629
+ report2(await readInstanceStates(session), options.json);
7630
+ });
7631
+ return;
7632
+ }
7633
+ await withTarget(
7634
+ raw,
7635
+ {
7636
+ remote: async (ctx) => {
7637
+ report2(await readInstanceStates(ctx.session, ctx.instance), options.json);
7638
+ }
7639
+ },
7640
+ {
7641
+ label: "status",
7642
+ localHint: "Nothing is deployed locally \u2014 `vela dev` reports what it is running."
7643
+ }
7644
+ );
7645
+ }, "Failed to read status.")
7646
+ );
7647
+ function report2(states, json) {
7648
+ if (json) {
7649
+ console.log(JSON.stringify(states, null, 2));
7650
+ return;
7651
+ }
7652
+ if (states.length === 0) {
7653
+ p38.log.info("Nothing is deployed here yet.");
7654
+ return;
7655
+ }
7656
+ for (const state of states) {
7657
+ p38.log.info(describe(state));
7658
+ }
7659
+ }
7660
+ function describe(state) {
7661
+ const health = (value) => value === "active" ? pc15.green(value) : pc15.red(value || "inactive");
7662
+ const rows = [
7663
+ ["Instance", state.instance],
7664
+ ["Environment", state.env],
7665
+ ["Release", state.activeRelease ?? "\u2014"],
7666
+ ["Previous", state.previousRelease || "\u2014"],
7667
+ ["Domain", state.domain || "\u2014"],
7668
+ ["Ports", `web ${state.webPort ?? "?"}${state.backend ? `, pb ${state.pbPort ?? "?"}` : ""}`],
7669
+ ["App", health(state.services?.web)],
7670
+ ...state.backend ? [["PocketBase", health(state.services?.pocketbase)]] : [],
7671
+ ["Deployed", state.deployedAt ?? "\u2014"],
7672
+ ...state.rolledBackAt ? [["Rolled back", state.rolledBackAt]] : [],
7673
+ ...state.gitSha ? [["Commit", state.gitSha.slice(0, 12)]] : []
7674
+ ];
7675
+ const width = Math.max(...rows.map(([label]) => label.length));
7676
+ return pc15.cyan(state.name) + "\n\n" + rows.map(([label, value]) => ` ${label.padEnd(width)} ${value}`).join("\n");
7677
+ }
7678
+
7679
+ // src/commands/rollback.ts
7680
+ import { Command as Command79 } from "commander";
7681
+ import * as p39 from "@clack/prompts";
7682
+ import pc16 from "picocolors";
7683
+ var rollback = addTargetOptions(
7684
+ new Command79("rollback").description("put the previous release back").configureHelp(helpConfig),
7685
+ "production"
7686
+ ).option("--to <release>", "roll back to a specific release instead of the previous one").action(
7687
+ (raw) => runCommand(async () => {
7688
+ const options = raw;
7689
+ await withTarget(
7690
+ raw,
7691
+ {
7692
+ remote: async (ctx) => {
7693
+ p39.log.step(
7694
+ `Rolling back ${pc16.cyan(ctx.appName)} ${pc16.dim(`(${ctx.targetName})`)} on ${ctx.server}`
7695
+ );
7696
+ const result = await runServerScript(
7697
+ ctx.session,
7698
+ "rollback.sh",
7699
+ {
7700
+ args: [ctx.instance, ...options.to ? ["--to", options.to] : []],
7701
+ stream: true
7702
+ }
7703
+ );
7704
+ p39.log.success(
7705
+ `Rolled back to ${pc16.cyan(result?.release ?? "the previous release")}` + (result?.from ? ` ${pc16.dim(`(was ${result.from})`)}` : "")
7706
+ );
7707
+ }
7708
+ },
7709
+ {
7710
+ label: "rollback",
7711
+ localHint: "Nothing is released locally \u2014 `vela dev` always runs the working tree."
7712
+ }
7713
+ );
7714
+ }, "Failed to roll back.")
7715
+ );
7716
+
7717
+ // src/commands/logs.ts
7718
+ import { Command as Command80 } from "commander";
7719
+ var logs = addTargetOptions(
7720
+ new Command80("logs").description("tail the logs of a deployed app").configureHelp(helpConfig),
7721
+ "production"
7722
+ ).option("-f, --follow", "keep streaming new output").option("-n, --lines <count>", "how many lines of history to show", "100").option("--pocketbase", "show the PocketBase service instead of the app").action(
7723
+ (raw) => runCommand(async () => {
7724
+ const options = raw;
7725
+ await withTarget(
7726
+ raw,
7727
+ {
7728
+ remote: async (ctx) => {
7729
+ const unit = options.pocketbase ? remotePaths.pbUnit(ctx.instance) : remotePaths.webUnit(ctx.instance);
7730
+ const command = [
7731
+ "journalctl",
7732
+ "-u",
7733
+ unit,
7734
+ "-n",
7735
+ options.lines ?? "100",
7736
+ "--no-pager",
7737
+ ...options.follow ? ["-f"] : []
7738
+ ];
7739
+ const code = await ctx.session.interactive(command);
7740
+ if (code !== 0 && !options.follow) {
7741
+ throw new Error(`journalctl exited ${code}.`);
7742
+ }
7743
+ }
7744
+ },
7745
+ {
7746
+ label: "logs",
7747
+ localHint: "There are no logs for the copy on this machine \u2014 `vela dev` prints them as it runs."
7748
+ }
7749
+ );
7750
+ }, "Failed to read logs.")
7751
+ );
7752
+
7753
+ // src/commands/admin.ts
7754
+ import { Command as Command82 } from "commander";
7755
+
7756
+ // src/commands/admin/create.ts
7757
+ import process27 from "node:process";
7758
+ import { Command as Command81 } from "commander";
7759
+ import * as p40 from "@clack/prompts";
7760
+ import pc17 from "picocolors";
7761
+ var MIN_PASSWORD = 10;
7762
+ var adminCreate = addTargetOptions(
7763
+ new Command81("create").description("create a login for the admin panel").argument("[email]", "email to sign in with \u2014 prompted for when omitted").configureHelp(helpConfig),
7764
+ "local"
7765
+ ).action(
7766
+ (email3, raw) => runCommand(
7767
+ () => withTarget(
7768
+ raw,
7769
+ {
7770
+ local: async (ctx) => {
7771
+ const creds = readLocalEnv(ctx.envFile);
7772
+ const address = email3 ?? await promptEmail();
7773
+ const password10 = await promptPassword2();
7774
+ let signIn = "";
7775
+ await withPocketbase(
7776
+ ctx.workspaceRootDir,
7777
+ async (pb) => {
7778
+ await upsertSuperuser(pb, address, password10);
7779
+ const settings = await pb.settings.getAll();
7780
+ signIn = settings.meta?.appURL ?? "";
7781
+ },
7782
+ {
7783
+ email: creds.POCKETBASE_SUPERUSER_EMAIL,
7784
+ password: creds.POCKETBASE_SUPERUSER_PASSWORD
7785
+ }
7786
+ );
7787
+ if (!signIn) {
7788
+ const metadata = getPocketbaseMetadata(ctx.workspaceRootDir);
7789
+ if (metadata) signIn = `http://${metadata.viteHost}:${metadata.vitePort}`;
7790
+ }
7791
+ p40.log.info(
7792
+ signIn ? `Sign in at ${pc17.cyan(`${signIn}/admin`)}` : `Sign in at ${pc17.cyan("/admin")} once ${pc17.cyan("vela dev")} is running.`
7793
+ );
7794
+ },
7795
+ remote: async (ctx) => {
7796
+ const address = email3 ?? await promptEmail();
7797
+ const password10 = await promptPassword2();
7798
+ const [state] = await readInstanceStates(ctx.session, ctx.instance);
7799
+ await withRemotePocketbase(
7800
+ ctx.session,
7801
+ ctx.instance,
7802
+ (pb) => upsertSuperuser(pb, address, password10)
7803
+ );
7804
+ const base2 = state?.domain ? `https://${state.domain.split(",")[0].trim()}` : "";
7805
+ p40.log.info(
7806
+ base2 ? `Sign in at ${pc17.cyan(`${base2}/admin`)}` : `Sign in at ${pc17.cyan("/admin")} once a domain is configured for this target.`
7807
+ );
7808
+ }
7809
+ },
7810
+ { label: "admin create" }
7811
+ ),
7812
+ "Failed to create the admin login."
7813
+ )
7814
+ );
7815
+ async function upsertSuperuser(pb, email3, password10) {
7816
+ const existing = await findSuperuser(pb, email3);
7817
+ if (existing) {
7818
+ const confirmed = await p40.confirm({
7819
+ message: `${email3} can already sign in. Reset its password?`,
7820
+ initialValue: false
7821
+ });
7822
+ if (p40.isCancel(confirmed) || !confirmed) {
7823
+ p40.cancel("Operation cancelled.");
7824
+ process27.exit(0);
7825
+ }
7826
+ await pb.collection("_superusers").update(existing, { password: password10, passwordConfirm: password10 });
7827
+ p40.log.success(`Password reset for ${pc17.cyan(email3)}`);
7828
+ return;
7829
+ }
7830
+ await pb.collection("_superusers").create({ email: email3, password: password10, passwordConfirm: password10 });
7831
+ p40.log.success(`${pc17.cyan(email3)} can now sign in`);
7832
+ }
7833
+ async function findSuperuser(pb, email3) {
7834
+ try {
7835
+ const record = await pb.collection("_superusers").getFirstListItem(pb.filter("email = {:email}", { email: email3 }));
7836
+ return record.id;
7837
+ } catch {
7838
+ return null;
7839
+ }
7840
+ }
7841
+ async function promptEmail() {
7842
+ const value = await p40.text({
7843
+ message: "Email to sign in with",
7844
+ validate: (input) => input?.includes("@") ? void 0 : "An email address is required"
7845
+ });
7846
+ if (p40.isCancel(value)) {
7847
+ p40.cancel("Operation cancelled.");
7848
+ process27.exit(0);
7849
+ }
7850
+ return value.trim();
7851
+ }
7852
+ async function promptPassword2() {
7853
+ const value = await p40.password({
7854
+ message: "Password",
7855
+ validate: (input) => (input?.length ?? 0) >= MIN_PASSWORD ? void 0 : `At least ${MIN_PASSWORD} characters is required`
7856
+ });
7857
+ if (p40.isCancel(value)) {
7858
+ p40.cancel("Operation cancelled.");
7859
+ process27.exit(0);
7860
+ }
7861
+ const again = await p40.password({
7862
+ message: "Password again",
7863
+ validate: (input) => input === value ? void 0 : "The two do not match"
7864
+ });
7865
+ if (p40.isCancel(again)) {
7866
+ p40.cancel("Operation cancelled.");
7867
+ process27.exit(0);
7868
+ }
7869
+ return value;
7870
+ }
7871
+
7872
+ // src/commands/admin.ts
7873
+ var admin = new Command82("admin").description("manage admin panel logins").configureHelp(helpConfig).addCommand(adminCreate);
7874
+
7875
+ // src/commands/targets.ts
7876
+ import { Command as Command83 } from "commander";
7877
+ import * as p41 from "@clack/prompts";
7878
+ import pc18 from "picocolors";
7879
+ import * as v8 from "valibot";
7880
+ var OptionsSchema4 = v8.object({
7881
+ ...SSH_OPTION_SCHEMA,
7882
+ json: v8.optional(v8.boolean()),
7883
+ offline: v8.optional(v8.boolean())
7884
+ });
7885
+ var targets = addSshOptions(
7886
+ new Command83("targets").description("list the targets this project can deploy to").configureHelp(helpConfig)
7887
+ ).option("--json", "print raw JSON").option("--offline", "skip connecting to servers").action(
7888
+ (raw) => runCommand(async () => {
7889
+ const options = parseOptions(OptionsSchema4, raw);
7890
+ const { workspaceRootDir } = await getWorkspace();
7891
+ const config = await loadDeployConfig(workspaceRootDir);
7892
+ const app = readAppIdentity(workspaceRootDir, config);
7893
+ const bindings = readBindings(workspaceRootDir);
7894
+ const metadata = getPocketbaseMetadata(workspaceRootDir);
7895
+ const rows = [
7896
+ {
7897
+ target: "local",
7898
+ kind: "local",
7899
+ server: "\u2014",
7900
+ domain: metadata ? `http://${metadata.viteHost}:${metadata.vitePort}` : "\u2014",
7901
+ release: "\u2014",
7902
+ reachable: true
7903
+ }
7904
+ ];
7905
+ const byServer = /* @__PURE__ */ new Map();
7906
+ for (const [envTag, binding] of Object.entries(bindings)) {
7907
+ byServer.set(binding.server, [...byServer.get(binding.server) ?? [], envTag]);
7908
+ }
7909
+ for (const [server, envTags] of byServer) {
7910
+ let states = [];
7911
+ let reachable = false;
7912
+ if (!options.offline && app) {
7913
+ try {
7914
+ await withSsh(server, sshOptionsFrom(options), async (session) => {
7915
+ await session.detectElevation();
7916
+ states = await readInstanceStates(session);
7917
+ });
7918
+ reachable = true;
7919
+ } catch {
7920
+ }
7921
+ }
7922
+ for (const envTag of envTags) {
7923
+ const binding = bindings[envTag];
7924
+ const instance = app ? instanceId(app.appId, envTag) : "";
7925
+ const state = states.find((candidate) => candidate.instance === instance);
7926
+ rows.push({
7927
+ target: envTag,
7928
+ kind: "remote",
7929
+ server,
7930
+ domain: state?.domain || binding.domain || "\u2014",
7931
+ release: state?.activeRelease ?? (reachable ? "not deployed" : "\u2014"),
7932
+ reachable
7933
+ });
7934
+ }
7935
+ }
7936
+ if (options.json) {
7937
+ console.log(JSON.stringify(rows, null, 2));
7938
+ return;
7939
+ }
7940
+ report3(rows, options.offline === true);
7941
+ }, "Failed to list targets.")
7942
+ );
7943
+ function report3(rows, offline) {
7944
+ const width = (heading, pick) => Math.max(heading.length, ...rows.map((row) => pick(row).length));
7945
+ const target = width("TARGET", (row) => row.target);
7946
+ const server = width("SERVER", (row) => row.server);
7947
+ const domain = width("DOMAIN", (row) => row.domain);
7948
+ const header = `${"TARGET".padEnd(target)} ${"SERVER".padEnd(server)} ${"DOMAIN".padEnd(domain)} RELEASE`;
7949
+ const lines = rows.map(
7950
+ (row) => `${row.kind === "local" ? pc18.dim(row.target.padEnd(target)) : pc18.cyan(row.target.padEnd(target))} ${row.server.padEnd(server)} ${row.domain.padEnd(domain)} ${row.release}`
7951
+ );
7952
+ p41.log.info(`${pc18.dim(header)}
7953
+ ${lines.join("\n")}`);
7954
+ const unreachable = rows.filter((row) => row.kind === "remote" && !row.reachable);
7955
+ if (!offline && unreachable.length > 0) {
7956
+ p41.log.warn(
7957
+ `Could not reach ${unreachable.map((row) => row.server).join(", ")}.
7958
+ Release and domain are shown from what this project recorded.`
7959
+ );
7960
+ }
5971
7961
  }
5972
7962
 
5973
7963
  // src/commands/test.ts
5974
- import path34 from "node:path";
5975
- import process21 from "node:process";
5976
- import { Command as Command70 } from "commander";
5977
- import PocketBase3 from "pocketbase";
5978
- import pc6 from "picocolors";
7964
+ import path41 from "node:path";
7965
+ import process28 from "node:process";
7966
+ import { Command as Command84 } from "commander";
7967
+ import PocketBase5 from "pocketbase";
7968
+ import pc19 from "picocolors";
5979
7969
  import { x as x5 } from "tinyexec";
5980
- import { detect as detect7 } from "package-manager-detector";
5981
- import { resolveCommand as resolveCommand7 } from "package-manager-detector/commands";
5982
- import fs28 from "node:fs";
5983
- var testServer = new Command70("test:server").description("run server tests").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(async (_opts, cmd) => {
5984
- const cwd = process21.cwd();
7970
+ import { detect as detect8 } from "package-manager-detector";
7971
+ import { resolveCommand as resolveCommand8 } from "package-manager-detector/commands";
7972
+ import fs36 from "node:fs";
7973
+ var testServer = new Command84("test:server").description("run server tests").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(async (_opts, cmd) => {
7974
+ const cwd = process28.cwd();
5985
7975
  const email3 = `test-${Math.random().toString(36).slice(2)}@example.com`;
5986
- const password8 = "password";
5987
- const testDataDir = path34.join(cwd, "test-data");
5988
- fs28.rmSync(testDataDir, { recursive: true, force: true });
7976
+ const password10 = "password";
7977
+ const testDataDir = path41.join(cwd, "test-data");
7978
+ fs36.rmSync(testDataDir, { recursive: true, force: true });
5989
7979
  const { stop, url } = await launchPocketbase(cwd, {
5990
7980
  dir: testDataDir,
5991
- migrationsDir: path34.join(cwd, "migrations"),
7981
+ migrationsDir: path41.join(cwd, "migrations"),
5992
7982
  email: email3,
5993
- password: password8
7983
+ password: password10
5994
7984
  });
5995
- process21.env.POCKETBASE_URL = url;
5996
- process21.env.POCKETBASE_SUPERUSER_EMAIL = email3;
5997
- process21.env.POCKETBASE_SUPERUSER_PASSWORD = password8;
5998
- process21.env.TEST = "true";
5999
- console.log(`${pc6.greenBright("\u2713")} Created test database`);
7985
+ process28.env.POCKETBASE_URL = url;
7986
+ process28.env.POCKETBASE_SUPERUSER_EMAIL = email3;
7987
+ process28.env.POCKETBASE_SUPERUSER_PASSWORD = password10;
7988
+ process28.env.TEST = "true";
7989
+ console.log(`${pc19.greenBright("\u2713")} Created test database`);
6000
7990
  const { createServer } = await import("vite");
6001
7991
  const vite = await createServer({
6002
7992
  mode: "test",
@@ -6005,29 +7995,29 @@ var testServer = new Command70("test:server").description("run server tests").al
6005
7995
  });
6006
7996
  const vitePort = await findFreePort();
6007
7997
  await vite.listen(vitePort);
6008
- process21.env.VITE_TEST_URL = `http://localhost:${vitePort}`;
6009
- console.log(`${pc6.greenBright("\u2713")} Started Vite: http://localhost:${vitePort}`);
6010
- console.log(`${pc6.greenBright("\u2713")} Started PocketBase: ${url}`);
7998
+ process28.env.VITE_TEST_URL = `http://localhost:${vitePort}`;
7999
+ console.log(`${pc19.greenBright("\u2713")} Started Vite: http://localhost:${vitePort}`);
8000
+ console.log(`${pc19.greenBright("\u2713")} Started PocketBase: ${url}`);
6011
8001
  const cleanup = async () => {
6012
8002
  stop();
6013
8003
  await vite.close();
6014
- fs28.rmSync(testDataDir, { recursive: true, force: true });
8004
+ fs36.rmSync(testDataDir, { recursive: true, force: true });
6015
8005
  };
6016
- const pb = new PocketBase3(url);
8006
+ const pb = new PocketBase5(url);
6017
8007
  try {
6018
- await authWithRetries(pb, email3, password8);
8008
+ await authWithRetries(pb, email3, password10);
6019
8009
  } catch (e) {
6020
8010
  await cleanup();
6021
- console.error(`${pc6.redBright("\u2717")} Auth failed: ${e.message}`);
6022
- process21.exit(1);
8011
+ console.error(`${pc19.redBright("\u2717")} Auth failed: ${e.message}`);
8012
+ process28.exit(1);
6023
8013
  }
6024
8014
  const extraArgs = (cmd.parent?.args ?? []).slice(1);
6025
8015
  let filter = extraArgs.find((arg) => !arg.startsWith("-"));
6026
8016
  const passthrough = filter ? extraArgs.filter((a) => a !== filter) : extraArgs;
6027
8017
  if (!filter) filter = "server";
6028
8018
  try {
6029
- const pm = (await detect7({ cwd }))?.name ?? "npm";
6030
- const resolved = resolveCommand7(pm, "execute", [
8019
+ const pm = (await detect8({ cwd }))?.name ?? "npm";
8020
+ const resolved = resolveCommand8(pm, "execute", [
6031
8021
  "vitest",
6032
8022
  "run",
6033
8023
  filter,
@@ -6037,7 +8027,7 @@ var testServer = new Command70("test:server").description("run server tests").al
6037
8027
  const resolvedArgs = resolved.args.slice();
6038
8028
  if (pm === "npm") resolvedArgs.unshift("--yes");
6039
8029
  await x5(resolved.command, resolvedArgs, {
6040
- nodeOptions: { cwd, stdio: "inherit", env: { ...process21.env, CI: "1" } },
8030
+ nodeOptions: { cwd, stdio: "inherit", env: { ...process28.env, CI: "1" } },
6041
8031
  throwOnError: true
6042
8032
  });
6043
8033
  } catch {
@@ -6063,9 +8053,9 @@ function stubPagesPlugin() {
6063
8053
  }
6064
8054
 
6065
8055
  // src/commands/routes.ts
6066
- import fs29 from "node:fs";
6067
- import path35 from "node:path";
6068
- import { Command as Command71 } from "commander";
8056
+ import fs37 from "node:fs";
8057
+ import path42 from "node:path";
8058
+ import { Command as Command85 } from "commander";
6069
8059
  var HTTP_METHODS = /* @__PURE__ */ new Set([
6070
8060
  "GET",
6071
8061
  "POST",
@@ -6076,32 +8066,32 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
6076
8066
  "HEAD",
6077
8067
  "fallback"
6078
8068
  ]);
6079
- var routes = new Command71("routes").description("list routes").configureHelp(helpConfig).action(async () => {
8069
+ var routes = new Command85("routes").description("list routes").configureHelp(helpConfig).action(async () => {
6080
8070
  const { workspaceRootDir, routesDir } = await getWorkspace();
6081
- const routesRoot = path35.join(workspaceRootDir, routesDir);
8071
+ const routesRoot = path42.join(workspaceRootDir, routesDir);
6082
8072
  const found = walk(routesRoot, routesRoot).filter((r) => r.methods.length > 0);
6083
8073
  found.sort((a, b) => a.urlPattern.localeCompare(b.urlPattern));
6084
8074
  printTable(found);
6085
8075
  });
6086
8076
  function walk(root, dir) {
6087
- const entries = fs29.readdirSync(dir, { withFileTypes: true });
8077
+ const entries = fs37.readdirSync(dir, { withFileTypes: true });
6088
8078
  const routes2 = [];
6089
8079
  const hasLeaf = entries.some((e) => e.isFile() && isRouteFile(e.name));
6090
8080
  if (hasLeaf) {
6091
- const id = "/" + path35.relative(root, dir).split(path35.sep).filter(Boolean).join("/");
8081
+ const id = "/" + path42.relative(root, dir).split(path42.sep).filter(Boolean).join("/");
6092
8082
  const urlPattern = id.replace(/\([^)]+\)\/?/g, "").replace(/\/$/, "") || "/";
6093
8083
  const methods = /* @__PURE__ */ new Set();
6094
8084
  for (const entry of entries) {
6095
8085
  if (!entry.isFile()) continue;
6096
8086
  if (entry.name.endsWith("+page.svelte")) methods.add("GET");
6097
8087
  if (entry.name.endsWith("+server.ts") || entry.name.endsWith("+server.js") || entry.name.endsWith("+page.server.ts") || entry.name.endsWith("+page.server.js")) {
6098
- extractMethods(path35.join(dir, entry.name)).forEach((m) => methods.add(m));
8088
+ extractMethods(path42.join(dir, entry.name)).forEach((m) => methods.add(m));
6099
8089
  }
6100
8090
  }
6101
8091
  routes2.push({ id: id || "/", urlPattern, methods: [...methods] });
6102
8092
  }
6103
8093
  for (const entry of entries) {
6104
- if (entry.isDirectory()) routes2.push(...walk(root, path35.join(dir, entry.name)));
8094
+ if (entry.isDirectory()) routes2.push(...walk(root, path42.join(dir, entry.name)));
6105
8095
  }
6106
8096
  return routes2;
6107
8097
  }
@@ -6110,7 +8100,7 @@ function isRouteFile(name) {
6110
8100
  }
6111
8101
  function extractMethods(file) {
6112
8102
  try {
6113
- const content = fs29.readFileSync(file, "utf8");
8103
+ const content = fs37.readFileSync(file, "utf8");
6114
8104
  const methods = [];
6115
8105
  const exportRegex = /export\s+(?:const|async\s+function|function)\s+(\w+)/g;
6116
8106
  let match;
@@ -6151,15 +8141,15 @@ function printTable(routes2) {
6151
8141
  }
6152
8142
 
6153
8143
  // src/commands/i18n.ts
6154
- import process22 from "node:process";
6155
- import { Command as Command72 } from "commander";
8144
+ import process29 from "node:process";
8145
+ import { Command as Command86 } from "commander";
6156
8146
  import { x as x6 } from "tinyexec";
6157
- import { detect as detect8 } from "package-manager-detector";
6158
- import { resolveCommand as resolveCommand8 } from "package-manager-detector/commands";
8147
+ import { detect as detect9 } from "package-manager-detector";
8148
+ import { resolveCommand as resolveCommand9 } from "package-manager-detector/commands";
6159
8149
  async function runWuchale(extraArgs) {
6160
- const cwd = process22.cwd();
6161
- const pm = (await detect8({ cwd }))?.name ?? "npm";
6162
- const resolved = resolveCommand8(pm, "execute", ["wuchale", ...extraArgs]);
8150
+ const cwd = process29.cwd();
8151
+ const pm = (await detect9({ cwd }))?.name ?? "npm";
8152
+ const resolved = resolveCommand9(pm, "execute", ["wuchale", ...extraArgs]);
6163
8153
  const args = resolved.args.slice();
6164
8154
  if (pm === "npm") args.unshift("--yes");
6165
8155
  await x6(resolved.command, args, {
@@ -6167,11 +8157,11 @@ async function runWuchale(extraArgs) {
6167
8157
  throwOnError: true
6168
8158
  });
6169
8159
  }
6170
- var extract = new Command72("extract").description("extract translatable strings").configureHelp(helpConfig).action(() => runWuchale([]));
6171
- var watch = new Command72("watch").description("watch and extract translatable strings").configureHelp(helpConfig).action(() => runWuchale(["--watch"]));
6172
- var status = new Command72("status").description("show i18n status").configureHelp(helpConfig).action(() => runWuchale(["status"]));
6173
- var clean = new Command72("clean").description("clean unused translatable strings").configureHelp(helpConfig).action(() => runWuchale(["--clean"]));
6174
- var i18n3 = new Command72("i18n").description("i18n utilities").configureHelp(helpConfig).addCommand(extract, { isDefault: true }).addCommand(watch).addCommand(status).addCommand(clean);
8160
+ var extract = new Command86("extract").description("extract translatable strings").configureHelp(helpConfig).action(() => runWuchale([]));
8161
+ var watch = new Command86("watch").description("watch and extract translatable strings").configureHelp(helpConfig).action(() => runWuchale(["--watch"]));
8162
+ var status2 = new Command86("status").description("show i18n status").configureHelp(helpConfig).action(() => runWuchale(["status"]));
8163
+ var clean = new Command86("clean").description("clean unused translatable strings").configureHelp(helpConfig).action(() => runWuchale(["--clean"]));
8164
+ var i18n3 = new Command86("i18n").description("i18n utilities").configureHelp(helpConfig).addCommand(extract, { isDefault: true }).addCommand(watch).addCommand(status2).addCommand(clean);
6175
8165
 
6176
8166
  // src/commands/oauth.ts
6177
8167
  var oauth = stubCommand("oauth", "configure OAuth providers");
@@ -6192,42 +8182,52 @@ var NO_BACKEND_COMMMANDS = /* @__PURE__ */ new Set([
6192
8182
  "routes",
6193
8183
  "i18n",
6194
8184
  "generate schema",
6195
- "generate form"
8185
+ "generate form",
8186
+ // Server commands talk to a VPS over SSH, never to the local database.
8187
+ "provision",
8188
+ "env",
8189
+ "status",
8190
+ "rollback",
8191
+ "logs",
8192
+ "admin",
8193
+ "targets",
8194
+ "link"
6196
8195
  ]);
6197
8196
  var BACKEND_OPTIONAL_COMMANDS = /* @__PURE__ */ new Set(["dev", "build", "preview", "deploy"]);
6198
- var program = new Command73().name(package_default.name).description(package_default.description).version(package_default.version, "-v, --version").configureHelp(helpConfig);
8197
+ var program = new Command87().name(package_default.name).description(package_default.description).version(package_default.version, "-v, --version").configureHelp(helpConfig);
6199
8198
  program.hook("preAction", (_thisCommand, actionCommand) => {
6200
8199
  if (isStub(actionCommand)) return;
6201
- dotenv.config({ quiet: true });
6202
- const path36 = getCommandPath(actionCommand);
6203
- if (NO_BACKEND_COMMMANDS.has(path36)) return;
6204
- const top = path36.split(" ", 1)[0];
8200
+ const envRoot = findWorkspaceRoot() ?? process30.cwd();
8201
+ dotenv2.config({ path: nodePath.join(envRoot, ".env"), quiet: true });
8202
+ const path43 = getCommandPath(actionCommand);
8203
+ if (NO_BACKEND_COMMMANDS.has(path43)) return;
8204
+ const top = path43.split(" ", 1)[0];
6205
8205
  if (NO_BACKEND_COMMMANDS.has(top)) return;
6206
8206
  if (!hasBackend()) {
6207
8207
  if (BACKEND_OPTIONAL_COMMANDS.has(top)) return;
6208
- p29.log.error(
6209
- `${pc7.cyan(`vela ${path36}`)} needs a backend, and this project does not have one.
8208
+ p42.log.error(
8209
+ `${pc20.cyan(`vela ${path43}`)} needs a backend, and this project does not have one.
6210
8210
 
6211
8211
  Static projects have no database to talk to.
6212
8212
 
6213
- To add a backend to this project, run ${pc7.cyan("vela bless")}.`
8213
+ To add a backend to this project, run ${pc20.cyan("vela bless")}.`
6214
8214
  );
6215
- p29.log.message();
6216
- p29.cancel("Operation failed.");
6217
- process23.exit(1);
8215
+ p42.log.message();
8216
+ p42.cancel("Operation failed.");
8217
+ process30.exit(1);
6218
8218
  }
6219
- if (!process23.env.POCKETBASE_SUPERUSER_EMAIL || !process23.env.POCKETBASE_SUPERUSER_PASSWORD) {
6220
- p29.log.error(
8219
+ if (!process30.env.POCKETBASE_SUPERUSER_EMAIL || !process30.env.POCKETBASE_SUPERUSER_PASSWORD) {
8220
+ p42.log.error(
6221
8221
  `PocketBase superuser credentials are required.
6222
8222
 
6223
- Set ${pc7.cyan("POCKETBASE_SUPERUSER_EMAIL")} and ${pc7.cyan("POCKETBASE_SUPERUSER_PASSWORD")} in your .env file.
8223
+ Set ${pc20.cyan("POCKETBASE_SUPERUSER_EMAIL")} and ${pc20.cyan("POCKETBASE_SUPERUSER_PASSWORD")} in your .env file.
6224
8224
 
6225
- To set up a new project, run ${pc7.cyan("vela create")}.
6226
- To set up an existing project, run ${pc7.cyan("vela bless")}.`
8225
+ To set up a new project, run ${pc20.cyan("vela create")}.
8226
+ To set up an existing project, run ${pc20.cyan("vela bless")}.`
6227
8227
  );
6228
- p29.log.message();
6229
- p29.cancel("Operation failed.");
6230
- process23.exit(1);
8228
+ p42.log.message();
8229
+ p42.cancel("Operation failed.");
8230
+ process30.exit(1);
6231
8231
  }
6232
8232
  });
6233
8233
  function getCommandPath(cmd) {
@@ -6261,6 +8261,13 @@ for (const command of [
6261
8261
  sync,
6262
8262
  provision,
6263
8263
  deploy,
8264
+ rollback,
8265
+ status,
8266
+ logs,
8267
+ env,
8268
+ admin,
8269
+ targets,
8270
+ link,
6264
8271
  testServer,
6265
8272
  routes,
6266
8273
  i18n3,
@@ -6271,12 +8278,14 @@ for (const command of [
6271
8278
  }
6272
8279
 
6273
8280
  // src/bin.ts
8281
+ var argv = normalizeArgv(process31.argv.slice(2));
6274
8282
  var delegatedExitCode = delegateToLocalCli({
8283
+ argv,
6275
8284
  selfPath: fileURLToPath2(import.meta.url),
6276
8285
  selfVersion: package_default.version
6277
8286
  });
6278
8287
  if (delegatedExitCode !== null) {
6279
- process24.exit(delegatedExitCode);
8288
+ process31.exit(delegatedExitCode);
6280
8289
  }
6281
- program.parse();
8290
+ program.parse(argv, { from: "user" });
6282
8291
  //# sourceMappingURL=bin.js.map