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