vela 0.10.2 → 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/README.md +34 -0
- package/dist/bin.js +2678 -684
- package/dist/bin.js.map +4 -4
- package/package.json +1 -1
- package/templates/server/apply.sh +349 -0
- package/templates/server/destroy.sh +55 -0
- package/templates/server/lib.sh +166 -0
- package/templates/server/pocketbase.sh +45 -0
- package/templates/server/provision.sh +153 -0
- package/templates/server/rollback.sh +88 -0
- package/templates/server/status.sh +44 -0
- package/templates/server/systemd/vela-pb@.service +41 -0
- package/templates/server/systemd/vela-web@.service +39 -0
package/dist/bin.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin.ts
|
|
4
|
-
import
|
|
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.
|
|
10
|
+
version: "0.10.3",
|
|
11
11
|
type: "module",
|
|
12
12
|
description: "A CLI for creating and updating SvelteKit projects",
|
|
13
13
|
license: "MIT",
|
|
@@ -152,15 +152,15 @@ function isDelegatable(argv2) {
|
|
|
152
152
|
return first === void 0 || !NO_DELEGATE_COMMANDS.has(first);
|
|
153
153
|
}
|
|
154
154
|
function delegateToLocalCli(opts) {
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
155
|
+
const env2 = opts.env ?? process2.env;
|
|
156
|
+
if (env2[NO_DELEGATE_ENV]) return null;
|
|
157
157
|
const argv2 = opts.argv ?? process2.argv.slice(2);
|
|
158
158
|
if (!isDelegatable(argv2)) return null;
|
|
159
159
|
const local = findLocalCli(opts.cwd ?? process2.cwd(), opts.selfPath);
|
|
160
160
|
if (!local || local.version === opts.selfVersion) return null;
|
|
161
161
|
const result = spawnSync(process2.execPath, [local.binPath, ...argv2], {
|
|
162
162
|
stdio: "inherit",
|
|
163
|
-
env: { ...
|
|
163
|
+
env: { ...env2, [NO_DELEGATE_ENV]: "1" }
|
|
164
164
|
});
|
|
165
165
|
if (result.error) return null;
|
|
166
166
|
if (result.signal) return 1;
|
|
@@ -168,11 +168,12 @@ function delegateToLocalCli(opts) {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
// src/program.ts
|
|
171
|
-
import
|
|
172
|
-
import * as
|
|
173
|
-
import { Command as
|
|
174
|
-
import
|
|
175
|
-
import
|
|
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";
|
|
176
177
|
|
|
177
178
|
// src/lib/help.ts
|
|
178
179
|
import pc from "picocolors";
|
|
@@ -349,8 +350,8 @@ function mergePackageJson(user, template) {
|
|
|
349
350
|
}
|
|
350
351
|
return { merged, added, conflicts, replaced };
|
|
351
352
|
}
|
|
352
|
-
function readPackageJson(
|
|
353
|
-
return JSON.parse(fs2.readFileSync(
|
|
353
|
+
function readPackageJson(path43) {
|
|
354
|
+
return JSON.parse(fs2.readFileSync(path43, "utf8"));
|
|
354
355
|
}
|
|
355
356
|
var PACKAGE_NAME_PLACEHOLDER = /~TODO~/g;
|
|
356
357
|
var APP_NAME_PLACEHOLDER = /~APP_NAME~/g;
|
|
@@ -363,12 +364,12 @@ function fillTemplatePlaceholders(raw, values) {
|
|
|
363
364
|
function escapeSingleQuoted(value) {
|
|
364
365
|
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
365
366
|
}
|
|
366
|
-
function readTemplatePackageJson(
|
|
367
|
-
const raw = fillTemplatePlaceholders(fs2.readFileSync(
|
|
367
|
+
function readTemplatePackageJson(path43, values) {
|
|
368
|
+
const raw = fillTemplatePlaceholders(fs2.readFileSync(path43, "utf8"), values);
|
|
368
369
|
return JSON.parse(raw);
|
|
369
370
|
}
|
|
370
|
-
function writePackageJson(
|
|
371
|
-
fs2.writeFileSync(
|
|
371
|
+
function writePackageJson(path43, pkg) {
|
|
372
|
+
fs2.writeFileSync(path43, JSON.stringify(pkg, null, " ") + "\n");
|
|
372
373
|
}
|
|
373
374
|
function toValidPackageName(name) {
|
|
374
375
|
return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z0-9~.-]+/g, "-");
|
|
@@ -630,13 +631,14 @@ import fs6 from "node:fs";
|
|
|
630
631
|
import net from "node:net";
|
|
631
632
|
import path5 from "node:path";
|
|
632
633
|
import process5 from "node:process";
|
|
634
|
+
import { createRequire } from "node:module";
|
|
633
635
|
import { spawn } from "node:child_process";
|
|
634
636
|
import PocketBase from "pocketbase";
|
|
635
637
|
import { detect as detect2 } from "package-manager-detector";
|
|
636
638
|
import { resolveCommand } from "package-manager-detector/commands";
|
|
637
639
|
import { x } from "tinyexec";
|
|
638
640
|
function findFreePort(host = "localhost") {
|
|
639
|
-
return new Promise((
|
|
641
|
+
return new Promise((resolve2, reject) => {
|
|
640
642
|
const server = net.createServer();
|
|
641
643
|
server.unref();
|
|
642
644
|
server.once("error", reject);
|
|
@@ -645,13 +647,13 @@ function findFreePort(host = "localhost") {
|
|
|
645
647
|
server.close((err) => {
|
|
646
648
|
if (err) reject(err);
|
|
647
649
|
else if (!addr) reject(new Error("Failed to obtain a free port"));
|
|
648
|
-
else
|
|
650
|
+
else resolve2(addr.port);
|
|
649
651
|
});
|
|
650
652
|
});
|
|
651
653
|
});
|
|
652
654
|
}
|
|
653
655
|
function waitForReadyOrExit(proc, url, maxAttempts = 40) {
|
|
654
|
-
return new Promise((
|
|
656
|
+
return new Promise((resolve2, reject) => {
|
|
655
657
|
let settled = false;
|
|
656
658
|
const onExit = (code, signal) => {
|
|
657
659
|
if (settled) return;
|
|
@@ -671,7 +673,7 @@ function waitForReadyOrExit(proc, url, maxAttempts = 40) {
|
|
|
671
673
|
if (settled) return;
|
|
672
674
|
settled = true;
|
|
673
675
|
proc.removeListener("exit", onExit);
|
|
674
|
-
|
|
676
|
+
resolve2();
|
|
675
677
|
return;
|
|
676
678
|
}
|
|
677
679
|
} catch {
|
|
@@ -715,8 +717,8 @@ async function startPocketbaseServe(opts) {
|
|
|
715
717
|
} catch (err) {
|
|
716
718
|
lastErr = err;
|
|
717
719
|
if (proc.exitCode === null && proc.signalCode === null) {
|
|
718
|
-
await new Promise((
|
|
719
|
-
proc.once("exit", () =>
|
|
720
|
+
await new Promise((resolve2) => {
|
|
721
|
+
proc.once("exit", () => resolve2());
|
|
720
722
|
proc.kill();
|
|
721
723
|
});
|
|
722
724
|
}
|
|
@@ -724,10 +726,10 @@ async function startPocketbaseServe(opts) {
|
|
|
724
726
|
}
|
|
725
727
|
throw lastErr instanceof Error ? lastErr : new Error("Failed to start PocketBase");
|
|
726
728
|
}
|
|
727
|
-
async function authWithRetries(pb, email3,
|
|
729
|
+
async function authWithRetries(pb, email3, password10, attempts = 3) {
|
|
728
730
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
729
731
|
try {
|
|
730
|
-
await pb.collection("_superusers").authWithPassword(email3,
|
|
732
|
+
await pb.collection("_superusers").authWithPassword(email3, password10);
|
|
731
733
|
return;
|
|
732
734
|
} catch (e) {
|
|
733
735
|
if (attempt === attempts) {
|
|
@@ -759,14 +761,14 @@ async function withPocketbase(cwd, fn, creds) {
|
|
|
759
761
|
const migrationsDir = path5.join(cwd, MIGRATIONS_DIR);
|
|
760
762
|
const host = "localhost";
|
|
761
763
|
const email3 = creds?.email ?? process5.env.POCKETBASE_SUPERUSER_EMAIL;
|
|
762
|
-
const
|
|
764
|
+
const password10 = creds?.password ?? process5.env.POCKETBASE_SUPERUSER_PASSWORD;
|
|
763
765
|
if (!fs6.existsSync(dir)) {
|
|
764
766
|
throw new Error("PocketBase data directory does not exist");
|
|
765
767
|
}
|
|
766
768
|
const metadata = getPocketbaseMetadata(cwd);
|
|
767
769
|
if (metadata?.pocketbaseUrl) {
|
|
768
770
|
const pb = new PocketBase(metadata.pocketbaseUrl);
|
|
769
|
-
await authWithRetries(pb, email3,
|
|
771
|
+
await authWithRetries(pb, email3, password10);
|
|
770
772
|
await fn(pb);
|
|
771
773
|
return;
|
|
772
774
|
}
|
|
@@ -778,13 +780,13 @@ async function withPocketbase(cwd, fn, creds) {
|
|
|
778
780
|
});
|
|
779
781
|
try {
|
|
780
782
|
const pb = new PocketBase(url);
|
|
781
|
-
await authWithRetries(pb, email3,
|
|
783
|
+
await authWithRetries(pb, email3, password10);
|
|
782
784
|
await fn(pb);
|
|
783
785
|
} finally {
|
|
784
786
|
proc.kill();
|
|
785
787
|
}
|
|
786
788
|
}
|
|
787
|
-
async function createSuperuser(cwd, email3,
|
|
789
|
+
async function createSuperuser(cwd, email3, password10) {
|
|
788
790
|
const dir = path5.join(cwd, DATA_DIR);
|
|
789
791
|
const migrationsDir = path5.join(cwd, MIGRATIONS_DIR);
|
|
790
792
|
fs6.mkdirSync(dir, { recursive: true });
|
|
@@ -800,7 +802,7 @@ async function createSuperuser(cwd, email3, password8) {
|
|
|
800
802
|
"superuser",
|
|
801
803
|
"create",
|
|
802
804
|
email3,
|
|
803
|
-
|
|
805
|
+
password10
|
|
804
806
|
],
|
|
805
807
|
"pipe"
|
|
806
808
|
);
|
|
@@ -809,7 +811,7 @@ async function launchPocketbase(cwd, {
|
|
|
809
811
|
dir,
|
|
810
812
|
migrationsDir,
|
|
811
813
|
email: email3,
|
|
812
|
-
password:
|
|
814
|
+
password: password10
|
|
813
815
|
}) {
|
|
814
816
|
const host = "localhost";
|
|
815
817
|
fs6.mkdirSync(dir, { recursive: true });
|
|
@@ -825,7 +827,7 @@ async function launchPocketbase(cwd, {
|
|
|
825
827
|
"superuser",
|
|
826
828
|
"create",
|
|
827
829
|
email3,
|
|
828
|
-
|
|
830
|
+
password10
|
|
829
831
|
],
|
|
830
832
|
"pipe"
|
|
831
833
|
);
|
|
@@ -845,6 +847,33 @@ async function launchPocketbase(cwd, {
|
|
|
845
847
|
url
|
|
846
848
|
};
|
|
847
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
|
+
}
|
|
848
877
|
|
|
849
878
|
// src/lib/env.ts
|
|
850
879
|
import fs7 from "node:fs";
|
|
@@ -869,6 +898,35 @@ function writeEnvFile(cwd, vars, comments = []) {
|
|
|
869
898
|
for (const [key, value] of Object.entries(vars)) content = addEnvVar(content, key, value);
|
|
870
899
|
fs7.writeFileSync(envPath, content);
|
|
871
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
|
+
}
|
|
872
930
|
|
|
873
931
|
// src/lib/config-merge.ts
|
|
874
932
|
import fs9 from "node:fs";
|
|
@@ -1128,18 +1186,18 @@ function isVanillaRoutes(cwd) {
|
|
|
1128
1186
|
|
|
1129
1187
|
// src/lib/result-report.ts
|
|
1130
1188
|
import * as p3 from "@clack/prompts";
|
|
1131
|
-
function reportResult(
|
|
1189
|
+
function reportResult(report4) {
|
|
1132
1190
|
const sections = [
|
|
1133
|
-
["Files created",
|
|
1134
|
-
["Files modified",
|
|
1135
|
-
["Files deleted",
|
|
1136
|
-
["UI components added",
|
|
1137
|
-
["Packages installed",
|
|
1138
|
-
["Collections added",
|
|
1139
|
-
["Records loaded",
|
|
1140
|
-
["Records cleared",
|
|
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]
|
|
1141
1199
|
];
|
|
1142
|
-
for (const extra of
|
|
1200
|
+
for (const extra of report4.sections ?? []) {
|
|
1143
1201
|
sections.push([extra.label, extra.items]);
|
|
1144
1202
|
}
|
|
1145
1203
|
const body = [];
|
|
@@ -1150,14 +1208,14 @@ function reportResult(report) {
|
|
|
1150
1208
|
for (const item of items) body.push(`- ${item}`);
|
|
1151
1209
|
}
|
|
1152
1210
|
if (body.length > 0) {
|
|
1153
|
-
p3.log.success(`${
|
|
1211
|
+
p3.log.success(`${report4.summary}
|
|
1154
1212
|
|
|
1155
1213
|
${body.join("\n")}`);
|
|
1156
1214
|
} else {
|
|
1157
|
-
p3.log.success(
|
|
1215
|
+
p3.log.success(report4.summary);
|
|
1158
1216
|
}
|
|
1159
|
-
if (
|
|
1160
|
-
p3.note(
|
|
1217
|
+
if (report4.nextSteps && report4.nextSteps.length > 0) {
|
|
1218
|
+
p3.note(report4.nextSteps.map((s) => `- ${s}`).join("\n"), "Next steps", {
|
|
1161
1219
|
format: (line) => line
|
|
1162
1220
|
});
|
|
1163
1221
|
}
|
|
@@ -1247,7 +1305,7 @@ async function blessProject(cwdArg, options) {
|
|
|
1247
1305
|
const projectPath = cwdArg ? resolveProjectPath(cwdArg) : (await getWorkspace()).workspaceRootDir;
|
|
1248
1306
|
assertNotAlreadyBlessed(projectPath);
|
|
1249
1307
|
const templateDir = findProjectTemplate(options.template ?? DEFAULT_TEMPLATE).dir;
|
|
1250
|
-
const { email: email3, password:
|
|
1308
|
+
const { email: email3, password: password10 } = await p4.group(
|
|
1251
1309
|
{
|
|
1252
1310
|
email: () => {
|
|
1253
1311
|
if (options.email) return Promise.resolve(options.email);
|
|
@@ -1288,12 +1346,12 @@ async function blessProject(cwdArg, options) {
|
|
|
1288
1346
|
}
|
|
1289
1347
|
}
|
|
1290
1348
|
p4.log.step("Initializing PocketBase...");
|
|
1291
|
-
await createSuperuser(projectPath, email3,
|
|
1349
|
+
await createSuperuser(projectPath, email3, password10);
|
|
1292
1350
|
writeEnvFile(
|
|
1293
1351
|
projectPath,
|
|
1294
1352
|
{
|
|
1295
1353
|
POCKETBASE_SUPERUSER_EMAIL: email3,
|
|
1296
|
-
POCKETBASE_SUPERUSER_PASSWORD:
|
|
1354
|
+
POCKETBASE_SUPERUSER_PASSWORD: password10
|
|
1297
1355
|
},
|
|
1298
1356
|
["PocketBase superuser credentials \u2014 used by `vela` commands"]
|
|
1299
1357
|
);
|
|
@@ -1618,9 +1676,9 @@ async function createProject(cwdArg, options) {
|
|
|
1618
1676
|
}
|
|
1619
1677
|
}
|
|
1620
1678
|
if (credentials) {
|
|
1621
|
-
const { email: email3, password:
|
|
1679
|
+
const { email: email3, password: password10 } = credentials;
|
|
1622
1680
|
p5.log.step("Initializing PocketBase...");
|
|
1623
|
-
await createSuperuser(projectPath, email3,
|
|
1681
|
+
await createSuperuser(projectPath, email3, password10);
|
|
1624
1682
|
await withPocketbase(
|
|
1625
1683
|
projectPath,
|
|
1626
1684
|
async (pb) => {
|
|
@@ -1628,13 +1686,13 @@ async function createProject(cwdArg, options) {
|
|
|
1628
1686
|
meta: { appName: name, appURL: "http://localhost:5173" }
|
|
1629
1687
|
});
|
|
1630
1688
|
},
|
|
1631
|
-
{ email: email3, password:
|
|
1689
|
+
{ email: email3, password: password10 }
|
|
1632
1690
|
);
|
|
1633
1691
|
writeEnvFile(
|
|
1634
1692
|
projectPath,
|
|
1635
1693
|
{
|
|
1636
1694
|
POCKETBASE_SUPERUSER_EMAIL: email3,
|
|
1637
|
-
POCKETBASE_SUPERUSER_PASSWORD:
|
|
1695
|
+
POCKETBASE_SUPERUSER_PASSWORD: password10
|
|
1638
1696
|
},
|
|
1639
1697
|
["PocketBase superuser credentials \u2014 used by `vela` commands"]
|
|
1640
1698
|
);
|
|
@@ -1688,13 +1746,13 @@ import { bySlug } from "@velastack/patterns";
|
|
|
1688
1746
|
function toRelative(root, filePath) {
|
|
1689
1747
|
return path13.isAbsolute(filePath) ? path13.relative(root, filePath) : filePath;
|
|
1690
1748
|
}
|
|
1691
|
-
async function runPattern(
|
|
1692
|
-
const pattern = bySlug[
|
|
1749
|
+
async function runPattern(slug2, argv2, input, report4) {
|
|
1750
|
+
const pattern = bySlug[slug2];
|
|
1693
1751
|
if (!pattern) {
|
|
1694
|
-
throw new Error(`Unknown pattern: ${
|
|
1752
|
+
throw new Error(`Unknown pattern: ${slug2}`);
|
|
1695
1753
|
}
|
|
1696
1754
|
const { workspaceRootDir, features } = await getWorkspace();
|
|
1697
|
-
const
|
|
1755
|
+
const log31 = p6.taskLog({ title: report4.task.title });
|
|
1698
1756
|
let result;
|
|
1699
1757
|
try {
|
|
1700
1758
|
result = await pattern.generate({
|
|
@@ -1714,28 +1772,28 @@ async function runPattern(slug, argv2, input, report) {
|
|
|
1714
1772
|
});
|
|
1715
1773
|
return collections2;
|
|
1716
1774
|
},
|
|
1717
|
-
logger: { info: (message) =>
|
|
1775
|
+
logger: { info: (message) => log31.message(message) }
|
|
1718
1776
|
});
|
|
1719
|
-
|
|
1777
|
+
log31.success(report4.task.success);
|
|
1720
1778
|
} catch (e) {
|
|
1721
|
-
|
|
1779
|
+
log31.error(report4.task.error);
|
|
1722
1780
|
throw e;
|
|
1723
1781
|
}
|
|
1724
1782
|
const rel = (f) => toRelative(workspaceRootDir, f);
|
|
1725
1783
|
const totalChanges = result.creates.length + result.modifies.length + result.deletes.length + result.components.length + result.packages.length + result.collections.length;
|
|
1726
1784
|
if (totalChanges === 0) {
|
|
1727
|
-
p6.log.info(`${pattern.title ??
|
|
1785
|
+
p6.log.info(`${pattern.title ?? slug2} produced no changes.`);
|
|
1728
1786
|
return;
|
|
1729
1787
|
}
|
|
1730
1788
|
reportResult({
|
|
1731
|
-
summary:
|
|
1789
|
+
summary: report4.summary ?? `Applied ${pattern.title ?? slug2}.`,
|
|
1732
1790
|
filesCreated: result.creates.map((f) => rel(f.path)),
|
|
1733
1791
|
filesModified: result.modifies.map((f) => rel(f.path)),
|
|
1734
1792
|
filesDeleted: result.deletes.map((f) => rel(f.path)),
|
|
1735
1793
|
componentsAdded: result.components,
|
|
1736
1794
|
packagesInstalled: result.packages,
|
|
1737
1795
|
collectionsAdded: result.collections.map((c) => c.name),
|
|
1738
|
-
nextSteps:
|
|
1796
|
+
nextSteps: report4.nextSteps
|
|
1739
1797
|
});
|
|
1740
1798
|
}
|
|
1741
1799
|
|
|
@@ -1800,7 +1858,14 @@ function readProjectConfig(workspaceRootDir) {
|
|
|
1800
1858
|
function writeProjectConfig(workspaceRootDir, config) {
|
|
1801
1859
|
const file = projectConfigPath(workspaceRootDir);
|
|
1802
1860
|
fs15.mkdirSync(path15.dirname(file), { recursive: true });
|
|
1803
|
-
|
|
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");
|
|
1804
1869
|
}
|
|
1805
1870
|
|
|
1806
1871
|
// src/lib/ai-client.ts
|
|
@@ -2117,7 +2182,7 @@ var form = new Command4("form").description("generate a form from a model").argu
|
|
|
2117
2182
|
argv2 = [model, ...fields];
|
|
2118
2183
|
modelName = model;
|
|
2119
2184
|
}
|
|
2120
|
-
const
|
|
2185
|
+
const slug2 = options.remote ? "generate-form-remote" : "generate-form";
|
|
2121
2186
|
const nextSteps = [
|
|
2122
2187
|
"Edit the form fields and validation in the generated +page.svelte.",
|
|
2123
2188
|
"Run `vela dev` to preview the form in the browser."
|
|
@@ -2128,7 +2193,7 @@ var form = new Command4("form").description("generate a form from a model").argu
|
|
|
2128
2193
|
);
|
|
2129
2194
|
}
|
|
2130
2195
|
await runPattern(
|
|
2131
|
-
|
|
2196
|
+
slug2,
|
|
2132
2197
|
argv2,
|
|
2133
2198
|
{ route: options.route },
|
|
2134
2199
|
{
|
|
@@ -2253,7 +2318,7 @@ var scaffold = new Command7("scaffold").description("generate a full CRUD scaffo
|
|
|
2253
2318
|
argv2 = [model, ...fields];
|
|
2254
2319
|
modelName = model;
|
|
2255
2320
|
}
|
|
2256
|
-
const
|
|
2321
|
+
const slug2 = options.remote ? "generate-scaffold-remote" : "generate-scaffold";
|
|
2257
2322
|
const nextSteps = [
|
|
2258
2323
|
`Run \`vela fixtures generate\` to create 10 ${modelName} records for development.`,
|
|
2259
2324
|
"Run `vela dev` and visit the generated route to use the scaffold.",
|
|
@@ -2265,7 +2330,7 @@ var scaffold = new Command7("scaffold").description("generate a full CRUD scaffo
|
|
|
2265
2330
|
);
|
|
2266
2331
|
}
|
|
2267
2332
|
await runPattern(
|
|
2268
|
-
|
|
2333
|
+
slug2,
|
|
2269
2334
|
argv2,
|
|
2270
2335
|
{ route: options.route },
|
|
2271
2336
|
{
|
|
@@ -2560,7 +2625,7 @@ async function ensureKey(envVar, message) {
|
|
|
2560
2625
|
async function promptPassword(message) {
|
|
2561
2626
|
const value = await p14.password({
|
|
2562
2627
|
message,
|
|
2563
|
-
validate: (
|
|
2628
|
+
validate: (v9) => v9 && v9.length ? void 0 : "Required"
|
|
2564
2629
|
});
|
|
2565
2630
|
if (p14.isCancel(value)) {
|
|
2566
2631
|
p14.cancel("Operation cancelled.");
|
|
@@ -2607,24 +2672,24 @@ var s3 = new Command18("s3").description("enable S3 file storage").configureHelp
|
|
|
2607
2672
|
endpoint: () => p15.text({
|
|
2608
2673
|
message: "S3 endpoint URL",
|
|
2609
2674
|
initialValue: "https://s3.amazonaws.com",
|
|
2610
|
-
validate: (
|
|
2675
|
+
validate: (v9) => v9 ? void 0 : "Endpoint is required"
|
|
2611
2676
|
}),
|
|
2612
2677
|
accessKey: () => p15.text({
|
|
2613
2678
|
message: "S3 access key",
|
|
2614
|
-
validate: (
|
|
2679
|
+
validate: (v9) => v9 ? void 0 : "Access key is required"
|
|
2615
2680
|
}),
|
|
2616
2681
|
secret: () => p15.password({
|
|
2617
2682
|
message: "S3 secret key",
|
|
2618
|
-
validate: (
|
|
2683
|
+
validate: (v9) => v9 ? void 0 : "Secret key is required"
|
|
2619
2684
|
}),
|
|
2620
2685
|
bucket: () => p15.text({
|
|
2621
2686
|
message: "S3 bucket name",
|
|
2622
|
-
validate: (
|
|
2687
|
+
validate: (v9) => v9 ? void 0 : "Bucket name is required"
|
|
2623
2688
|
}),
|
|
2624
2689
|
region: () => p15.text({
|
|
2625
2690
|
message: "S3 region",
|
|
2626
2691
|
initialValue: "us-east-1",
|
|
2627
|
-
validate: (
|
|
2692
|
+
validate: (v9) => v9 ? void 0 : "Region is required"
|
|
2628
2693
|
})
|
|
2629
2694
|
},
|
|
2630
2695
|
{
|
|
@@ -2678,14 +2743,14 @@ var smtp = new Command19("smtp").description("configure SMTP for transactional e
|
|
|
2678
2743
|
host: () => p16.text({
|
|
2679
2744
|
message: "SMTP host",
|
|
2680
2745
|
placeholder: "smtp.example.com",
|
|
2681
|
-
validate: (
|
|
2746
|
+
validate: (v9) => v9 ? void 0 : "Host is required"
|
|
2682
2747
|
}),
|
|
2683
2748
|
port: () => p16.text({
|
|
2684
2749
|
message: "SMTP port",
|
|
2685
2750
|
initialValue: "587",
|
|
2686
|
-
validate: (
|
|
2687
|
-
if (!
|
|
2688
|
-
const n = Number(
|
|
2751
|
+
validate: (v9) => {
|
|
2752
|
+
if (!v9) return "Port is required";
|
|
2753
|
+
const n = Number(v9);
|
|
2689
2754
|
if (!Number.isInteger(n) || n <= 0 || n > 65535) return "Invalid port";
|
|
2690
2755
|
return void 0;
|
|
2691
2756
|
}
|
|
@@ -3046,7 +3111,7 @@ var smtp2 = new Command32("smtp").description("disable SMTP configuration").conf
|
|
|
3046
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);
|
|
3047
3112
|
|
|
3048
3113
|
// src/commands/destroy.ts
|
|
3049
|
-
import { Command as
|
|
3114
|
+
import { Command as Command39 } from "commander";
|
|
3050
3115
|
|
|
3051
3116
|
// src/commands/destroy/form.ts
|
|
3052
3117
|
import { Command as Command34 } from "commander";
|
|
@@ -3054,7 +3119,7 @@ import { Command as Command34 } from "commander";
|
|
|
3054
3119
|
// src/commands/destroy/_shared.ts
|
|
3055
3120
|
import process12 from "node:process";
|
|
3056
3121
|
import * as p18 from "@clack/prompts";
|
|
3057
|
-
async function runDestroy(
|
|
3122
|
+
async function runDestroy(slug2, model, confirmMessage, report4, flags) {
|
|
3058
3123
|
if (!flags.yes) {
|
|
3059
3124
|
const ok = await p18.confirm({ message: confirmMessage, initialValue: false });
|
|
3060
3125
|
if (p18.isCancel(ok) || !ok) {
|
|
@@ -3062,7 +3127,7 @@ async function runDestroy(slug, model, confirmMessage, report, flags) {
|
|
|
3062
3127
|
process12.exit(0);
|
|
3063
3128
|
}
|
|
3064
3129
|
}
|
|
3065
|
-
await runPattern(
|
|
3130
|
+
await runPattern(slug2, [model], { destructive: true, route: flags.route }, report4);
|
|
3066
3131
|
}
|
|
3067
3132
|
|
|
3068
3133
|
// src/commands/destroy/form.ts
|
|
@@ -3161,198 +3226,1111 @@ var scaffold2 = new Command37("scaffold").description("destroy a scaffold genera
|
|
|
3161
3226
|
)
|
|
3162
3227
|
);
|
|
3163
3228
|
|
|
3164
|
-
// src/commands/destroy.ts
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
import
|
|
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";
|
|
3169
3234
|
|
|
3170
|
-
// src/
|
|
3171
|
-
import
|
|
3172
|
-
import
|
|
3173
|
-
import
|
|
3174
|
-
|
|
3175
|
-
(components) => runCommand(async () => {
|
|
3176
|
-
const { workspaceRootDir } = await getWorkspace();
|
|
3177
|
-
const packageManager = (await detect4({ cwd: workspaceRootDir }))?.name ?? "npm";
|
|
3178
|
-
const resolved = resolveCommand4(packageManager, "execute", [
|
|
3179
|
-
"shadcn-svelte",
|
|
3180
|
-
"add",
|
|
3181
|
-
...components
|
|
3182
|
-
]);
|
|
3183
|
-
if (!resolved) {
|
|
3184
|
-
throw new Error(`Unable to resolve execute command for ${packageManager}`);
|
|
3185
|
-
}
|
|
3186
|
-
const args = [...resolved.args];
|
|
3187
|
-
if (packageManager === "npm") args.unshift("--yes");
|
|
3188
|
-
try {
|
|
3189
|
-
await exec2(resolved.command, args, {
|
|
3190
|
-
nodeOptions: { cwd: workspaceRootDir, stdio: "inherit" },
|
|
3191
|
-
throwOnError: true
|
|
3192
|
-
});
|
|
3193
|
-
} catch (error) {
|
|
3194
|
-
const typed = error;
|
|
3195
|
-
throw new Error(
|
|
3196
|
-
`Failed to execute '${resolved.command} ${args.join(" ")}': ${typed.message}`,
|
|
3197
|
-
{ cause: typed.output }
|
|
3198
|
-
);
|
|
3199
|
-
}
|
|
3200
|
-
reportResult({
|
|
3201
|
-
summary: `Added ${components.length} UI component(s).`,
|
|
3202
|
-
componentsAdded: components,
|
|
3203
|
-
nextSteps: [
|
|
3204
|
-
`Import a component with: import { Button } from '$lib/components/ui/button';`,
|
|
3205
|
-
"Tweak styling in src/lib/components/ui/<component>/*.svelte.",
|
|
3206
|
-
"Run `vela ui base <color>` to change the palette (slate, gray, zinc, stone, neutral)."
|
|
3207
|
-
]
|
|
3208
|
-
});
|
|
3209
|
-
}, "Failed to add UI components.")
|
|
3210
|
-
);
|
|
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";
|
|
3211
3240
|
|
|
3212
|
-
// src/
|
|
3241
|
+
// src/lib/deploy-config.ts
|
|
3213
3242
|
import fs17 from "node:fs";
|
|
3214
3243
|
import path17 from "node:path";
|
|
3215
|
-
import
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
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;
|
|
3221
3256
|
}
|
|
3222
|
-
return
|
|
3257
|
+
return null;
|
|
3223
3258
|
}
|
|
3224
|
-
function
|
|
3225
|
-
const
|
|
3226
|
-
|
|
3227
|
-
if (
|
|
3228
|
-
|
|
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 });
|
|
3229
3275
|
}
|
|
3230
|
-
return { root: rootMatch[0], dark: darkMatch[0] };
|
|
3231
3276
|
}
|
|
3232
|
-
function
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
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;
|
|
3241
3289
|
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
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 {
|
|
3245
3333
|
}
|
|
3246
|
-
return
|
|
3247
|
-
}
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
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
|
+
}
|
|
3268
3365
|
|
|
3269
|
-
// src/
|
|
3270
|
-
var
|
|
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
|
+
}
|
|
3271
3393
|
|
|
3272
|
-
// src/
|
|
3273
|
-
import
|
|
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
|
+
}
|
|
3274
3411
|
|
|
3275
|
-
// src/
|
|
3412
|
+
// src/lib/ssh.ts
|
|
3276
3413
|
import fs18 from "node:fs";
|
|
3414
|
+
import os2 from "node:os";
|
|
3277
3415
|
import path18 from "node:path";
|
|
3278
|
-
import
|
|
3279
|
-
import * as p20 from "@clack/prompts";
|
|
3280
|
-
|
|
3281
|
-
// src/commands/legal/shared.ts
|
|
3416
|
+
import crypto2 from "node:crypto";
|
|
3282
3417
|
import process13 from "node:process";
|
|
3283
|
-
import
|
|
3284
|
-
var
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
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");
|
|
3292
3445
|
}
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
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
|
+
);
|
|
3301
3480
|
}
|
|
3302
|
-
}
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
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);
|
|
3321
3504
|
}
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
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
|
+
);
|
|
3330
3555
|
}
|
|
3331
|
-
}
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
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()}`);
|
|
3338
3596
|
}
|
|
3339
|
-
}
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
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()}`);
|
|
3346
3617
|
}
|
|
3347
|
-
}
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
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
|
+
}
|
|
3352
3640
|
};
|
|
3353
|
-
|
|
3354
|
-
const
|
|
3355
|
-
|
|
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);
|
|
4330
|
+
};
|
|
4331
|
+
async function contactMethods(type) {
|
|
4332
|
+
const selection = await p21.multiselect({
|
|
4333
|
+
message: `How can users contact you for any questions regarding your ${type === "privacy" ? "Privacy Policy" : "Terms & Conditions"}? Check all that apply`,
|
|
3356
4334
|
options: [
|
|
3357
4335
|
{ value: "email", label: "By email" },
|
|
3358
4336
|
{ value: "page", label: "By visiting a page on our website" },
|
|
@@ -3360,11 +4338,11 @@ async function contactMethods(type) {
|
|
|
3360
4338
|
{ value: "mail", label: "By sending post mail" }
|
|
3361
4339
|
]
|
|
3362
4340
|
});
|
|
3363
|
-
if (
|
|
4341
|
+
if (p21.isCancel(selection)) onCancel();
|
|
3364
4342
|
const contact = selection;
|
|
3365
4343
|
const details = {};
|
|
3366
4344
|
if (contact.includes("email")) {
|
|
3367
|
-
const email3 = await
|
|
4345
|
+
const email3 = await p21.text({
|
|
3368
4346
|
message: "What's the email?",
|
|
3369
4347
|
placeholder: "office@mycompany.com",
|
|
3370
4348
|
validate: (value) => {
|
|
@@ -3373,11 +4351,11 @@ async function contactMethods(type) {
|
|
|
3373
4351
|
}
|
|
3374
4352
|
}
|
|
3375
4353
|
});
|
|
3376
|
-
if (
|
|
4354
|
+
if (p21.isCancel(email3)) onCancel();
|
|
3377
4355
|
details.email = email3;
|
|
3378
4356
|
}
|
|
3379
4357
|
if (contact.includes("page")) {
|
|
3380
|
-
const page = await
|
|
4358
|
+
const page = await p21.text({
|
|
3381
4359
|
message: "What's the link?",
|
|
3382
4360
|
placeholder: "http://www.mycompany.com/contact",
|
|
3383
4361
|
validate: (value) => {
|
|
@@ -3386,11 +4364,11 @@ async function contactMethods(type) {
|
|
|
3386
4364
|
}
|
|
3387
4365
|
}
|
|
3388
4366
|
});
|
|
3389
|
-
if (
|
|
4367
|
+
if (p21.isCancel(page)) onCancel();
|
|
3390
4368
|
details.page = page;
|
|
3391
4369
|
}
|
|
3392
4370
|
if (contact.includes("phone")) {
|
|
3393
|
-
const phone = await
|
|
4371
|
+
const phone = await p21.text({
|
|
3394
4372
|
message: "What's the phone number?",
|
|
3395
4373
|
placeholder: "408.996.1010",
|
|
3396
4374
|
validate: (value) => {
|
|
@@ -3399,11 +4377,11 @@ async function contactMethods(type) {
|
|
|
3399
4377
|
}
|
|
3400
4378
|
}
|
|
3401
4379
|
});
|
|
3402
|
-
if (
|
|
4380
|
+
if (p21.isCancel(phone)) onCancel();
|
|
3403
4381
|
details.phone = phone;
|
|
3404
4382
|
}
|
|
3405
4383
|
if (contact.includes("mail")) {
|
|
3406
|
-
const address = await
|
|
4384
|
+
const address = await p21.text({
|
|
3407
4385
|
message: "What's the address?",
|
|
3408
4386
|
placeholder: "767 Fifth Avenue New York, NY 10153, United States",
|
|
3409
4387
|
validate: (value) => {
|
|
@@ -3412,7 +4390,7 @@ async function contactMethods(type) {
|
|
|
3412
4390
|
}
|
|
3413
4391
|
}
|
|
3414
4392
|
});
|
|
3415
|
-
if (
|
|
4393
|
+
if (p21.isCancel(address)) onCancel();
|
|
3416
4394
|
details.address = address;
|
|
3417
4395
|
}
|
|
3418
4396
|
return { methods: contact, details };
|
|
@@ -3808,7 +4786,7 @@ var generateTermsHtml = (answers) => {
|
|
|
3808
4786
|
};
|
|
3809
4787
|
async function termsAction() {
|
|
3810
4788
|
const { workspaceRootDir, publicRoutesDir } = await getWorkspace();
|
|
3811
|
-
const core = await
|
|
4789
|
+
const core = await p22.group(
|
|
3812
4790
|
{
|
|
3813
4791
|
websiteUrl: sharedFields.websiteUrl,
|
|
3814
4792
|
websiteName: sharedFields.websiteName,
|
|
@@ -3820,25 +4798,25 @@ async function termsAction() {
|
|
|
3820
4798
|
},
|
|
3821
4799
|
{ onCancel }
|
|
3822
4800
|
);
|
|
3823
|
-
const accounts = await
|
|
4801
|
+
const accounts = await p22.select({
|
|
3824
4802
|
message: "Can users create accounts?",
|
|
3825
4803
|
options: [
|
|
3826
4804
|
{ value: "yes", label: "Yes, users can create accounts" },
|
|
3827
4805
|
{ value: "no", label: "No" }
|
|
3828
4806
|
]
|
|
3829
4807
|
});
|
|
3830
|
-
if (
|
|
3831
|
-
const userContent = await
|
|
4808
|
+
if (p22.isCancel(accounts)) onCancel();
|
|
4809
|
+
const userContent = await p22.select({
|
|
3832
4810
|
message: "Can users create and/or upload content (ie. text, images)?",
|
|
3833
4811
|
options: [
|
|
3834
4812
|
{ value: "yes", label: "Yes, users can create and/or upload content" },
|
|
3835
4813
|
{ value: "no", label: "No" }
|
|
3836
4814
|
]
|
|
3837
4815
|
});
|
|
3838
|
-
if (
|
|
4816
|
+
if (p22.isCancel(userContent)) onCancel();
|
|
3839
4817
|
let infringementEmail;
|
|
3840
4818
|
if (userContent === "yes") {
|
|
3841
|
-
const email3 = await
|
|
4819
|
+
const email3 = await p22.text({
|
|
3842
4820
|
message: "What's the email address where you will receive infringements notices?",
|
|
3843
4821
|
placeholder: "dmca@website.com",
|
|
3844
4822
|
validate: (value) => {
|
|
@@ -3847,10 +4825,10 @@ async function termsAction() {
|
|
|
3847
4825
|
}
|
|
3848
4826
|
}
|
|
3849
4827
|
});
|
|
3850
|
-
if (
|
|
4828
|
+
if (p22.isCancel(email3)) onCancel();
|
|
3851
4829
|
infringementEmail = email3;
|
|
3852
4830
|
}
|
|
3853
|
-
const canBuyGoods = await
|
|
4831
|
+
const canBuyGoods = await p22.select({
|
|
3854
4832
|
message: "Can users buy goods (products, items)?",
|
|
3855
4833
|
options: [
|
|
3856
4834
|
{
|
|
@@ -3860,28 +4838,28 @@ async function termsAction() {
|
|
|
3860
4838
|
{ value: "no", label: "No" }
|
|
3861
4839
|
]
|
|
3862
4840
|
});
|
|
3863
|
-
if (
|
|
3864
|
-
const subscriptions2 = await
|
|
4841
|
+
if (p22.isCancel(canBuyGoods)) onCancel();
|
|
4842
|
+
const subscriptions2 = await p22.select({
|
|
3865
4843
|
message: "Do you offer subscription plans?",
|
|
3866
4844
|
options: [
|
|
3867
4845
|
{ value: "yes", label: "Yes, we offer subscription plans" },
|
|
3868
4846
|
{ value: "no", label: "No" }
|
|
3869
4847
|
]
|
|
3870
4848
|
});
|
|
3871
|
-
if (
|
|
4849
|
+
if (p22.isCancel(subscriptions2)) onCancel();
|
|
3872
4850
|
let freeTrial;
|
|
3873
4851
|
if (subscriptions2 === "yes") {
|
|
3874
|
-
const ft = await
|
|
4852
|
+
const ft = await p22.select({
|
|
3875
4853
|
message: "Do you offer a free trial?",
|
|
3876
4854
|
options: [
|
|
3877
4855
|
{ value: "yes", label: "Yes" },
|
|
3878
4856
|
{ value: "no", label: "No" }
|
|
3879
4857
|
]
|
|
3880
4858
|
});
|
|
3881
|
-
if (
|
|
4859
|
+
if (p22.isCancel(ft)) onCancel();
|
|
3882
4860
|
freeTrial = ft;
|
|
3883
4861
|
}
|
|
3884
|
-
const exclusiveContent = await
|
|
4862
|
+
const exclusiveContent = await p22.select({
|
|
3885
4863
|
message: "Do you want to make it clear that your own content & trademarks are your exclusive property?",
|
|
3886
4864
|
options: [
|
|
3887
4865
|
{
|
|
@@ -3891,24 +4869,24 @@ async function termsAction() {
|
|
|
3891
4869
|
{ value: "no", label: "No" }
|
|
3892
4870
|
]
|
|
3893
4871
|
});
|
|
3894
|
-
if (
|
|
3895
|
-
const feedbackReuse = await
|
|
4872
|
+
if (p22.isCancel(exclusiveContent)) onCancel();
|
|
4873
|
+
const feedbackReuse = await p22.select({
|
|
3896
4874
|
message: "If users provide you feedback & suggestions, do you want to use this feedback without compensation or credits given?",
|
|
3897
4875
|
options: [
|
|
3898
4876
|
{ value: "yes", label: "Yes, we may implement any feedback or suggestions we receive" },
|
|
3899
4877
|
{ value: "no", label: "No" }
|
|
3900
4878
|
]
|
|
3901
4879
|
});
|
|
3902
|
-
if (
|
|
3903
|
-
const promotions = await
|
|
4880
|
+
if (p22.isCancel(feedbackReuse)) onCancel();
|
|
4881
|
+
const promotions = await p22.select({
|
|
3904
4882
|
message: "Do you plan to offer promotions, contests, sweepstakes?",
|
|
3905
4883
|
options: [
|
|
3906
4884
|
{ value: "yes", label: "Yes, we may offer promotions, contests, sweepstakes" },
|
|
3907
4885
|
{ value: "no", label: "No" }
|
|
3908
4886
|
]
|
|
3909
4887
|
});
|
|
3910
|
-
if (
|
|
3911
|
-
const mobileAppRaw = await
|
|
4888
|
+
if (p22.isCancel(promotions)) onCancel();
|
|
4889
|
+
const mobileAppRaw = await p22.multiselect({
|
|
3912
4890
|
message: "Is the Service distributed through any mobile app stores? Check all that apply",
|
|
3913
4891
|
options: [
|
|
3914
4892
|
{ value: "apple", label: "Apple App Store" },
|
|
@@ -3916,7 +4894,7 @@ async function termsAction() {
|
|
|
3916
4894
|
],
|
|
3917
4895
|
required: false
|
|
3918
4896
|
});
|
|
3919
|
-
if (
|
|
4897
|
+
if (p22.isCancel(mobileAppRaw)) onCancel();
|
|
3920
4898
|
const mobileApp = mobileAppRaw ?? [];
|
|
3921
4899
|
const contact = await contactMethods("terms");
|
|
3922
4900
|
const html = generateTermsHtml({
|
|
@@ -3933,22 +4911,22 @@ async function termsAction() {
|
|
|
3933
4911
|
mobileApp,
|
|
3934
4912
|
contact
|
|
3935
4913
|
});
|
|
3936
|
-
const termsPage =
|
|
4914
|
+
const termsPage = path21.join(
|
|
3937
4915
|
workspaceRootDir,
|
|
3938
4916
|
publicRoutesDir,
|
|
3939
4917
|
LEGAL_DIR,
|
|
3940
4918
|
"terms",
|
|
3941
4919
|
"+page.svelte"
|
|
3942
4920
|
);
|
|
3943
|
-
const termsPageTs =
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
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(
|
|
3947
4925
|
termsPageTs,
|
|
3948
4926
|
pageMetaTagsLoader("Terms of Service", `Terms of Service for ${core.websiteName}`)
|
|
3949
4927
|
);
|
|
3950
|
-
const relativeTermsPage =
|
|
3951
|
-
const relativeTermsPageTs =
|
|
4928
|
+
const relativeTermsPage = path21.relative(workspaceRootDir, termsPage);
|
|
4929
|
+
const relativeTermsPageTs = path21.relative(workspaceRootDir, termsPageTs);
|
|
3952
4930
|
reportResult({
|
|
3953
4931
|
summary: "Generated placeholder terms and conditions.",
|
|
3954
4932
|
filesCreated: [relativeTermsPage, relativeTermsPageTs],
|
|
@@ -3959,13 +4937,13 @@ async function termsAction() {
|
|
|
3959
4937
|
]
|
|
3960
4938
|
});
|
|
3961
4939
|
}
|
|
3962
|
-
var terms = new
|
|
4940
|
+
var terms = new Command43("terms").description("generate placeholder terms and conditions").configureHelp(helpConfig).action(() => runCommand(termsAction, "Failed to generate terms and conditions."));
|
|
3963
4941
|
|
|
3964
4942
|
// src/commands/legal/privacy.ts
|
|
3965
|
-
import
|
|
3966
|
-
import
|
|
3967
|
-
import { Command as
|
|
3968
|
-
import * as
|
|
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";
|
|
3969
4947
|
var mapLabels = {
|
|
3970
4948
|
personalInfo: {
|
|
3971
4949
|
email: "Email address",
|
|
@@ -4052,9 +5030,9 @@ var compute2 = (a) => {
|
|
|
4052
5030
|
});
|
|
4053
5031
|
const piSelections = a.personalInfo ?? [];
|
|
4054
5032
|
const piLabels = piSelections.map(
|
|
4055
|
-
(
|
|
5033
|
+
(v9) => mapLabels.personalInfo[v9] ?? v9
|
|
4056
5034
|
);
|
|
4057
|
-
const lookup = (map, vs) => (vs ?? []).map((
|
|
5035
|
+
const lookup = (map, vs) => (vs ?? []).map((v9) => map[v9] ?? v9);
|
|
4058
5036
|
const companyName = a.core?.entityType === "business" ? a.core?.businessName ?? a.core?.websiteName : a.core?.websiteName ?? "Our Company";
|
|
4059
5037
|
const companyAddress = a.core?.entityType === "business" ? a.core?.businessAddress ?? "" : "";
|
|
4060
5038
|
const websiteName = a.core?.websiteName ?? "our website";
|
|
@@ -4248,7 +5226,7 @@ var sectionShare = (a, c) => {
|
|
|
4248
5226
|
};
|
|
4249
5227
|
var sectionSaleSharing = (a, c) => {
|
|
4250
5228
|
const sells = a.showAds === "yes" || a.remarketing === "yes";
|
|
4251
|
-
const
|
|
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>';
|
|
4252
5230
|
let recipients = "";
|
|
4253
5231
|
if (sells) {
|
|
4254
5232
|
const partners = [...c.adsPlatforms, ...c.remarketingPlatforms];
|
|
@@ -4260,7 +5238,7 @@ var sectionSaleSharing = (a, c) => {
|
|
|
4260
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>`;
|
|
4261
5239
|
return titledSection(
|
|
4262
5240
|
"Sale or Sharing of Personal Information; Targeted Advertising",
|
|
4263
|
-
`${
|
|
5241
|
+
`${intro3}${recipients}${optOut}`
|
|
4264
5242
|
);
|
|
4265
5243
|
};
|
|
4266
5244
|
var sectionCookies = (a, c) => {
|
|
@@ -4299,8 +5277,8 @@ var sectionAutomated = () => titledSection(
|
|
|
4299
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>"
|
|
4300
5278
|
);
|
|
4301
5279
|
var sectionRights = (a, c) => {
|
|
4302
|
-
const
|
|
4303
|
-
let body =
|
|
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;
|
|
4304
5282
|
if (a.usStates === "yes") {
|
|
4305
5283
|
body += subSection("U.S. State Privacy Rights", usStateRightsBlock(c.companyName));
|
|
4306
5284
|
}
|
|
@@ -4383,12 +5361,12 @@ var generatePrivacyHtml = (answers) => {
|
|
|
4383
5361
|
return `<section data-role="content">${sections.join("")}</section>`;
|
|
4384
5362
|
};
|
|
4385
5363
|
async function promptWithCustom(message, options, customMessage) {
|
|
4386
|
-
const selection = await
|
|
4387
|
-
if (
|
|
5364
|
+
const selection = await p23.multiselect({ message, options });
|
|
5365
|
+
if (p23.isCancel(selection)) onCancel();
|
|
4388
5366
|
const values = selection;
|
|
4389
5367
|
if (values.includes("custom")) {
|
|
4390
|
-
const custom = await
|
|
4391
|
-
if (
|
|
5368
|
+
const custom = await p23.text({ message: customMessage });
|
|
5369
|
+
if (p23.isCancel(custom)) onCancel();
|
|
4392
5370
|
const idx = values.indexOf("custom");
|
|
4393
5371
|
if (idx !== -1) values.splice(idx, 1);
|
|
4394
5372
|
if (custom) values.push(String(custom));
|
|
@@ -4397,7 +5375,7 @@ async function promptWithCustom(message, options, customMessage) {
|
|
|
4397
5375
|
}
|
|
4398
5376
|
async function privacyAction() {
|
|
4399
5377
|
const { workspaceRootDir, publicRoutesDir } = await getWorkspace();
|
|
4400
|
-
const core = await
|
|
5378
|
+
const core = await p23.group(
|
|
4401
5379
|
{
|
|
4402
5380
|
websiteUrl: sharedFields.websiteUrl,
|
|
4403
5381
|
websiteName: sharedFields.websiteName,
|
|
@@ -4409,7 +5387,7 @@ async function privacyAction() {
|
|
|
4409
5387
|
},
|
|
4410
5388
|
{ onCancel }
|
|
4411
5389
|
);
|
|
4412
|
-
const personalInfo = await
|
|
5390
|
+
const personalInfo = await p23.multiselect({
|
|
4413
5391
|
message: "What kind of personal information do you collect from users? Check all that apply",
|
|
4414
5392
|
options: [
|
|
4415
5393
|
{ value: "email", label: "Email address" },
|
|
@@ -4424,16 +5402,16 @@ async function privacyAction() {
|
|
|
4424
5402
|
],
|
|
4425
5403
|
required: false
|
|
4426
5404
|
});
|
|
4427
|
-
if (
|
|
5405
|
+
if (p23.isCancel(personalInfo)) onCancel();
|
|
4428
5406
|
const contact = await contactMethods("privacy");
|
|
4429
|
-
const tracking = await
|
|
5407
|
+
const tracking = await p23.select({
|
|
4430
5408
|
message: "Do you use tracking and/or analytics tools, such as Google Analytics?",
|
|
4431
5409
|
options: [
|
|
4432
5410
|
{ value: "yes", label: "Yes, we use Google Analytics or other related tools" },
|
|
4433
5411
|
{ value: "no", label: "No" }
|
|
4434
5412
|
]
|
|
4435
5413
|
});
|
|
4436
|
-
if (
|
|
5414
|
+
if (p23.isCancel(tracking)) onCancel();
|
|
4437
5415
|
let trackingTools;
|
|
4438
5416
|
if (tracking === "yes") {
|
|
4439
5417
|
trackingTools = await promptWithCustom(
|
|
@@ -4452,7 +5430,7 @@ async function privacyAction() {
|
|
|
4452
5430
|
"Enter your custom tracking/analytics tool name"
|
|
4453
5431
|
);
|
|
4454
5432
|
}
|
|
4455
|
-
const sendEmails = await
|
|
5433
|
+
const sendEmails = await p23.select({
|
|
4456
5434
|
message: "Do you send emails to users?",
|
|
4457
5435
|
options: [
|
|
4458
5436
|
{
|
|
@@ -4462,7 +5440,7 @@ async function privacyAction() {
|
|
|
4462
5440
|
{ value: "no", label: "No" }
|
|
4463
5441
|
]
|
|
4464
5442
|
});
|
|
4465
|
-
if (
|
|
5443
|
+
if (p23.isCancel(sendEmails)) onCancel();
|
|
4466
5444
|
let emailPlatforms;
|
|
4467
5445
|
if (sendEmails === "yes") {
|
|
4468
5446
|
emailPlatforms = await promptWithCustom(
|
|
@@ -4477,14 +5455,14 @@ async function privacyAction() {
|
|
|
4477
5455
|
"Enter your custom email platform"
|
|
4478
5456
|
);
|
|
4479
5457
|
}
|
|
4480
|
-
const showAds = await
|
|
5458
|
+
const showAds = await p23.select({
|
|
4481
5459
|
message: "Do you show ads?",
|
|
4482
5460
|
options: [
|
|
4483
5461
|
{ value: "yes", label: "Yes, we show ads" },
|
|
4484
5462
|
{ value: "no", label: "No" }
|
|
4485
5463
|
]
|
|
4486
5464
|
});
|
|
4487
|
-
if (
|
|
5465
|
+
if (p23.isCancel(showAds)) onCancel();
|
|
4488
5466
|
let adsPlatforms;
|
|
4489
5467
|
if (showAds === "yes") {
|
|
4490
5468
|
adsPlatforms = await promptWithCustom(
|
|
@@ -4507,7 +5485,7 @@ async function privacyAction() {
|
|
|
4507
5485
|
"Enter your custom ads platform"
|
|
4508
5486
|
);
|
|
4509
5487
|
}
|
|
4510
|
-
const canPay = await
|
|
5488
|
+
const canPay = await p23.select({
|
|
4511
5489
|
message: "Can users pay for products or services?",
|
|
4512
5490
|
options: [
|
|
4513
5491
|
{ value: "yes", label: "Yes, users can pay for our products/services" },
|
|
@@ -4517,7 +5495,7 @@ async function privacyAction() {
|
|
|
4517
5495
|
}
|
|
4518
5496
|
]
|
|
4519
5497
|
});
|
|
4520
|
-
if (
|
|
5498
|
+
if (p23.isCancel(canPay)) onCancel();
|
|
4521
5499
|
let paymentProcessors;
|
|
4522
5500
|
if (canPay === "yes") {
|
|
4523
5501
|
paymentProcessors = await promptWithCustom(
|
|
@@ -4548,14 +5526,14 @@ async function privacyAction() {
|
|
|
4548
5526
|
"Enter your custom payment processor/method"
|
|
4549
5527
|
);
|
|
4550
5528
|
}
|
|
4551
|
-
const remarketing = await
|
|
5529
|
+
const remarketing = await p23.select({
|
|
4552
5530
|
message: "Do you use remarketing services for marketing & advertising purposes?",
|
|
4553
5531
|
options: [
|
|
4554
5532
|
{ value: "yes", label: "Yes, we use remarketing services to advertise our business" },
|
|
4555
5533
|
{ value: "no", label: "No" }
|
|
4556
5534
|
]
|
|
4557
5535
|
});
|
|
4558
|
-
if (
|
|
5536
|
+
if (p23.isCancel(remarketing)) onCancel();
|
|
4559
5537
|
let remarketingPlatforms;
|
|
4560
5538
|
if (remarketing === "yes") {
|
|
4561
5539
|
remarketingPlatforms = await promptWithCustom(
|
|
@@ -4574,7 +5552,7 @@ async function privacyAction() {
|
|
|
4574
5552
|
"Enter your custom remarketing platform"
|
|
4575
5553
|
);
|
|
4576
5554
|
}
|
|
4577
|
-
const providersRaw = await
|
|
5555
|
+
const providersRaw = await p23.multiselect({
|
|
4578
5556
|
message: "Select if you use any of the following providers",
|
|
4579
5557
|
options: [
|
|
4580
5558
|
{ value: "recaptcha", label: "Invisible reCAPTCHA" },
|
|
@@ -4585,16 +5563,16 @@ async function privacyAction() {
|
|
|
4585
5563
|
],
|
|
4586
5564
|
required: false
|
|
4587
5565
|
});
|
|
4588
|
-
if (
|
|
5566
|
+
if (p23.isCancel(providersRaw)) onCancel();
|
|
4589
5567
|
const providers = providersRaw;
|
|
4590
5568
|
if (providers.includes("custom")) {
|
|
4591
|
-
const custom = await
|
|
4592
|
-
if (
|
|
5569
|
+
const custom = await p23.text({ message: "Enter your custom provider" });
|
|
5570
|
+
if (p23.isCancel(custom)) onCancel();
|
|
4593
5571
|
const idx = providers.indexOf("custom");
|
|
4594
5572
|
if (idx !== -1) providers.splice(idx, 1);
|
|
4595
5573
|
if (custom) providers.push(String(custom));
|
|
4596
5574
|
}
|
|
4597
|
-
const usStates = await
|
|
5575
|
+
const usStates = await p23.select({
|
|
4598
5576
|
message: "Include U.S. state privacy rights (CCPA/CPRA, VCDPA, CPA, CTDPA, UCPA, TX, OR, etc.)?",
|
|
4599
5577
|
options: [
|
|
4600
5578
|
{
|
|
@@ -4605,55 +5583,55 @@ async function privacyAction() {
|
|
|
4605
5583
|
],
|
|
4606
5584
|
initialValue: "yes"
|
|
4607
5585
|
});
|
|
4608
|
-
if (
|
|
4609
|
-
const gdpr = await
|
|
5586
|
+
if (p23.isCancel(usStates)) onCancel();
|
|
5587
|
+
const gdpr = await p23.select({
|
|
4610
5588
|
message: "Do you want your Privacy Policy to include GDPR / UK GDPR wording?",
|
|
4611
5589
|
options: [
|
|
4612
5590
|
{ value: "yes", label: "Yes. Include GDPR rights for EEA, UK, and Swiss residents" },
|
|
4613
5591
|
{ value: "no", label: "No" }
|
|
4614
5592
|
]
|
|
4615
5593
|
});
|
|
4616
|
-
if (
|
|
5594
|
+
if (p23.isCancel(gdpr)) onCancel();
|
|
4617
5595
|
let facebookFanPage = "no";
|
|
4618
5596
|
const facebookDetails = { name: "", url: "" };
|
|
4619
5597
|
if (gdpr === "yes") {
|
|
4620
|
-
const fan = await
|
|
5598
|
+
const fan = await p23.select({
|
|
4621
5599
|
message: "Do you have a Facebook Fan Page?",
|
|
4622
5600
|
options: [
|
|
4623
5601
|
{ value: "yes", label: "Yes, we have a Facebook Fan Page" },
|
|
4624
5602
|
{ value: "no", label: "No" }
|
|
4625
5603
|
]
|
|
4626
5604
|
});
|
|
4627
|
-
if (
|
|
5605
|
+
if (p23.isCancel(fan)) onCancel();
|
|
4628
5606
|
facebookFanPage = fan;
|
|
4629
5607
|
if (facebookFanPage === "yes") {
|
|
4630
|
-
const name = await
|
|
5608
|
+
const name = await p23.text({
|
|
4631
5609
|
message: "What is the name of the Facebook Fan Page?",
|
|
4632
5610
|
placeholder: "My Facebook Page"
|
|
4633
5611
|
});
|
|
4634
|
-
if (
|
|
5612
|
+
if (p23.isCancel(name)) onCancel();
|
|
4635
5613
|
facebookDetails.name = name;
|
|
4636
|
-
const url = await
|
|
5614
|
+
const url = await p23.text({
|
|
4637
5615
|
message: "What is the URL of the Facebook Fan Page?",
|
|
4638
5616
|
placeholder: "https://facebook.com/my-facebook-page"
|
|
4639
5617
|
});
|
|
4640
|
-
if (
|
|
5618
|
+
if (p23.isCancel(url)) onCancel();
|
|
4641
5619
|
facebookDetails.url = url;
|
|
4642
5620
|
}
|
|
4643
5621
|
}
|
|
4644
|
-
const kids = await
|
|
5622
|
+
const kids = await p23.select({
|
|
4645
5623
|
message: "Do you collect information from kids under the age of 13?",
|
|
4646
5624
|
options: [
|
|
4647
5625
|
{ value: "yes", label: "Yes. We collect information from children under the age of 13" },
|
|
4648
5626
|
{ value: "no", label: "No" }
|
|
4649
5627
|
]
|
|
4650
5628
|
});
|
|
4651
|
-
if (
|
|
4652
|
-
const retention = await
|
|
5629
|
+
if (p23.isCancel(kids)) onCancel();
|
|
5630
|
+
const retention = await p23.text({
|
|
4653
5631
|
message: "How long do you retain personal information? (leave blank for default wording)",
|
|
4654
5632
|
placeholder: "e.g. 12 months after account closure"
|
|
4655
5633
|
});
|
|
4656
|
-
if (
|
|
5634
|
+
if (p23.isCancel(retention)) onCancel();
|
|
4657
5635
|
const html = generatePrivacyHtml({
|
|
4658
5636
|
core,
|
|
4659
5637
|
personalInfo: personalInfo ?? [],
|
|
@@ -4676,28 +5654,28 @@ async function privacyAction() {
|
|
|
4676
5654
|
kids,
|
|
4677
5655
|
retention
|
|
4678
5656
|
});
|
|
4679
|
-
const privacyPage =
|
|
5657
|
+
const privacyPage = path22.join(
|
|
4680
5658
|
workspaceRootDir,
|
|
4681
5659
|
publicRoutesDir,
|
|
4682
5660
|
LEGAL_DIR,
|
|
4683
5661
|
"privacy",
|
|
4684
5662
|
"+page.svelte"
|
|
4685
5663
|
);
|
|
4686
|
-
const privacyPageTs =
|
|
5664
|
+
const privacyPageTs = path22.join(
|
|
4687
5665
|
workspaceRootDir,
|
|
4688
5666
|
publicRoutesDir,
|
|
4689
5667
|
LEGAL_DIR,
|
|
4690
5668
|
"privacy",
|
|
4691
5669
|
"+page.ts"
|
|
4692
5670
|
);
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
5671
|
+
fs22.mkdirSync(path22.dirname(privacyPage), { recursive: true });
|
|
5672
|
+
fs22.writeFileSync(privacyPage, html);
|
|
5673
|
+
fs22.writeFileSync(
|
|
4696
5674
|
privacyPageTs,
|
|
4697
5675
|
pageMetaTagsLoader("Privacy Policy", `Privacy Policy for ${core.websiteName}`)
|
|
4698
5676
|
);
|
|
4699
|
-
const relativePrivacyPage =
|
|
4700
|
-
const relativePrivacyPageTs =
|
|
5677
|
+
const relativePrivacyPage = path22.relative(workspaceRootDir, privacyPage);
|
|
5678
|
+
const relativePrivacyPageTs = path22.relative(workspaceRootDir, privacyPageTs);
|
|
4701
5679
|
reportResult({
|
|
4702
5680
|
summary: "Generated placeholder privacy policy.",
|
|
4703
5681
|
filesCreated: [relativePrivacyPage, relativePrivacyPageTs],
|
|
@@ -4708,23 +5686,23 @@ async function privacyAction() {
|
|
|
4708
5686
|
]
|
|
4709
5687
|
});
|
|
4710
5688
|
}
|
|
4711
|
-
var privacy = new
|
|
5689
|
+
var privacy = new Command44("privacy").description("generate placeholder privacy policy").configureHelp(helpConfig).action(() => runCommand(privacyAction, "Failed to generate privacy policy."));
|
|
4712
5690
|
|
|
4713
5691
|
// src/commands/legal.ts
|
|
4714
|
-
var legal = new
|
|
5692
|
+
var legal = new Command45("legal").description("generate placeholder legal documents").configureHelp(helpConfig).addCommand(terms).addCommand(privacy);
|
|
4715
5693
|
|
|
4716
5694
|
// src/commands/fixtures.ts
|
|
4717
|
-
import { Command as
|
|
5695
|
+
import { Command as Command51 } from "commander";
|
|
4718
5696
|
|
|
4719
5697
|
// src/commands/fixtures/load.ts
|
|
4720
|
-
import
|
|
4721
|
-
import
|
|
4722
|
-
import { Command as
|
|
5698
|
+
import fs23 from "node:fs";
|
|
5699
|
+
import path23 from "node:path";
|
|
5700
|
+
import { Command as Command46 } from "commander";
|
|
4723
5701
|
function getFixtureFiles(cwd) {
|
|
4724
|
-
const fixturesDir =
|
|
4725
|
-
if (!
|
|
4726
|
-
return
|
|
4727
|
-
const file =
|
|
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);
|
|
4728
5706
|
const nameWithoutExt = file.replace(/\.json$/i, "");
|
|
4729
5707
|
const collectionName = nameWithoutExt.replace(/^\d+[-_]?/, "");
|
|
4730
5708
|
return { collectionName, fixturePath };
|
|
@@ -4745,15 +5723,15 @@ async function loadFixtures(pb, workspaceRootDir) {
|
|
|
4745
5723
|
`${collectionName} collection already has fixture records. Run \`vela fixtures reset\` to clear and reload, or \`vela fixtures clear\` first.`
|
|
4746
5724
|
);
|
|
4747
5725
|
}
|
|
4748
|
-
const items = JSON.parse(
|
|
5726
|
+
const items = JSON.parse(fs23.readFileSync(fixturePath, "utf8"));
|
|
4749
5727
|
for (const fixture of items) {
|
|
4750
5728
|
await pb.collection(collectionName).create(fixture);
|
|
4751
5729
|
}
|
|
4752
|
-
loaded.push(`${
|
|
5730
|
+
loaded.push(`${path23.relative(workspaceRootDir, fixturePath)} (${items.length} records)`);
|
|
4753
5731
|
}
|
|
4754
5732
|
return loaded;
|
|
4755
5733
|
}
|
|
4756
|
-
var load = new
|
|
5734
|
+
var load = new Command46("load").description("load fixtures into the database").configureHelp(helpConfig).action(
|
|
4757
5735
|
() => runCommand(async () => {
|
|
4758
5736
|
const { workspaceRootDir } = await getWorkspace();
|
|
4759
5737
|
let loaded = [];
|
|
@@ -4779,8 +5757,8 @@ var load = new Command45("load").description("load fixtures into the database").
|
|
|
4779
5757
|
);
|
|
4780
5758
|
|
|
4781
5759
|
// src/commands/fixtures/clear.ts
|
|
4782
|
-
import
|
|
4783
|
-
import { Command as
|
|
5760
|
+
import path24 from "node:path";
|
|
5761
|
+
import { Command as Command47 } from "commander";
|
|
4784
5762
|
|
|
4785
5763
|
// src/lib/collections.ts
|
|
4786
5764
|
function dependencyOrder(collections2, startingCollectionId) {
|
|
@@ -4842,12 +5820,12 @@ async function clearFixtures(pb, workspaceRootDir) {
|
|
|
4842
5820
|
await pb.collection(collectionName).delete(id);
|
|
4843
5821
|
}
|
|
4844
5822
|
const fixturePath = fixturePathByName.get(collectionName);
|
|
4845
|
-
const label = fixturePath ?
|
|
5823
|
+
const label = fixturePath ? path24.relative(workspaceRootDir, fixturePath) : collectionName;
|
|
4846
5824
|
cleared.push(`${label} (${records.length} records)`);
|
|
4847
5825
|
}
|
|
4848
5826
|
return cleared;
|
|
4849
5827
|
}
|
|
4850
|
-
var clear = new
|
|
5828
|
+
var clear = new Command47("clear").description("clear loaded fixtures").configureHelp(helpConfig).action(
|
|
4851
5829
|
() => runCommand(async () => {
|
|
4852
5830
|
const { workspaceRootDir } = await getWorkspace();
|
|
4853
5831
|
let cleared = [];
|
|
@@ -4876,8 +5854,8 @@ var clear = new Command46("clear").description("clear loaded fixtures").configur
|
|
|
4876
5854
|
);
|
|
4877
5855
|
|
|
4878
5856
|
// src/commands/fixtures/reset.ts
|
|
4879
|
-
import { Command as
|
|
4880
|
-
var reset = new
|
|
5857
|
+
import { Command as Command48 } from "commander";
|
|
5858
|
+
var reset = new Command48("reset").description("clear and reload fixtures").configureHelp(helpConfig).action(
|
|
4881
5859
|
() => runCommand(async () => {
|
|
4882
5860
|
const { workspaceRootDir } = await getWorkspace();
|
|
4883
5861
|
let cleared = [];
|
|
@@ -4907,10 +5885,10 @@ var reset = new Command47("reset").description("clear and reload fixtures").conf
|
|
|
4907
5885
|
);
|
|
4908
5886
|
|
|
4909
5887
|
// src/commands/fixtures/generate.ts
|
|
4910
|
-
import
|
|
4911
|
-
import
|
|
4912
|
-
import { Command as
|
|
4913
|
-
import * as
|
|
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";
|
|
4914
5892
|
import { annotate } from "annotate-json-schema";
|
|
4915
5893
|
import { createGenerator } from "json-schema-faker";
|
|
4916
5894
|
import { faker } from "@faker-js/faker";
|
|
@@ -4957,10 +5935,10 @@ async function loadCollections(pb) {
|
|
|
4957
5935
|
}));
|
|
4958
5936
|
}
|
|
4959
5937
|
async function generateFixtureFiles(pb, workspaceRootDir, opts) {
|
|
4960
|
-
const fixturesDir =
|
|
4961
|
-
|
|
4962
|
-
for (const file of
|
|
4963
|
-
if (file.endsWith(".json"))
|
|
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));
|
|
4964
5942
|
}
|
|
4965
5943
|
if (opts.seed !== void 0) faker.seed(opts.seed);
|
|
4966
5944
|
const generator = createGenerator({
|
|
@@ -5024,13 +6002,13 @@ async function generateFixtureFiles(pb, workspaceRootDir, opts) {
|
|
|
5024
6002
|
items.push(record);
|
|
5025
6003
|
}
|
|
5026
6004
|
const filename = `${padZeros(fileIndex, 2)}-${collection.name}.json`;
|
|
5027
|
-
|
|
5028
|
-
writtenFiles.push(`${
|
|
6005
|
+
fs24.writeFileSync(path25.join(fixturesDir, filename), JSON.stringify(items, null, 2));
|
|
6006
|
+
writtenFiles.push(`${path25.join("data", "fixtures", filename)} (${items.length} records)`);
|
|
5029
6007
|
fileIndex++;
|
|
5030
6008
|
}
|
|
5031
6009
|
return { writtenFiles, warnings };
|
|
5032
6010
|
}
|
|
5033
|
-
var generate2 = new
|
|
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(
|
|
5034
6012
|
(opts) => runCommand(async () => {
|
|
5035
6013
|
const { workspaceRootDir } = await getWorkspace();
|
|
5036
6014
|
const existing = getFixtureFiles(workspaceRootDir);
|
|
@@ -5059,7 +6037,7 @@ var generate2 = new Command48("generate").description("generate fixture data").o
|
|
|
5059
6037
|
seed: opts.seed
|
|
5060
6038
|
});
|
|
5061
6039
|
});
|
|
5062
|
-
for (const warning of result.warnings)
|
|
6040
|
+
for (const warning of result.warnings) p24.log.warn(warning);
|
|
5063
6041
|
if (result.writtenFiles.length === 0) {
|
|
5064
6042
|
reportResult({
|
|
5065
6043
|
summary: "No eligible collections found to generate fixtures for.",
|
|
@@ -5083,8 +6061,8 @@ var generate2 = new Command48("generate").description("generate fixture data").o
|
|
|
5083
6061
|
);
|
|
5084
6062
|
|
|
5085
6063
|
// src/commands/fixtures/regen.ts
|
|
5086
|
-
import { Command as
|
|
5087
|
-
import * as
|
|
6064
|
+
import { Command as Command50, InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
6065
|
+
import * as p25 from "@clack/prompts";
|
|
5088
6066
|
function parseCount2(value) {
|
|
5089
6067
|
const n = parseInt(value, 10);
|
|
5090
6068
|
if (!Number.isFinite(n) || n <= 0 || n > 999) {
|
|
@@ -5099,7 +6077,7 @@ function parseSeed2(value) {
|
|
|
5099
6077
|
}
|
|
5100
6078
|
return n;
|
|
5101
6079
|
}
|
|
5102
|
-
var regen = new
|
|
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(
|
|
5103
6081
|
(opts) => runCommand(async () => {
|
|
5104
6082
|
const { workspaceRootDir } = await getWorkspace();
|
|
5105
6083
|
let cleared = [];
|
|
@@ -5113,7 +6091,7 @@ var regen = new Command49("regen").description("clear the database, regenerate f
|
|
|
5113
6091
|
});
|
|
5114
6092
|
loaded = await loadFixtures(pb, workspaceRootDir);
|
|
5115
6093
|
});
|
|
5116
|
-
for (const warning of result.warnings)
|
|
6094
|
+
for (const warning of result.warnings) p25.log.warn(warning);
|
|
5117
6095
|
reportResult({
|
|
5118
6096
|
summary: `Regenerated fixtures (${loaded.length} collection(s) reloaded).`,
|
|
5119
6097
|
filesCreated: result.writtenFiles,
|
|
@@ -5128,26 +6106,26 @@ var regen = new Command49("regen").description("clear the database, regenerate f
|
|
|
5128
6106
|
);
|
|
5129
6107
|
|
|
5130
6108
|
// src/commands/fixtures.ts
|
|
5131
|
-
var fixtures = new
|
|
6109
|
+
var fixtures = new Command51("fixtures").description("manage fixture data").configureHelp(helpConfig).addCommand(generate2).addCommand(load).addCommand(clear).addCommand(reset).addCommand(regen);
|
|
5132
6110
|
|
|
5133
6111
|
// src/commands/seeds.ts
|
|
5134
|
-
import { Command as
|
|
6112
|
+
import { Command as Command55 } from "commander";
|
|
5135
6113
|
|
|
5136
6114
|
// src/commands/seeds/load.ts
|
|
5137
|
-
import
|
|
5138
|
-
import
|
|
5139
|
-
import { Command as
|
|
6115
|
+
import fs25 from "node:fs";
|
|
6116
|
+
import path26 from "node:path";
|
|
6117
|
+
import { Command as Command52 } from "commander";
|
|
5140
6118
|
function getSeedFiles(cwd) {
|
|
5141
|
-
const seedsDir =
|
|
5142
|
-
if (!
|
|
5143
|
-
return
|
|
5144
|
-
const file =
|
|
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);
|
|
5145
6123
|
const nameWithoutExt = file.replace(/\.json$/i, "");
|
|
5146
6124
|
const collectionName = nameWithoutExt.replace(/^\d+[-_]?/, "");
|
|
5147
6125
|
return { collectionName, seedPath };
|
|
5148
6126
|
});
|
|
5149
6127
|
}
|
|
5150
|
-
var load2 = new
|
|
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(
|
|
5151
6129
|
(opts) => runCommand(async () => {
|
|
5152
6130
|
const { workspaceRootDir } = await getWorkspace();
|
|
5153
6131
|
const seedFiles = getSeedFiles(workspaceRootDir);
|
|
@@ -5171,11 +6149,11 @@ var load2 = new Command51("load").description("load seeds into the database").op
|
|
|
5171
6149
|
}
|
|
5172
6150
|
}
|
|
5173
6151
|
for (const { collectionName, seedPath } of seedFiles) {
|
|
5174
|
-
const seeds2 = JSON.parse(
|
|
6152
|
+
const seeds2 = JSON.parse(fs25.readFileSync(seedPath, "utf8"));
|
|
5175
6153
|
for (const seed of seeds2) {
|
|
5176
6154
|
await pb.collection(collectionName).create(seed);
|
|
5177
6155
|
}
|
|
5178
|
-
loaded.push(`${
|
|
6156
|
+
loaded.push(`${path26.relative(workspaceRootDir, seedPath)} (${seeds2.length} records)`);
|
|
5179
6157
|
}
|
|
5180
6158
|
});
|
|
5181
6159
|
reportResult({
|
|
@@ -5190,9 +6168,9 @@ var load2 = new Command51("load").description("load seeds into the database").op
|
|
|
5190
6168
|
);
|
|
5191
6169
|
|
|
5192
6170
|
// src/commands/seeds/save.ts
|
|
5193
|
-
import
|
|
5194
|
-
import
|
|
5195
|
-
import { Command as
|
|
6171
|
+
import fs26 from "node:fs";
|
|
6172
|
+
import path27 from "node:path";
|
|
6173
|
+
import { Command as Command53 } from "commander";
|
|
5196
6174
|
var padZeros2 = (num, length) => num.toString().padStart(length, "0");
|
|
5197
6175
|
function filterSystemFields(record, systemFieldNames) {
|
|
5198
6176
|
const out = {};
|
|
@@ -5203,18 +6181,18 @@ function filterSystemFields(record, systemFieldNames) {
|
|
|
5203
6181
|
}
|
|
5204
6182
|
return out;
|
|
5205
6183
|
}
|
|
5206
|
-
var save = new
|
|
6184
|
+
var save = new Command53("save").description("save the current data as seeds").option("-f, --force", "overwrite existing seed files").configureHelp(helpConfig).action(
|
|
5207
6185
|
(opts) => runCommand(async () => {
|
|
5208
6186
|
const { workspaceRootDir } = await getWorkspace();
|
|
5209
|
-
const seedsPath =
|
|
6187
|
+
const seedsPath = path27.join(workspaceRootDir, "data", "seeds");
|
|
5210
6188
|
const existing = getSeedFiles(workspaceRootDir);
|
|
5211
6189
|
if (existing.length > 0 && !opts.force) {
|
|
5212
6190
|
throw new Error("Existing seed files found in data/seeds. Pass --force to overwrite.");
|
|
5213
6191
|
}
|
|
5214
|
-
|
|
6192
|
+
fs26.mkdirSync(seedsPath, { recursive: true });
|
|
5215
6193
|
if (opts.force) {
|
|
5216
|
-
for (const file of
|
|
5217
|
-
if (file.endsWith(".json"))
|
|
6194
|
+
for (const file of fs26.readdirSync(seedsPath)) {
|
|
6195
|
+
if (file.endsWith(".json")) fs26.unlinkSync(path27.join(seedsPath, file));
|
|
5218
6196
|
}
|
|
5219
6197
|
}
|
|
5220
6198
|
const saved = [];
|
|
@@ -5243,13 +6221,13 @@ var save = new Command52("save").description("save the current data as seeds").o
|
|
|
5243
6221
|
const filtered = records.map(
|
|
5244
6222
|
(r) => filterSystemFields(r, systemFieldNames)
|
|
5245
6223
|
);
|
|
5246
|
-
const relativeSeedPath =
|
|
6224
|
+
const relativeSeedPath = path27.join(
|
|
5247
6225
|
"data",
|
|
5248
6226
|
"seeds",
|
|
5249
6227
|
`${padZeros2(count, 2)}-${collectionName}.json`
|
|
5250
6228
|
);
|
|
5251
|
-
const seedPath =
|
|
5252
|
-
|
|
6229
|
+
const seedPath = path27.join(workspaceRootDir, relativeSeedPath);
|
|
6230
|
+
fs26.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
|
|
5253
6231
|
saved.push(`${relativeSeedPath} (${filtered.length} records)`);
|
|
5254
6232
|
count++;
|
|
5255
6233
|
}
|
|
@@ -5275,21 +6253,21 @@ var save = new Command52("save").description("save the current data as seeds").o
|
|
|
5275
6253
|
);
|
|
5276
6254
|
|
|
5277
6255
|
// src/commands/seeds/clear.ts
|
|
5278
|
-
import
|
|
5279
|
-
import
|
|
5280
|
-
import { Command as
|
|
6256
|
+
import fs27 from "node:fs";
|
|
6257
|
+
import path28 from "node:path";
|
|
6258
|
+
import { Command as Command54 } from "commander";
|
|
5281
6259
|
async function clearSeeds(pb, workspaceRootDir, seedFiles) {
|
|
5282
6260
|
const cleared = [];
|
|
5283
6261
|
for (const { collectionName, seedPath } of seedFiles) {
|
|
5284
|
-
const seeds2 = JSON.parse(
|
|
6262
|
+
const seeds2 = JSON.parse(fs27.readFileSync(seedPath, "utf8"));
|
|
5285
6263
|
for (const { id } of seeds2) {
|
|
5286
6264
|
await pb.collection(collectionName).delete(id);
|
|
5287
6265
|
}
|
|
5288
|
-
cleared.push(`${
|
|
6266
|
+
cleared.push(`${path28.relative(workspaceRootDir, seedPath)} (${seeds2.length} records)`);
|
|
5289
6267
|
}
|
|
5290
6268
|
return cleared;
|
|
5291
6269
|
}
|
|
5292
|
-
var clear2 = new
|
|
6270
|
+
var clear2 = new Command54("clear").description("clear seeded records").configureHelp(helpConfig).action(
|
|
5293
6271
|
() => runCommand(async () => {
|
|
5294
6272
|
const { workspaceRootDir } = await getWorkspace();
|
|
5295
6273
|
const seedFiles = getSeedFiles(workspaceRootDir);
|
|
@@ -5320,23 +6298,23 @@ var clear2 = new Command53("clear").description("clear seeded records").configur
|
|
|
5320
6298
|
);
|
|
5321
6299
|
|
|
5322
6300
|
// src/commands/seeds.ts
|
|
5323
|
-
var seeds = new
|
|
6301
|
+
var seeds = new Command55("seeds").description("manage seed data").configureHelp(helpConfig).addCommand(load2).addCommand(save).addCommand(clear2);
|
|
5324
6302
|
|
|
5325
6303
|
// src/commands/signup.ts
|
|
5326
|
-
import { Command as
|
|
5327
|
-
import * as
|
|
6304
|
+
import { Command as Command57 } from "commander";
|
|
6305
|
+
import * as p27 from "@clack/prompts";
|
|
5328
6306
|
import makeFetchCookie2 from "fetch-cookie";
|
|
5329
6307
|
|
|
5330
6308
|
// src/commands/login.ts
|
|
5331
|
-
import
|
|
5332
|
-
import { Command as
|
|
5333
|
-
import * as
|
|
6309
|
+
import os3 from "node:os";
|
|
6310
|
+
import { Command as Command56 } from "commander";
|
|
6311
|
+
import * as p26 from "@clack/prompts";
|
|
5334
6312
|
import makeFetchCookie from "fetch-cookie";
|
|
5335
|
-
var login = new
|
|
6313
|
+
var login = new Command56("login").description("login to velastack.dev").configureHelp(helpConfig).action(
|
|
5336
6314
|
() => runCommand(async () => {
|
|
5337
|
-
const { email: email3, password:
|
|
5338
|
-
email: () =>
|
|
5339
|
-
password: () =>
|
|
6315
|
+
const { email: email3, password: password10 } = await p26.group({
|
|
6316
|
+
email: () => p26.text({ message: "Email" }),
|
|
6317
|
+
password: () => p26.password({ message: "Password" })
|
|
5340
6318
|
});
|
|
5341
6319
|
const fetchCookie = makeFetchCookie(fetch);
|
|
5342
6320
|
const loginRes = await fetchCookie(`${API_URL}/login`, {
|
|
@@ -5348,7 +6326,7 @@ var login = new Command55("login").description("login to velastack.dev").configu
|
|
|
5348
6326
|
body: new URLSearchParams({
|
|
5349
6327
|
type: "password",
|
|
5350
6328
|
email: email3,
|
|
5351
|
-
password:
|
|
6329
|
+
password: password10
|
|
5352
6330
|
}).toString()
|
|
5353
6331
|
});
|
|
5354
6332
|
if (!loginRes.headers.get("Set-Cookie")) {
|
|
@@ -5358,11 +6336,11 @@ var login = new Command55("login").description("login to velastack.dev").configu
|
|
|
5358
6336
|
}
|
|
5359
6337
|
const apiKey = await issueApiKey(fetchCookie);
|
|
5360
6338
|
writeConfig({ apiKey });
|
|
5361
|
-
|
|
6339
|
+
p26.log.success("Logged in to velastack.dev");
|
|
5362
6340
|
}, "Failed to login.")
|
|
5363
6341
|
);
|
|
5364
6342
|
async function issueApiKey(fetchCookie) {
|
|
5365
|
-
const label = `CLI - ${
|
|
6343
|
+
const label = `CLI - ${os3.hostname()}`;
|
|
5366
6344
|
await fetchCookie(`${API_URL}/api-keys/new`, {
|
|
5367
6345
|
method: "POST",
|
|
5368
6346
|
headers: {
|
|
@@ -5399,14 +6377,14 @@ function extractApiKey(cookie) {
|
|
|
5399
6377
|
}
|
|
5400
6378
|
|
|
5401
6379
|
// src/commands/signup.ts
|
|
5402
|
-
var signup = new
|
|
6380
|
+
var signup = new Command57("signup").description("signup to velastack.dev").configureHelp(helpConfig).action(
|
|
5403
6381
|
() => runCommand(async () => {
|
|
5404
|
-
const { email: email3, password:
|
|
5405
|
-
email: () =>
|
|
5406
|
-
password: () =>
|
|
5407
|
-
passwordConfirm: () =>
|
|
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" })
|
|
5408
6386
|
});
|
|
5409
|
-
if (
|
|
6387
|
+
if (password10 !== passwordConfirm) {
|
|
5410
6388
|
throw new Error("Passwords do not match.");
|
|
5411
6389
|
}
|
|
5412
6390
|
const fetchCookie = makeFetchCookie2(fetch);
|
|
@@ -5419,7 +6397,7 @@ var signup = new Command56("signup").description("signup to velastack.dev").conf
|
|
|
5419
6397
|
body: new URLSearchParams({
|
|
5420
6398
|
type: "password",
|
|
5421
6399
|
email: email3,
|
|
5422
|
-
password:
|
|
6400
|
+
password: password10,
|
|
5423
6401
|
passwordConfirm
|
|
5424
6402
|
}).toString()
|
|
5425
6403
|
});
|
|
@@ -5428,33 +6406,33 @@ var signup = new Command56("signup").description("signup to velastack.dev").conf
|
|
|
5428
6406
|
}
|
|
5429
6407
|
const apiKey = await issueApiKey(fetchCookie);
|
|
5430
6408
|
writeConfig({ apiKey });
|
|
5431
|
-
|
|
5432
|
-
|
|
6409
|
+
p27.log.success("Signed up to velastack.dev");
|
|
6410
|
+
p27.log.info("Check your email for a confirmation link.");
|
|
5433
6411
|
}, "Failed to signup.")
|
|
5434
6412
|
);
|
|
5435
6413
|
|
|
5436
6414
|
// src/commands/logout.ts
|
|
5437
|
-
import { Command as
|
|
5438
|
-
import * as
|
|
5439
|
-
var logout = new
|
|
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(
|
|
5440
6418
|
() => runCommand(() => {
|
|
5441
6419
|
if (!readConfig()) {
|
|
5442
|
-
|
|
6420
|
+
p28.log.info("Not logged in");
|
|
5443
6421
|
return;
|
|
5444
6422
|
}
|
|
5445
6423
|
clearConfig();
|
|
5446
|
-
|
|
6424
|
+
p28.log.success("Logged out of velastack.dev");
|
|
5447
6425
|
})
|
|
5448
6426
|
);
|
|
5449
6427
|
|
|
5450
6428
|
// src/commands/whoami.ts
|
|
5451
|
-
import { Command as
|
|
5452
|
-
import * as
|
|
5453
|
-
var whoami = new
|
|
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(
|
|
5454
6432
|
() => runCommand(async () => {
|
|
5455
6433
|
const apiKey = readConfig()?.apiKey;
|
|
5456
6434
|
if (!apiKey) {
|
|
5457
|
-
|
|
6435
|
+
p29.log.info("Not logged in. Run `vela login` to login.");
|
|
5458
6436
|
return;
|
|
5459
6437
|
}
|
|
5460
6438
|
const res = await fetch(`${API_URL}/api/collections/users/records`, {
|
|
@@ -5462,31 +6440,31 @@ var whoami = new Command58("whoami").description("show the current user").config
|
|
|
5462
6440
|
});
|
|
5463
6441
|
const data = await res.json();
|
|
5464
6442
|
if (!data.items.length) throw new Error("No user found. Run `vela login` to login.");
|
|
5465
|
-
|
|
6443
|
+
p29.log.success(`Logged in as ${data.items[0].email}`);
|
|
5466
6444
|
})
|
|
5467
6445
|
);
|
|
5468
6446
|
|
|
5469
6447
|
// src/commands/migrate.ts
|
|
5470
|
-
import { Command as
|
|
6448
|
+
import { Command as Command65 } from "commander";
|
|
5471
6449
|
|
|
5472
6450
|
// src/commands/migrate/up.ts
|
|
5473
|
-
import { Command as
|
|
6451
|
+
import { Command as Command60 } from "commander";
|
|
5474
6452
|
|
|
5475
6453
|
// src/lib/migrate.ts
|
|
5476
|
-
import
|
|
5477
|
-
import
|
|
6454
|
+
import path29 from "node:path";
|
|
6455
|
+
import process17 from "node:process";
|
|
5478
6456
|
import { x as x2 } from "tinyexec";
|
|
5479
6457
|
async function runPocketbaseMigrate(args) {
|
|
5480
|
-
const cwd =
|
|
6458
|
+
const cwd = process17.cwd();
|
|
5481
6459
|
const { getBinaryPath } = await import("pocketbase-server");
|
|
5482
6460
|
const binaryPath = getBinaryPath();
|
|
5483
6461
|
await x2(
|
|
5484
6462
|
binaryPath,
|
|
5485
6463
|
[
|
|
5486
6464
|
"--dir",
|
|
5487
|
-
|
|
6465
|
+
path29.join(cwd, DATA_DIR),
|
|
5488
6466
|
"--migrationsDir",
|
|
5489
|
-
|
|
6467
|
+
path29.join(cwd, MIGRATIONS_DIR),
|
|
5490
6468
|
"migrate",
|
|
5491
6469
|
...args
|
|
5492
6470
|
],
|
|
@@ -5509,10 +6487,10 @@ async function runMigrateUp() {
|
|
|
5509
6487
|
]
|
|
5510
6488
|
});
|
|
5511
6489
|
}
|
|
5512
|
-
var up = new
|
|
6490
|
+
var up = new Command60("up").description("apply all pending migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations."));
|
|
5513
6491
|
|
|
5514
6492
|
// src/commands/migrate/down.ts
|
|
5515
|
-
import { Command as
|
|
6493
|
+
import { Command as Command61, InvalidArgumentError as InvalidArgumentError4 } from "commander";
|
|
5516
6494
|
function parseSteps(value) {
|
|
5517
6495
|
const n = parseInt(value, 10);
|
|
5518
6496
|
if (!Number.isFinite(n) || n <= 0) {
|
|
@@ -5520,7 +6498,7 @@ function parseSteps(value) {
|
|
|
5520
6498
|
}
|
|
5521
6499
|
return n;
|
|
5522
6500
|
}
|
|
5523
|
-
var down = new
|
|
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(
|
|
5524
6502
|
(n) => runCommand(async () => {
|
|
5525
6503
|
await runPocketbaseMigrate(["down", String(n)]);
|
|
5526
6504
|
reportResult({
|
|
@@ -5534,20 +6512,20 @@ var down = new Command60("down").alias("rollback").description("revert the last
|
|
|
5534
6512
|
);
|
|
5535
6513
|
|
|
5536
6514
|
// src/commands/migrate/create.ts
|
|
5537
|
-
import
|
|
5538
|
-
import
|
|
5539
|
-
import
|
|
5540
|
-
import { Command as
|
|
5541
|
-
var create2 = new
|
|
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(
|
|
5542
6520
|
(name) => runCommand(async () => {
|
|
5543
|
-
const cwd =
|
|
6521
|
+
const cwd = process18.cwd();
|
|
5544
6522
|
const before = listMigrationFiles(cwd);
|
|
5545
6523
|
await runPocketbaseMigrate(["create", name]);
|
|
5546
6524
|
const after = listMigrationFiles(cwd);
|
|
5547
6525
|
const added = [...after].filter((f) => !before.has(f));
|
|
5548
6526
|
reportResult({
|
|
5549
6527
|
summary: `Created blank migration ${name}.`,
|
|
5550
|
-
filesCreated: added.map((f) =>
|
|
6528
|
+
filesCreated: added.map((f) => path30.join(MIGRATIONS_DIR, f)),
|
|
5551
6529
|
nextSteps: [
|
|
5552
6530
|
`Open the new file in ${MIGRATIONS_DIR}/ and fill in the up/down handlers.`,
|
|
5553
6531
|
"Run `vela migrate up` to apply the migration once the handlers are written."
|
|
@@ -5556,19 +6534,19 @@ var create2 = new Command61("create").alias("new").description("create a new bla
|
|
|
5556
6534
|
}, "Failed to create migration.")
|
|
5557
6535
|
);
|
|
5558
6536
|
function listMigrationFiles(cwd) {
|
|
5559
|
-
const dir =
|
|
5560
|
-
if (!
|
|
5561
|
-
return new Set(
|
|
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)));
|
|
5562
6540
|
}
|
|
5563
6541
|
|
|
5564
6542
|
// src/commands/migrate/collections.ts
|
|
5565
|
-
import
|
|
5566
|
-
import
|
|
5567
|
-
import
|
|
5568
|
-
import { Command as
|
|
5569
|
-
var collections = new
|
|
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(
|
|
5570
6548
|
() => runCommand(async () => {
|
|
5571
|
-
const cwd =
|
|
6549
|
+
const cwd = process19.cwd();
|
|
5572
6550
|
const before = listMigrationFiles2(cwd);
|
|
5573
6551
|
await runPocketbaseMigrate(["collections"]);
|
|
5574
6552
|
const after = listMigrationFiles2(cwd);
|
|
@@ -5581,7 +6559,7 @@ var collections = new Command62("collections").alias("snapshot").description("sn
|
|
|
5581
6559
|
}
|
|
5582
6560
|
reportResult({
|
|
5583
6561
|
summary: "Snapshotted local collections into a new migration.",
|
|
5584
|
-
filesCreated: added.map((f) =>
|
|
6562
|
+
filesCreated: added.map((f) => path31.join(MIGRATIONS_DIR, f)),
|
|
5585
6563
|
nextSteps: [
|
|
5586
6564
|
`Review the generated snapshot in ${MIGRATIONS_DIR}/.`,
|
|
5587
6565
|
"Commit the snapshot so teammates pick up the new schema.",
|
|
@@ -5591,14 +6569,14 @@ var collections = new Command62("collections").alias("snapshot").description("sn
|
|
|
5591
6569
|
}, "Failed to snapshot collections.")
|
|
5592
6570
|
);
|
|
5593
6571
|
function listMigrationFiles2(cwd) {
|
|
5594
|
-
const dir =
|
|
5595
|
-
if (!
|
|
5596
|
-
return new Set(
|
|
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)));
|
|
5597
6575
|
}
|
|
5598
6576
|
|
|
5599
6577
|
// src/commands/migrate/history-sync.ts
|
|
5600
|
-
import { Command as
|
|
5601
|
-
var historySync = new
|
|
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(
|
|
5602
6580
|
() => runCommand(async () => {
|
|
5603
6581
|
await runPocketbaseMigrate(["history-sync"]);
|
|
5604
6582
|
reportResult({
|
|
@@ -5609,15 +6587,15 @@ var historySync = new Command63("history-sync").description("drop _migrations ro
|
|
|
5609
6587
|
);
|
|
5610
6588
|
|
|
5611
6589
|
// src/commands/migrate.ts
|
|
5612
|
-
var migrate = new
|
|
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);
|
|
5613
6591
|
|
|
5614
6592
|
// src/commands/dev.ts
|
|
5615
|
-
import
|
|
5616
|
-
import
|
|
5617
|
-
import
|
|
6593
|
+
import fs30 from "node:fs";
|
|
6594
|
+
import path32 from "node:path";
|
|
6595
|
+
import process20 from "node:process";
|
|
5618
6596
|
import { performance } from "node:perf_hooks";
|
|
5619
|
-
import { Command as
|
|
5620
|
-
import
|
|
6597
|
+
import { Command as Command66, InvalidArgumentError as InvalidArgumentError5 } from "commander";
|
|
6598
|
+
import pc7 from "picocolors";
|
|
5621
6599
|
import PocketBase2 from "pocketbase";
|
|
5622
6600
|
function parsePort(value) {
|
|
5623
6601
|
const port = Number(value);
|
|
@@ -5626,38 +6604,38 @@ function parsePort(value) {
|
|
|
5626
6604
|
}
|
|
5627
6605
|
return port;
|
|
5628
6606
|
}
|
|
5629
|
-
var dev = new
|
|
5630
|
-
const 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();
|
|
5631
6609
|
const startTime = performance.now();
|
|
5632
6610
|
const { createServer, version } = await import("vite");
|
|
5633
|
-
const viteMetadataDir =
|
|
5634
|
-
const viteMetadataFile =
|
|
6611
|
+
const viteMetadataDir = path32.join(cwd, "node_modules", ".vite");
|
|
6612
|
+
const viteMetadataFile = path32.join(viteMetadataDir, "_pocketbase_metadata.json");
|
|
5635
6613
|
let pbProc;
|
|
5636
6614
|
const backend3 = hasBackend(cwd);
|
|
5637
|
-
const needsStart = backend3 && !
|
|
6615
|
+
const needsStart = backend3 && !process20.env.POCKETBASE_URL;
|
|
5638
6616
|
const cleanup = () => {
|
|
5639
6617
|
if (pbProc?.pid) pbProc.kill();
|
|
5640
|
-
if (
|
|
6618
|
+
if (fs30.existsSync(viteMetadataFile)) fs30.rmSync(viteMetadataFile);
|
|
5641
6619
|
};
|
|
5642
6620
|
if (needsStart) {
|
|
5643
|
-
const dataDir =
|
|
6621
|
+
const dataDir = path32.join(cwd, DATA_DIR);
|
|
5644
6622
|
const started = await startPocketbaseServe({
|
|
5645
6623
|
dataDir,
|
|
5646
6624
|
migrationsDir: MIGRATIONS_DIR,
|
|
5647
|
-
hooksDir:
|
|
6625
|
+
hooksDir: path32.join(dataDir, "hooks"),
|
|
5648
6626
|
dev: true,
|
|
5649
6627
|
stdio: "pipe"
|
|
5650
6628
|
});
|
|
5651
6629
|
pbProc = started.proc;
|
|
5652
|
-
|
|
5653
|
-
pbProc.stdout?.pipe(
|
|
5654
|
-
pbProc.stderr?.pipe(
|
|
6630
|
+
process20.env.POCKETBASE_URL = started.url;
|
|
6631
|
+
pbProc.stdout?.pipe(process20.stdout);
|
|
6632
|
+
pbProc.stderr?.pipe(process20.stderr);
|
|
5655
6633
|
pbProc.on("error", (err) => console.error("PocketBase error:", err));
|
|
5656
6634
|
pbProc.on("exit", (code) => console.log(`PocketBase exited with code ${code}`));
|
|
5657
|
-
|
|
5658
|
-
|
|
6635
|
+
process20.on("exit", cleanup);
|
|
6636
|
+
process20.on("SIGINT", () => {
|
|
5659
6637
|
cleanup();
|
|
5660
|
-
|
|
6638
|
+
process20.exit(0);
|
|
5661
6639
|
});
|
|
5662
6640
|
}
|
|
5663
6641
|
const serverOptions = {};
|
|
@@ -5675,31 +6653,31 @@ var dev = new Command65("dev").description("start the development server").optio
|
|
|
5675
6653
|
if (!backend3) return;
|
|
5676
6654
|
const { address, port: vitePort } = server.httpServer.address();
|
|
5677
6655
|
const viteHost = address === "::1" ? "localhost" : address;
|
|
5678
|
-
await
|
|
5679
|
-
await
|
|
6656
|
+
await fs30.promises.mkdir(viteMetadataDir, { recursive: true });
|
|
6657
|
+
await fs30.promises.writeFile(
|
|
5680
6658
|
viteMetadataFile,
|
|
5681
6659
|
JSON.stringify({
|
|
5682
|
-
pocketbaseUrl:
|
|
6660
|
+
pocketbaseUrl: process20.env.POCKETBASE_URL,
|
|
5683
6661
|
vitePort,
|
|
5684
6662
|
viteHost
|
|
5685
6663
|
})
|
|
5686
6664
|
);
|
|
5687
|
-
const pb = new PocketBase2(
|
|
6665
|
+
const pb = new PocketBase2(process20.env.POCKETBASE_URL);
|
|
5688
6666
|
await pb.collection("_superusers").authWithPassword(
|
|
5689
|
-
|
|
5690
|
-
|
|
6667
|
+
process20.env.POCKETBASE_SUPERUSER_EMAIL,
|
|
6668
|
+
process20.env.POCKETBASE_SUPERUSER_PASSWORD
|
|
5691
6669
|
);
|
|
5692
6670
|
await pb.settings.update({ meta: { appURL: `http://${viteHost}:${vitePort}` } });
|
|
5693
6671
|
await startWatchingTypes(cwd, pb);
|
|
5694
6672
|
});
|
|
5695
6673
|
await server.listen();
|
|
5696
|
-
const hasExistingLogs =
|
|
5697
|
-
const startupDurationString =
|
|
5698
|
-
`ready in ${
|
|
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`
|
|
5699
6677
|
);
|
|
5700
6678
|
server.config.logger.info(
|
|
5701
6679
|
`
|
|
5702
|
-
${
|
|
6680
|
+
${pc7.green(`${pc7.bold("VITE")} v${version}`)} ${startupDurationString}
|
|
5703
6681
|
`,
|
|
5704
6682
|
{ clear: !hasExistingLogs }
|
|
5705
6683
|
);
|
|
@@ -5708,18 +6686,18 @@ var dev = new Command65("dev").description("start the development server").optio
|
|
|
5708
6686
|
});
|
|
5709
6687
|
async function startWatchingTypes(cwd, pb) {
|
|
5710
6688
|
const { processTypes } = await import("@velastack/pocketbase-codegen");
|
|
5711
|
-
const typesDir =
|
|
5712
|
-
const pocketbaseDir =
|
|
5713
|
-
const pocketbaseTypes =
|
|
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");
|
|
5714
6692
|
const regenerate = () => processTypes(pb, typesDir).catch(() => {
|
|
5715
6693
|
});
|
|
5716
6694
|
await regenerate();
|
|
5717
6695
|
void (async () => {
|
|
5718
6696
|
for (; ; ) {
|
|
5719
6697
|
try {
|
|
5720
|
-
await
|
|
5721
|
-
for await (const event of
|
|
5722
|
-
if (event.eventType === "rename" && event.filename === "$types.d.ts" && !
|
|
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)) {
|
|
5723
6701
|
setTimeout(regenerate, 100);
|
|
5724
6702
|
}
|
|
5725
6703
|
}
|
|
@@ -5731,117 +6709,540 @@ async function startWatchingTypes(cwd, pb) {
|
|
|
5731
6709
|
}
|
|
5732
6710
|
|
|
5733
6711
|
// src/commands/build.ts
|
|
5734
|
-
import
|
|
5735
|
-
import
|
|
5736
|
-
import { Command as
|
|
6712
|
+
import path33 from "node:path";
|
|
6713
|
+
import process21 from "node:process";
|
|
6714
|
+
import { Command as Command67 } from "commander";
|
|
5737
6715
|
import { x as x3 } from "tinyexec";
|
|
5738
6716
|
import { detect as detect5 } from "package-manager-detector";
|
|
5739
6717
|
import { resolveCommand as resolveCommand5 } from "package-manager-detector/commands";
|
|
5740
|
-
var build = new
|
|
5741
|
-
|
|
5742
|
-
const 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();
|
|
5743
6766
|
let pbProc;
|
|
5744
|
-
const needsStart = hasBackend(cwd) && !
|
|
6767
|
+
const needsStart = hasBackend(cwd) && !process22.env.POCKETBASE_URL;
|
|
5745
6768
|
const cleanup = () => {
|
|
5746
6769
|
if (pbProc?.pid) pbProc.kill();
|
|
5747
6770
|
};
|
|
5748
6771
|
if (needsStart) {
|
|
5749
|
-
const dataDir =
|
|
6772
|
+
const dataDir = path34.join(cwd, DATA_DIR);
|
|
5750
6773
|
const started = await startPocketbaseServe({
|
|
5751
6774
|
dataDir,
|
|
5752
6775
|
migrationsDir: MIGRATIONS_DIR,
|
|
5753
|
-
hooksDir:
|
|
6776
|
+
hooksDir: path34.join(dataDir, "hooks"),
|
|
5754
6777
|
dev: true
|
|
5755
6778
|
});
|
|
5756
6779
|
pbProc = started.proc;
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
6780
|
+
process22.env.POCKETBASE_URL = started.url;
|
|
6781
|
+
process22.on("exit", cleanup);
|
|
6782
|
+
process22.on("SIGINT", () => {
|
|
5760
6783
|
cleanup();
|
|
5761
|
-
|
|
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
|
+
);
|
|
5762
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
|
+
);
|
|
5763
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;
|
|
5764
7158
|
try {
|
|
5765
|
-
const
|
|
5766
|
-
|
|
5767
|
-
const
|
|
5768
|
-
if (
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
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
|
+
);
|
|
5775
7175
|
}
|
|
5776
|
-
}
|
|
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.
|
|
5777
7182
|
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
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)
|
|
5791
7206
|
};
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
}
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
process19.exit(0);
|
|
5806
|
-
});
|
|
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);
|
|
5807
7220
|
}
|
|
7221
|
+
}
|
|
7222
|
+
function isDirectory(target) {
|
|
5808
7223
|
try {
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
if (pm === "npm") args.unshift("--yes");
|
|
5813
|
-
await x4(resolved.command, args, {
|
|
5814
|
-
nodeOptions: { cwd, stdio: "inherit" },
|
|
5815
|
-
throwOnError: true
|
|
5816
|
-
});
|
|
5817
|
-
} finally {
|
|
5818
|
-
cleanup();
|
|
7224
|
+
return fs33.statSync(target).isDirectory();
|
|
7225
|
+
} catch {
|
|
7226
|
+
return false;
|
|
5819
7227
|
}
|
|
5820
|
-
}
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
const { workspaceRootDir } = await getWorkspace();
|
|
5828
|
-
const typesDir = path32.join(workspaceRootDir, ".svelte-kit", "types");
|
|
5829
|
-
const { processTypes } = await import("@velastack/pocketbase-codegen");
|
|
5830
|
-
await withPocketbase(workspaceRootDir, async (pb) => {
|
|
5831
|
-
await processTypes(pb, typesDir);
|
|
5832
|
-
});
|
|
5833
|
-
console.log("types synced");
|
|
5834
|
-
}, "Failed to sync types.")
|
|
5835
|
-
);
|
|
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.
|
|
5836
7235
|
|
|
5837
|
-
|
|
5838
|
-
|
|
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
|
+
}
|
|
5839
7240
|
|
|
5840
|
-
// src/commands/
|
|
5841
|
-
import
|
|
5842
|
-
import
|
|
5843
|
-
import { Command as
|
|
5844
|
-
import * as
|
|
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";
|
|
5845
7246
|
|
|
5846
7247
|
// src/lib/velastack-api.ts
|
|
5847
7248
|
async function apiFetch(apiKey, pathAndQuery, init) {
|
|
@@ -5890,14 +7291,14 @@ async function createProject2(apiKey, args) {
|
|
|
5890
7291
|
});
|
|
5891
7292
|
}
|
|
5892
7293
|
|
|
5893
|
-
// src/commands/
|
|
7294
|
+
// src/commands/link.ts
|
|
5894
7295
|
var CREATE_NEW = "__new__";
|
|
5895
|
-
var
|
|
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."));
|
|
5896
7297
|
async function linkProject() {
|
|
5897
7298
|
const { workspaceRootDir } = await getWorkspace();
|
|
5898
7299
|
const existing = readProjectConfig(workspaceRootDir);
|
|
5899
7300
|
if (existing) {
|
|
5900
|
-
|
|
7301
|
+
p32.log.success(`Linked to ${existing.projectName}.`);
|
|
5901
7302
|
return;
|
|
5902
7303
|
}
|
|
5903
7304
|
const apiKey = requireApiKey();
|
|
@@ -5924,10 +7325,10 @@ async function linkProject() {
|
|
|
5924
7325
|
projectName = created.name;
|
|
5925
7326
|
}
|
|
5926
7327
|
writeProjectConfig(workspaceRootDir, { projectId, teamId, projectName });
|
|
5927
|
-
|
|
7328
|
+
p32.log.success(`Linked to ${projectName}.`);
|
|
5928
7329
|
}
|
|
5929
7330
|
async function pickExistingProject(projects) {
|
|
5930
|
-
const choice = await
|
|
7331
|
+
const choice = await p32.select({
|
|
5931
7332
|
message: "Select a project",
|
|
5932
7333
|
options: [
|
|
5933
7334
|
...projects.map((pr) => ({
|
|
@@ -5937,79 +7338,655 @@ async function pickExistingProject(projects) {
|
|
|
5937
7338
|
{ value: CREATE_NEW, label: "Create a new project" }
|
|
5938
7339
|
]
|
|
5939
7340
|
});
|
|
5940
|
-
if (
|
|
5941
|
-
|
|
5942
|
-
|
|
7341
|
+
if (p32.isCancel(choice)) {
|
|
7342
|
+
p32.cancel("Operation cancelled.");
|
|
7343
|
+
process24.exit(0);
|
|
5943
7344
|
}
|
|
5944
7345
|
return choice;
|
|
5945
7346
|
}
|
|
5946
7347
|
async function pickTeam(teams3) {
|
|
5947
7348
|
if (teams3.length === 1) return teams3[0];
|
|
5948
|
-
const choice = await
|
|
7349
|
+
const choice = await p32.select({
|
|
5949
7350
|
message: "Select a team",
|
|
5950
7351
|
options: teams3.map((team) => ({
|
|
5951
7352
|
value: team.id,
|
|
5952
7353
|
label: team.is_personal ? `${team.name} (personal)` : team.name
|
|
5953
7354
|
}))
|
|
5954
7355
|
});
|
|
5955
|
-
if (
|
|
5956
|
-
|
|
5957
|
-
|
|
7356
|
+
if (p32.isCancel(choice)) {
|
|
7357
|
+
p32.cancel("Operation cancelled.");
|
|
7358
|
+
process24.exit(0);
|
|
5958
7359
|
}
|
|
5959
7360
|
return teams3.find((team) => team.id === choice);
|
|
5960
7361
|
}
|
|
5961
7362
|
async function promptProjectName(workspaceRootDir) {
|
|
5962
|
-
const defaultValue =
|
|
5963
|
-
const value = await
|
|
7363
|
+
const defaultValue = defaultProjectName2(workspaceRootDir);
|
|
7364
|
+
const value = await p32.text({
|
|
5964
7365
|
message: "Project name",
|
|
5965
7366
|
defaultValue,
|
|
5966
7367
|
initialValue: defaultValue,
|
|
5967
7368
|
placeholder: defaultValue,
|
|
5968
|
-
validate: (
|
|
7369
|
+
validate: (v9) => !v9?.trim() ? "Required" : void 0
|
|
5969
7370
|
});
|
|
5970
|
-
if (
|
|
5971
|
-
|
|
5972
|
-
|
|
7371
|
+
if (p32.isCancel(value)) {
|
|
7372
|
+
p32.cancel("Operation cancelled.");
|
|
7373
|
+
process24.exit(0);
|
|
5973
7374
|
}
|
|
5974
7375
|
return value.trim();
|
|
5975
7376
|
}
|
|
5976
|
-
function
|
|
7377
|
+
function defaultProjectName2(workspaceRootDir) {
|
|
5977
7378
|
try {
|
|
5978
|
-
const pkg = readPackageJson(
|
|
7379
|
+
const pkg = readPackageJson(path39.join(workspaceRootDir, "package.json"));
|
|
5979
7380
|
const name = pkg.name;
|
|
5980
7381
|
if (typeof name === "string" && name.trim()) return name.trim();
|
|
5981
7382
|
} catch {
|
|
5982
7383
|
}
|
|
5983
|
-
return
|
|
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
|
+
}
|
|
5984
7961
|
}
|
|
5985
7962
|
|
|
5986
7963
|
// src/commands/test.ts
|
|
5987
|
-
import
|
|
5988
|
-
import
|
|
5989
|
-
import { Command as
|
|
5990
|
-
import
|
|
5991
|
-
import
|
|
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";
|
|
5992
7969
|
import { x as x5 } from "tinyexec";
|
|
5993
|
-
import { detect as
|
|
5994
|
-
import { resolveCommand as
|
|
5995
|
-
import
|
|
5996
|
-
var testServer = new
|
|
5997
|
-
const 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();
|
|
5998
7975
|
const email3 = `test-${Math.random().toString(36).slice(2)}@example.com`;
|
|
5999
|
-
const
|
|
6000
|
-
const testDataDir =
|
|
6001
|
-
|
|
7976
|
+
const password10 = "password";
|
|
7977
|
+
const testDataDir = path41.join(cwd, "test-data");
|
|
7978
|
+
fs36.rmSync(testDataDir, { recursive: true, force: true });
|
|
6002
7979
|
const { stop, url } = await launchPocketbase(cwd, {
|
|
6003
7980
|
dir: testDataDir,
|
|
6004
|
-
migrationsDir:
|
|
7981
|
+
migrationsDir: path41.join(cwd, "migrations"),
|
|
6005
7982
|
email: email3,
|
|
6006
|
-
password:
|
|
7983
|
+
password: password10
|
|
6007
7984
|
});
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
console.log(`${
|
|
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`);
|
|
6013
7990
|
const { createServer } = await import("vite");
|
|
6014
7991
|
const vite = await createServer({
|
|
6015
7992
|
mode: "test",
|
|
@@ -6018,29 +7995,29 @@ var testServer = new Command70("test:server").description("run server tests").al
|
|
|
6018
7995
|
});
|
|
6019
7996
|
const vitePort = await findFreePort();
|
|
6020
7997
|
await vite.listen(vitePort);
|
|
6021
|
-
|
|
6022
|
-
console.log(`${
|
|
6023
|
-
console.log(`${
|
|
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}`);
|
|
6024
8001
|
const cleanup = async () => {
|
|
6025
8002
|
stop();
|
|
6026
8003
|
await vite.close();
|
|
6027
|
-
|
|
8004
|
+
fs36.rmSync(testDataDir, { recursive: true, force: true });
|
|
6028
8005
|
};
|
|
6029
|
-
const pb = new
|
|
8006
|
+
const pb = new PocketBase5(url);
|
|
6030
8007
|
try {
|
|
6031
|
-
await authWithRetries(pb, email3,
|
|
8008
|
+
await authWithRetries(pb, email3, password10);
|
|
6032
8009
|
} catch (e) {
|
|
6033
8010
|
await cleanup();
|
|
6034
|
-
console.error(`${
|
|
6035
|
-
|
|
8011
|
+
console.error(`${pc19.redBright("\u2717")} Auth failed: ${e.message}`);
|
|
8012
|
+
process28.exit(1);
|
|
6036
8013
|
}
|
|
6037
8014
|
const extraArgs = (cmd.parent?.args ?? []).slice(1);
|
|
6038
8015
|
let filter = extraArgs.find((arg) => !arg.startsWith("-"));
|
|
6039
8016
|
const passthrough = filter ? extraArgs.filter((a) => a !== filter) : extraArgs;
|
|
6040
8017
|
if (!filter) filter = "server";
|
|
6041
8018
|
try {
|
|
6042
|
-
const pm = (await
|
|
6043
|
-
const resolved =
|
|
8019
|
+
const pm = (await detect8({ cwd }))?.name ?? "npm";
|
|
8020
|
+
const resolved = resolveCommand8(pm, "execute", [
|
|
6044
8021
|
"vitest",
|
|
6045
8022
|
"run",
|
|
6046
8023
|
filter,
|
|
@@ -6050,7 +8027,7 @@ var testServer = new Command70("test:server").description("run server tests").al
|
|
|
6050
8027
|
const resolvedArgs = resolved.args.slice();
|
|
6051
8028
|
if (pm === "npm") resolvedArgs.unshift("--yes");
|
|
6052
8029
|
await x5(resolved.command, resolvedArgs, {
|
|
6053
|
-
nodeOptions: { cwd, stdio: "inherit", env: { ...
|
|
8030
|
+
nodeOptions: { cwd, stdio: "inherit", env: { ...process28.env, CI: "1" } },
|
|
6054
8031
|
throwOnError: true
|
|
6055
8032
|
});
|
|
6056
8033
|
} catch {
|
|
@@ -6076,9 +8053,9 @@ function stubPagesPlugin() {
|
|
|
6076
8053
|
}
|
|
6077
8054
|
|
|
6078
8055
|
// src/commands/routes.ts
|
|
6079
|
-
import
|
|
6080
|
-
import
|
|
6081
|
-
import { Command as
|
|
8056
|
+
import fs37 from "node:fs";
|
|
8057
|
+
import path42 from "node:path";
|
|
8058
|
+
import { Command as Command85 } from "commander";
|
|
6082
8059
|
var HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
6083
8060
|
"GET",
|
|
6084
8061
|
"POST",
|
|
@@ -6089,32 +8066,32 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
|
6089
8066
|
"HEAD",
|
|
6090
8067
|
"fallback"
|
|
6091
8068
|
]);
|
|
6092
|
-
var routes = new
|
|
8069
|
+
var routes = new Command85("routes").description("list routes").configureHelp(helpConfig).action(async () => {
|
|
6093
8070
|
const { workspaceRootDir, routesDir } = await getWorkspace();
|
|
6094
|
-
const routesRoot =
|
|
8071
|
+
const routesRoot = path42.join(workspaceRootDir, routesDir);
|
|
6095
8072
|
const found = walk(routesRoot, routesRoot).filter((r) => r.methods.length > 0);
|
|
6096
8073
|
found.sort((a, b) => a.urlPattern.localeCompare(b.urlPattern));
|
|
6097
8074
|
printTable(found);
|
|
6098
8075
|
});
|
|
6099
8076
|
function walk(root, dir) {
|
|
6100
|
-
const entries =
|
|
8077
|
+
const entries = fs37.readdirSync(dir, { withFileTypes: true });
|
|
6101
8078
|
const routes2 = [];
|
|
6102
8079
|
const hasLeaf = entries.some((e) => e.isFile() && isRouteFile(e.name));
|
|
6103
8080
|
if (hasLeaf) {
|
|
6104
|
-
const id = "/" +
|
|
8081
|
+
const id = "/" + path42.relative(root, dir).split(path42.sep).filter(Boolean).join("/");
|
|
6105
8082
|
const urlPattern = id.replace(/\([^)]+\)\/?/g, "").replace(/\/$/, "") || "/";
|
|
6106
8083
|
const methods = /* @__PURE__ */ new Set();
|
|
6107
8084
|
for (const entry of entries) {
|
|
6108
8085
|
if (!entry.isFile()) continue;
|
|
6109
8086
|
if (entry.name.endsWith("+page.svelte")) methods.add("GET");
|
|
6110
8087
|
if (entry.name.endsWith("+server.ts") || entry.name.endsWith("+server.js") || entry.name.endsWith("+page.server.ts") || entry.name.endsWith("+page.server.js")) {
|
|
6111
|
-
extractMethods(
|
|
8088
|
+
extractMethods(path42.join(dir, entry.name)).forEach((m) => methods.add(m));
|
|
6112
8089
|
}
|
|
6113
8090
|
}
|
|
6114
8091
|
routes2.push({ id: id || "/", urlPattern, methods: [...methods] });
|
|
6115
8092
|
}
|
|
6116
8093
|
for (const entry of entries) {
|
|
6117
|
-
if (entry.isDirectory()) routes2.push(...walk(root,
|
|
8094
|
+
if (entry.isDirectory()) routes2.push(...walk(root, path42.join(dir, entry.name)));
|
|
6118
8095
|
}
|
|
6119
8096
|
return routes2;
|
|
6120
8097
|
}
|
|
@@ -6123,7 +8100,7 @@ function isRouteFile(name) {
|
|
|
6123
8100
|
}
|
|
6124
8101
|
function extractMethods(file) {
|
|
6125
8102
|
try {
|
|
6126
|
-
const content =
|
|
8103
|
+
const content = fs37.readFileSync(file, "utf8");
|
|
6127
8104
|
const methods = [];
|
|
6128
8105
|
const exportRegex = /export\s+(?:const|async\s+function|function)\s+(\w+)/g;
|
|
6129
8106
|
let match;
|
|
@@ -6164,15 +8141,15 @@ function printTable(routes2) {
|
|
|
6164
8141
|
}
|
|
6165
8142
|
|
|
6166
8143
|
// src/commands/i18n.ts
|
|
6167
|
-
import
|
|
6168
|
-
import { Command as
|
|
8144
|
+
import process29 from "node:process";
|
|
8145
|
+
import { Command as Command86 } from "commander";
|
|
6169
8146
|
import { x as x6 } from "tinyexec";
|
|
6170
|
-
import { detect as
|
|
6171
|
-
import { resolveCommand as
|
|
8147
|
+
import { detect as detect9 } from "package-manager-detector";
|
|
8148
|
+
import { resolveCommand as resolveCommand9 } from "package-manager-detector/commands";
|
|
6172
8149
|
async function runWuchale(extraArgs) {
|
|
6173
|
-
const cwd =
|
|
6174
|
-
const pm = (await
|
|
6175
|
-
const resolved =
|
|
8150
|
+
const cwd = process29.cwd();
|
|
8151
|
+
const pm = (await detect9({ cwd }))?.name ?? "npm";
|
|
8152
|
+
const resolved = resolveCommand9(pm, "execute", ["wuchale", ...extraArgs]);
|
|
6176
8153
|
const args = resolved.args.slice();
|
|
6177
8154
|
if (pm === "npm") args.unshift("--yes");
|
|
6178
8155
|
await x6(resolved.command, args, {
|
|
@@ -6180,11 +8157,11 @@ async function runWuchale(extraArgs) {
|
|
|
6180
8157
|
throwOnError: true
|
|
6181
8158
|
});
|
|
6182
8159
|
}
|
|
6183
|
-
var extract = new
|
|
6184
|
-
var watch = new
|
|
6185
|
-
var
|
|
6186
|
-
var clean = new
|
|
6187
|
-
var i18n3 = new
|
|
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);
|
|
6188
8165
|
|
|
6189
8166
|
// src/commands/oauth.ts
|
|
6190
8167
|
var oauth = stubCommand("oauth", "configure OAuth providers");
|
|
@@ -6205,42 +8182,52 @@ var NO_BACKEND_COMMMANDS = /* @__PURE__ */ new Set([
|
|
|
6205
8182
|
"routes",
|
|
6206
8183
|
"i18n",
|
|
6207
8184
|
"generate schema",
|
|
6208
|
-
"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"
|
|
6209
8195
|
]);
|
|
6210
8196
|
var BACKEND_OPTIONAL_COMMANDS = /* @__PURE__ */ new Set(["dev", "build", "preview", "deploy"]);
|
|
6211
|
-
var program = new
|
|
8197
|
+
var program = new Command87().name(package_default.name).description(package_default.description).version(package_default.version, "-v, --version").configureHelp(helpConfig);
|
|
6212
8198
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
6213
8199
|
if (isStub(actionCommand)) return;
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
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];
|
|
6218
8205
|
if (NO_BACKEND_COMMMANDS.has(top)) return;
|
|
6219
8206
|
if (!hasBackend()) {
|
|
6220
8207
|
if (BACKEND_OPTIONAL_COMMANDS.has(top)) return;
|
|
6221
|
-
|
|
6222
|
-
`${
|
|
8208
|
+
p42.log.error(
|
|
8209
|
+
`${pc20.cyan(`vela ${path43}`)} needs a backend, and this project does not have one.
|
|
6223
8210
|
|
|
6224
8211
|
Static projects have no database to talk to.
|
|
6225
8212
|
|
|
6226
|
-
To add a backend to this project, run ${
|
|
8213
|
+
To add a backend to this project, run ${pc20.cyan("vela bless")}.`
|
|
6227
8214
|
);
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
8215
|
+
p42.log.message();
|
|
8216
|
+
p42.cancel("Operation failed.");
|
|
8217
|
+
process30.exit(1);
|
|
6231
8218
|
}
|
|
6232
|
-
if (!
|
|
6233
|
-
|
|
8219
|
+
if (!process30.env.POCKETBASE_SUPERUSER_EMAIL || !process30.env.POCKETBASE_SUPERUSER_PASSWORD) {
|
|
8220
|
+
p42.log.error(
|
|
6234
8221
|
`PocketBase superuser credentials are required.
|
|
6235
8222
|
|
|
6236
|
-
Set ${
|
|
8223
|
+
Set ${pc20.cyan("POCKETBASE_SUPERUSER_EMAIL")} and ${pc20.cyan("POCKETBASE_SUPERUSER_PASSWORD")} in your .env file.
|
|
6237
8224
|
|
|
6238
|
-
To set up a new project, run ${
|
|
6239
|
-
To set up an existing project, run ${
|
|
8225
|
+
To set up a new project, run ${pc20.cyan("vela create")}.
|
|
8226
|
+
To set up an existing project, run ${pc20.cyan("vela bless")}.`
|
|
6240
8227
|
);
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
8228
|
+
p42.log.message();
|
|
8229
|
+
p42.cancel("Operation failed.");
|
|
8230
|
+
process30.exit(1);
|
|
6244
8231
|
}
|
|
6245
8232
|
});
|
|
6246
8233
|
function getCommandPath(cmd) {
|
|
@@ -6274,6 +8261,13 @@ for (const command of [
|
|
|
6274
8261
|
sync,
|
|
6275
8262
|
provision,
|
|
6276
8263
|
deploy,
|
|
8264
|
+
rollback,
|
|
8265
|
+
status,
|
|
8266
|
+
logs,
|
|
8267
|
+
env,
|
|
8268
|
+
admin,
|
|
8269
|
+
targets,
|
|
8270
|
+
link,
|
|
6277
8271
|
testServer,
|
|
6278
8272
|
routes,
|
|
6279
8273
|
i18n3,
|
|
@@ -6284,14 +8278,14 @@ for (const command of [
|
|
|
6284
8278
|
}
|
|
6285
8279
|
|
|
6286
8280
|
// src/bin.ts
|
|
6287
|
-
var argv = normalizeArgv(
|
|
8281
|
+
var argv = normalizeArgv(process31.argv.slice(2));
|
|
6288
8282
|
var delegatedExitCode = delegateToLocalCli({
|
|
6289
8283
|
argv,
|
|
6290
8284
|
selfPath: fileURLToPath2(import.meta.url),
|
|
6291
8285
|
selfVersion: package_default.version
|
|
6292
8286
|
});
|
|
6293
8287
|
if (delegatedExitCode !== null) {
|
|
6294
|
-
|
|
8288
|
+
process31.exit(delegatedExitCode);
|
|
6295
8289
|
}
|
|
6296
8290
|
program.parse(argv, { from: "user" });
|
|
6297
8291
|
//# sourceMappingURL=bin.js.map
|