@freecodecamp/universe-cli 0.4.0 → 0.5.1
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 +69 -67
- package/dist/index.cjs +468 -48
- package/dist/index.js +476 -50
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -16,10 +16,7 @@ var EXIT_STORAGE = 13;
|
|
|
16
16
|
var EXIT_GIT = 15;
|
|
17
17
|
var EXIT_CONFIRM = 18;
|
|
18
18
|
var EXIT_PARTIAL = 19;
|
|
19
|
-
function exitWithCode(code
|
|
20
|
-
if (message !== void 0) {
|
|
21
|
-
process.stderr.write(message + "\n");
|
|
22
|
-
}
|
|
19
|
+
function exitWithCode(code) {
|
|
23
20
|
process.exit(code);
|
|
24
21
|
}
|
|
25
22
|
|
|
@@ -384,6 +381,53 @@ See docs/platform-yaml.md.`
|
|
|
384
381
|
return { ok: true, value: result.data };
|
|
385
382
|
}
|
|
386
383
|
|
|
384
|
+
// src/lib/similarity.ts
|
|
385
|
+
function editDistance(a, b) {
|
|
386
|
+
const m = a.length;
|
|
387
|
+
const n = b.length;
|
|
388
|
+
if (m === 0) return n;
|
|
389
|
+
if (n === 0) return m;
|
|
390
|
+
const d = Array.from(
|
|
391
|
+
{ length: m + 1 },
|
|
392
|
+
() => new Array(n + 1).fill(0)
|
|
393
|
+
);
|
|
394
|
+
for (let i = 0; i <= m; i++) d[i][0] = i;
|
|
395
|
+
for (let j = 0; j <= n; j++) d[0][j] = j;
|
|
396
|
+
for (let i = 1; i <= m; i++) {
|
|
397
|
+
for (let j = 1; j <= n; j++) {
|
|
398
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
399
|
+
d[i][j] = Math.min(
|
|
400
|
+
d[i - 1][j] + 1,
|
|
401
|
+
d[i][j - 1] + 1,
|
|
402
|
+
d[i - 1][j - 1] + cost
|
|
403
|
+
);
|
|
404
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
405
|
+
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return d[m][n];
|
|
410
|
+
}
|
|
411
|
+
function suggest(target, candidates, threshold = 2) {
|
|
412
|
+
if (candidates.length === 0) return null;
|
|
413
|
+
const lc = target.toLowerCase();
|
|
414
|
+
const sub = candidates.find((c) => {
|
|
415
|
+
const clc = c.toLowerCase();
|
|
416
|
+
return clc.includes(lc) || lc.includes(clc);
|
|
417
|
+
});
|
|
418
|
+
if (sub) return sub;
|
|
419
|
+
let best = null;
|
|
420
|
+
let bestD = threshold + 1;
|
|
421
|
+
for (const c of candidates) {
|
|
422
|
+
const d = editDistance(lc, c.toLowerCase());
|
|
423
|
+
if (d < bestD) {
|
|
424
|
+
bestD = d;
|
|
425
|
+
best = c;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return bestD <= threshold ? best : null;
|
|
429
|
+
}
|
|
430
|
+
|
|
387
431
|
// src/lib/proxy-client.ts
|
|
388
432
|
var ProxyError = class extends CliError {
|
|
389
433
|
exitCode;
|
|
@@ -546,6 +590,52 @@ function createProxyClient(cfg) {
|
|
|
546
590
|
},
|
|
547
591
|
body: JSON.stringify({ to: req.to })
|
|
548
592
|
});
|
|
593
|
+
},
|
|
594
|
+
async registerSite(req) {
|
|
595
|
+
const body = { slug: req.slug };
|
|
596
|
+
if (req.teams && req.teams.length > 0) {
|
|
597
|
+
body.teams = req.teams;
|
|
598
|
+
}
|
|
599
|
+
return call(`${base}/api/site/register`, {
|
|
600
|
+
method: "POST",
|
|
601
|
+
headers: {
|
|
602
|
+
Authorization: await userBearer(),
|
|
603
|
+
Accept: "application/json",
|
|
604
|
+
"Content-Type": "application/json"
|
|
605
|
+
},
|
|
606
|
+
body: JSON.stringify(body)
|
|
607
|
+
});
|
|
608
|
+
},
|
|
609
|
+
async listSites() {
|
|
610
|
+
return call(`${base}/api/sites`, {
|
|
611
|
+
method: "GET",
|
|
612
|
+
headers: {
|
|
613
|
+
Authorization: await userBearer(),
|
|
614
|
+
Accept: "application/json"
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
},
|
|
618
|
+
async updateSite(req) {
|
|
619
|
+
const url = `${base}/api/site/${encodeURIComponent(req.slug)}`;
|
|
620
|
+
return call(url, {
|
|
621
|
+
method: "PATCH",
|
|
622
|
+
headers: {
|
|
623
|
+
Authorization: await userBearer(),
|
|
624
|
+
Accept: "application/json",
|
|
625
|
+
"Content-Type": "application/json"
|
|
626
|
+
},
|
|
627
|
+
body: JSON.stringify({ teams: req.teams })
|
|
628
|
+
});
|
|
629
|
+
},
|
|
630
|
+
async deleteSite(req) {
|
|
631
|
+
const url = `${base}/api/site/${encodeURIComponent(req.slug)}`;
|
|
632
|
+
return call(url, {
|
|
633
|
+
method: "DELETE",
|
|
634
|
+
headers: {
|
|
635
|
+
Authorization: await userBearer(),
|
|
636
|
+
Accept: "application/json"
|
|
637
|
+
}
|
|
638
|
+
});
|
|
549
639
|
}
|
|
550
640
|
};
|
|
551
641
|
}
|
|
@@ -740,6 +830,52 @@ function rethrowProxy(prefix, err) {
|
|
|
740
830
|
if (err instanceof Error) throw new StorageError(`${prefix}: ${err.message}`);
|
|
741
831
|
throw new StorageError(`${prefix}: ${String(err)}`);
|
|
742
832
|
}
|
|
833
|
+
var PREFLIGHT_INLINE_LIST_CAP = 10;
|
|
834
|
+
function formatUnauthorizedSiteError(a) {
|
|
835
|
+
const lines = [
|
|
836
|
+
`Site '${a.attempted}' is not registered for your GitHub identity.`,
|
|
837
|
+
``,
|
|
838
|
+
` You are: ${a.login}`,
|
|
839
|
+
``
|
|
840
|
+
];
|
|
841
|
+
if (a.authorized.length === 0) {
|
|
842
|
+
lines.push(
|
|
843
|
+
` Your identity is authorized for no sites yet.`,
|
|
844
|
+
``,
|
|
845
|
+
` Likely causes:`,
|
|
846
|
+
` 1. The '${a.attempted}' slug is not registered.`,
|
|
847
|
+
` Admin (staff): universe sites register ${a.attempted} --team <team>`,
|
|
848
|
+
` 2. You are not in any team listed on any registered site.`,
|
|
849
|
+
` Admin (staff): universe sites update <slug> --team +<your-team>`
|
|
850
|
+
);
|
|
851
|
+
return lines.join("\n");
|
|
852
|
+
}
|
|
853
|
+
const hint = suggest(a.attempted, a.authorized);
|
|
854
|
+
if (hint) {
|
|
855
|
+
lines.push(` Did you mean: ${hint}?`, ``);
|
|
856
|
+
}
|
|
857
|
+
lines.push(
|
|
858
|
+
` Likely causes (most common first):`,
|
|
859
|
+
` 1. Typo in platform.yaml \`site:\` \u2014 check the spelling above.`,
|
|
860
|
+
` 2. The '${a.attempted}' slug is not registered yet.`,
|
|
861
|
+
` Admin (staff): universe sites register ${a.attempted} --team <team>`,
|
|
862
|
+
` 3. You are not in any team authorized for '${a.attempted}'.`,
|
|
863
|
+
` Admin (staff): universe sites update ${a.attempted} --team +<your-team>`,
|
|
864
|
+
``
|
|
865
|
+
);
|
|
866
|
+
if (a.authorized.length <= PREFLIGHT_INLINE_LIST_CAP) {
|
|
867
|
+
lines.push(
|
|
868
|
+
` Your authorized sites (${a.authorized.length}):`,
|
|
869
|
+
...[...a.authorized].sort().map((s) => ` - ${s}`)
|
|
870
|
+
);
|
|
871
|
+
} else {
|
|
872
|
+
lines.push(
|
|
873
|
+
` You have ${a.authorized.length} authorized sites \u2014 too many to inline.`,
|
|
874
|
+
` Run \`universe sites ls --mine\` to inspect the full list.`
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
return lines.join("\n");
|
|
878
|
+
}
|
|
743
879
|
async function deploy(options, deps = {}) {
|
|
744
880
|
const cwd = deps.cwd ?? process.cwd();
|
|
745
881
|
const env = deps.env ?? process.env;
|
|
@@ -775,26 +911,16 @@ async function deploy(options, deps = {}) {
|
|
|
775
911
|
rethrowProxy("whoami preflight failed", err);
|
|
776
912
|
}
|
|
777
913
|
if (!me.authorizedSites.includes(config.site)) {
|
|
778
|
-
const sitesLine = me.authorizedSites.length > 0 ? me.authorizedSites.join(", ") : "(no sites authorized)";
|
|
779
914
|
throw new CredentialError(
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
``,
|
|
786
|
-
`Likely causes (most common first):`,
|
|
787
|
-
` 1. Platform admin has not added '${config.site}' to artemis`,
|
|
788
|
-
` 'config/sites.yaml' yet (one-time, per site).`,
|
|
789
|
-
` 2. You are not in any GitHub team listed for '${config.site}'.`,
|
|
790
|
-
``,
|
|
791
|
-
`Runbook:`,
|
|
792
|
-
` https://github.com/freeCodeCamp/infra/blob/main/docs/runbooks/01-deploy-new-constellation-site.md`
|
|
793
|
-
].join("\n")
|
|
915
|
+
formatUnauthorizedSiteError({
|
|
916
|
+
attempted: config.site,
|
|
917
|
+
login: me.login,
|
|
918
|
+
authorized: me.authorizedSites
|
|
919
|
+
})
|
|
794
920
|
);
|
|
795
921
|
}
|
|
796
922
|
const git = gitState();
|
|
797
|
-
if (git.dirty) {
|
|
923
|
+
if (git.dirty && !options.json) {
|
|
798
924
|
warn(
|
|
799
925
|
"git working tree is dirty \u2014 uncommitted changes will not be reflected."
|
|
800
926
|
);
|
|
@@ -806,7 +932,7 @@ async function deploy(options, deps = {}) {
|
|
|
806
932
|
cwd,
|
|
807
933
|
outputDir
|
|
808
934
|
});
|
|
809
|
-
if (buildResult.skipped) {
|
|
935
|
+
if (buildResult.skipped && !options.json) {
|
|
810
936
|
info("build.command not set \u2014 using pre-built output.");
|
|
811
937
|
}
|
|
812
938
|
const resolvedOutputDir = buildResult.outputDir;
|
|
@@ -865,7 +991,7 @@ async function deploy(options, deps = {}) {
|
|
|
865
991
|
);
|
|
866
992
|
} else {
|
|
867
993
|
const sizeKB = (uploadResult.totalSize / 1024).toFixed(1);
|
|
868
|
-
const nextLine = mode === "preview" ?
|
|
994
|
+
const nextLine = mode === "preview" ? `Next: universe static promote --from ${finalizeResult.deployId}` : "Promoted to production.\nPreview alias unchanged.";
|
|
869
995
|
success(
|
|
870
996
|
[
|
|
871
997
|
`Deployed ${finalizeResult.deployId}`,
|
|
@@ -901,7 +1027,7 @@ async function deploy(options, deps = {}) {
|
|
|
901
1027
|
} else {
|
|
902
1028
|
error(message);
|
|
903
1029
|
}
|
|
904
|
-
exit(code
|
|
1030
|
+
exit(code);
|
|
905
1031
|
}
|
|
906
1032
|
}
|
|
907
1033
|
|
|
@@ -1012,7 +1138,7 @@ async function login(options, deps = {}) {
|
|
|
1012
1138
|
} else {
|
|
1013
1139
|
error(msg);
|
|
1014
1140
|
}
|
|
1015
|
-
exit(EXIT_CONFIRM
|
|
1141
|
+
exit(EXIT_CONFIRM);
|
|
1016
1142
|
return;
|
|
1017
1143
|
}
|
|
1018
1144
|
}
|
|
@@ -1055,7 +1181,7 @@ async function login(options, deps = {}) {
|
|
|
1055
1181
|
} else {
|
|
1056
1182
|
error(message);
|
|
1057
1183
|
}
|
|
1058
|
-
exit(EXIT_CREDENTIALS
|
|
1184
|
+
exit(EXIT_CREDENTIALS);
|
|
1059
1185
|
return;
|
|
1060
1186
|
}
|
|
1061
1187
|
await save(token);
|
|
@@ -1168,7 +1294,10 @@ async function ls(options, deps = {}) {
|
|
|
1168
1294
|
getAuthToken: () => identity.token
|
|
1169
1295
|
});
|
|
1170
1296
|
const raw = await client.siteDeploys({ site });
|
|
1171
|
-
const
|
|
1297
|
+
const sorted = [...raw].sort(
|
|
1298
|
+
(a, b) => b.deployId.localeCompare(a.deployId)
|
|
1299
|
+
);
|
|
1300
|
+
const deploys = sorted.map((d) => parseDeployId(d.deployId));
|
|
1172
1301
|
if (options.json) {
|
|
1173
1302
|
emitJson4(
|
|
1174
1303
|
buildEnvelope("ls", true, {
|
|
@@ -1191,7 +1320,7 @@ async function ls(options, deps = {}) {
|
|
|
1191
1320
|
} else {
|
|
1192
1321
|
error(message);
|
|
1193
1322
|
}
|
|
1194
|
-
exit(code
|
|
1323
|
+
exit(code);
|
|
1195
1324
|
}
|
|
1196
1325
|
}
|
|
1197
1326
|
|
|
@@ -1262,15 +1391,17 @@ async function promote(options, deps = {}) {
|
|
|
1262
1391
|
})
|
|
1263
1392
|
);
|
|
1264
1393
|
} else {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1394
|
+
const lines = [
|
|
1395
|
+
`Promoted ${result.deployId} to production`,
|
|
1396
|
+
``,
|
|
1397
|
+
` Site: ${config.site}`,
|
|
1398
|
+
` Deploy: ${result.deployId}`,
|
|
1399
|
+
` Production: ${result.url}`
|
|
1400
|
+
];
|
|
1401
|
+
if (options.from) {
|
|
1402
|
+
lines.push(``, "Preview alias unchanged.");
|
|
1403
|
+
}
|
|
1404
|
+
success(lines.join("\n"));
|
|
1274
1405
|
}
|
|
1275
1406
|
} catch (err) {
|
|
1276
1407
|
const { code, message } = wrapProxyError("promote", err);
|
|
@@ -1279,7 +1410,7 @@ async function promote(options, deps = {}) {
|
|
|
1279
1410
|
} else {
|
|
1280
1411
|
error(message);
|
|
1281
1412
|
}
|
|
1282
|
-
exit(code
|
|
1413
|
+
exit(code);
|
|
1283
1414
|
}
|
|
1284
1415
|
}
|
|
1285
1416
|
|
|
@@ -1367,7 +1498,7 @@ async function rollback(options, deps = {}) {
|
|
|
1367
1498
|
} else {
|
|
1368
1499
|
error(message);
|
|
1369
1500
|
}
|
|
1370
|
-
exit(code
|
|
1501
|
+
exit(code);
|
|
1371
1502
|
}
|
|
1372
1503
|
}
|
|
1373
1504
|
|
|
@@ -1392,7 +1523,7 @@ async function whoami(options, deps = {}) {
|
|
|
1392
1523
|
} else {
|
|
1393
1524
|
error(msg);
|
|
1394
1525
|
}
|
|
1395
|
-
exit(EXIT_CREDENTIALS
|
|
1526
|
+
exit(EXIT_CREDENTIALS);
|
|
1396
1527
|
return;
|
|
1397
1528
|
}
|
|
1398
1529
|
const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL2;
|
|
@@ -1402,21 +1533,22 @@ async function whoami(options, deps = {}) {
|
|
|
1402
1533
|
});
|
|
1403
1534
|
try {
|
|
1404
1535
|
const result = await client.whoami();
|
|
1536
|
+
const count = result.authorizedSites.length;
|
|
1405
1537
|
if (options.json) {
|
|
1406
1538
|
emitJson7(
|
|
1407
1539
|
buildEnvelope("whoami", true, {
|
|
1408
1540
|
login: result.login,
|
|
1409
|
-
|
|
1410
|
-
|
|
1541
|
+
identitySource: identity.source,
|
|
1542
|
+
authorizedSitesCount: count
|
|
1411
1543
|
})
|
|
1412
1544
|
);
|
|
1413
1545
|
} else {
|
|
1414
|
-
const
|
|
1546
|
+
const sitesLine = count === 0 ? "Authorized for 0 sites." : `Authorized for ${count} site${count === 1 ? "" : "s"} \u2014 run \`universe sites ls --mine\``;
|
|
1415
1547
|
success(
|
|
1416
1548
|
[
|
|
1417
1549
|
`Logged in as: ${result.login}`,
|
|
1418
|
-
`
|
|
1419
|
-
|
|
1550
|
+
`Identity source: ${identity.source}`,
|
|
1551
|
+
sitesLine
|
|
1420
1552
|
].join("\n")
|
|
1421
1553
|
);
|
|
1422
1554
|
}
|
|
@@ -1428,13 +1560,238 @@ async function whoami(options, deps = {}) {
|
|
|
1428
1560
|
} else {
|
|
1429
1561
|
error(message);
|
|
1430
1562
|
}
|
|
1431
|
-
exit(exitCode
|
|
1563
|
+
exit(exitCode);
|
|
1432
1564
|
}
|
|
1433
1565
|
}
|
|
1434
1566
|
|
|
1435
|
-
// src/
|
|
1567
|
+
// src/commands/sites/ls.ts
|
|
1436
1568
|
import { log as log8 } from "@clack/prompts";
|
|
1437
1569
|
|
|
1570
|
+
// src/commands/sites/_shared.ts
|
|
1571
|
+
function emitJson8(envelope) {
|
|
1572
|
+
process.stdout.write(JSON.stringify(envelope) + "\n");
|
|
1573
|
+
}
|
|
1574
|
+
function parseTeamsFlag(raw) {
|
|
1575
|
+
if (raw === void 0 || raw === null) return [];
|
|
1576
|
+
const tokens = Array.isArray(raw) ? raw : [raw];
|
|
1577
|
+
return tokens.flatMap((s) => s.split(",")).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1578
|
+
}
|
|
1579
|
+
async function setupClient(deps) {
|
|
1580
|
+
const env = deps.env ?? process.env;
|
|
1581
|
+
const resolveId = deps.resolveIdentity ?? resolveIdentity;
|
|
1582
|
+
const mkClient = deps.createProxyClient ?? createProxyClient;
|
|
1583
|
+
const identity = await resolveId({ env });
|
|
1584
|
+
if (!identity) {
|
|
1585
|
+
throw new CredentialError(
|
|
1586
|
+
"No GitHub identity available. Run `universe login`, set $GITHUB_TOKEN, or install the gh CLI."
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
|
|
1590
|
+
const client = mkClient({
|
|
1591
|
+
baseUrl,
|
|
1592
|
+
getAuthToken: () => identity.token
|
|
1593
|
+
});
|
|
1594
|
+
return { client, identitySource: identity.source };
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// src/commands/sites/ls.ts
|
|
1598
|
+
function formatTable2(rows) {
|
|
1599
|
+
if (rows.length === 0) return "No registered sites.";
|
|
1600
|
+
const headers = ["SLUG", "TEAMS", "CREATED BY", "CREATED AT"];
|
|
1601
|
+
const cells = rows.map((r) => [
|
|
1602
|
+
r.slug,
|
|
1603
|
+
r.teams.join(","),
|
|
1604
|
+
r.createdBy,
|
|
1605
|
+
r.createdAt
|
|
1606
|
+
]);
|
|
1607
|
+
const widths = headers.map(
|
|
1608
|
+
(h, i) => Math.max(h.length, ...cells.map((row) => row[i]?.length ?? 0))
|
|
1609
|
+
);
|
|
1610
|
+
const fmt = (row) => row.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
|
|
1611
|
+
return [fmt(headers), ...cells.map(fmt)].join("\n");
|
|
1612
|
+
}
|
|
1613
|
+
async function ls2(options, deps = {}) {
|
|
1614
|
+
const command = "sites ls";
|
|
1615
|
+
const success = deps.logSuccess ?? ((s) => log8.message(s));
|
|
1616
|
+
const error = deps.logError ?? ((s) => log8.error(s));
|
|
1617
|
+
const exit = deps.exit ?? exitWithCode;
|
|
1618
|
+
try {
|
|
1619
|
+
const { client } = await setupClient(deps);
|
|
1620
|
+
let rows = await client.listSites();
|
|
1621
|
+
let scope = "all";
|
|
1622
|
+
if (options.mine) {
|
|
1623
|
+
const me = await client.whoami();
|
|
1624
|
+
const allowed = new Set(me.authorizedSites);
|
|
1625
|
+
rows = rows.filter((r) => allowed.has(r.slug));
|
|
1626
|
+
scope = "mine";
|
|
1627
|
+
}
|
|
1628
|
+
if (options.json) {
|
|
1629
|
+
emitJson8(
|
|
1630
|
+
buildEnvelope(command, true, {
|
|
1631
|
+
count: rows.length,
|
|
1632
|
+
scope,
|
|
1633
|
+
sites: rows
|
|
1634
|
+
})
|
|
1635
|
+
);
|
|
1636
|
+
} else {
|
|
1637
|
+
success(formatTable2(rows));
|
|
1638
|
+
}
|
|
1639
|
+
} catch (err) {
|
|
1640
|
+
const { code, message } = wrapProxyError(command, err);
|
|
1641
|
+
if (options.json) {
|
|
1642
|
+
emitJson8(buildErrorEnvelope(command, code, message));
|
|
1643
|
+
} else {
|
|
1644
|
+
error(message);
|
|
1645
|
+
}
|
|
1646
|
+
exit(code);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// src/commands/sites/register.ts
|
|
1651
|
+
import { log as log9 } from "@clack/prompts";
|
|
1652
|
+
async function register(options, deps = {}) {
|
|
1653
|
+
const command = "sites register";
|
|
1654
|
+
const success = deps.logSuccess ?? ((s) => log9.success(s));
|
|
1655
|
+
const error = deps.logError ?? ((s) => log9.error(s));
|
|
1656
|
+
const exit = deps.exit ?? exitWithCode;
|
|
1657
|
+
try {
|
|
1658
|
+
if (!options.slug || options.slug.trim().length === 0) {
|
|
1659
|
+
throw new UsageError("slug is required (positional argument)");
|
|
1660
|
+
}
|
|
1661
|
+
const teams = parseTeamsFlag(options.team);
|
|
1662
|
+
const { client } = await setupClient(deps);
|
|
1663
|
+
const row = await client.registerSite({
|
|
1664
|
+
slug: options.slug,
|
|
1665
|
+
teams: teams.length > 0 ? teams : void 0
|
|
1666
|
+
});
|
|
1667
|
+
if (options.json) {
|
|
1668
|
+
emitJson8(
|
|
1669
|
+
buildEnvelope(command, true, {
|
|
1670
|
+
slug: row.slug,
|
|
1671
|
+
teams: row.teams,
|
|
1672
|
+
createdAt: row.createdAt,
|
|
1673
|
+
createdBy: row.createdBy
|
|
1674
|
+
})
|
|
1675
|
+
);
|
|
1676
|
+
} else {
|
|
1677
|
+
success(
|
|
1678
|
+
[
|
|
1679
|
+
`Registered ${row.slug}`,
|
|
1680
|
+
``,
|
|
1681
|
+
` Slug: ${row.slug}`,
|
|
1682
|
+
` Teams: ${row.teams.join(", ")}`,
|
|
1683
|
+
` Created by: ${row.createdBy}`,
|
|
1684
|
+
` Created at: ${row.createdAt}`
|
|
1685
|
+
].join("\n")
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
} catch (err) {
|
|
1689
|
+
const { code, message } = wrapProxyError(command, err);
|
|
1690
|
+
if (options.json) {
|
|
1691
|
+
emitJson8(buildErrorEnvelope(command, code, message));
|
|
1692
|
+
} else {
|
|
1693
|
+
error(message);
|
|
1694
|
+
}
|
|
1695
|
+
exit(code);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// src/commands/sites/rm.ts
|
|
1700
|
+
import { log as log10 } from "@clack/prompts";
|
|
1701
|
+
async function rm2(options, deps = {}) {
|
|
1702
|
+
const command = "sites rm";
|
|
1703
|
+
const success = deps.logSuccess ?? ((s) => log10.success(s));
|
|
1704
|
+
const error = deps.logError ?? ((s) => log10.error(s));
|
|
1705
|
+
const exit = deps.exit ?? exitWithCode;
|
|
1706
|
+
try {
|
|
1707
|
+
if (!options.slug || options.slug.trim().length === 0) {
|
|
1708
|
+
throw new UsageError("slug is required (positional argument)");
|
|
1709
|
+
}
|
|
1710
|
+
const { client } = await setupClient(deps);
|
|
1711
|
+
await client.deleteSite({ slug: options.slug });
|
|
1712
|
+
if (options.json) {
|
|
1713
|
+
emitJson8(
|
|
1714
|
+
buildEnvelope(command, true, {
|
|
1715
|
+
slug: options.slug,
|
|
1716
|
+
deleted: true
|
|
1717
|
+
})
|
|
1718
|
+
);
|
|
1719
|
+
} else {
|
|
1720
|
+
success(
|
|
1721
|
+
[
|
|
1722
|
+
`Deleted ${options.slug}`,
|
|
1723
|
+
``,
|
|
1724
|
+
` Note: R2 deploy bytes are NOT removed; they age out via the`,
|
|
1725
|
+
` post-GA cleanup cron.`
|
|
1726
|
+
].join("\n")
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
} catch (err) {
|
|
1730
|
+
const { code, message } = wrapProxyError(command, err);
|
|
1731
|
+
if (options.json) {
|
|
1732
|
+
emitJson8(buildErrorEnvelope(command, code, message));
|
|
1733
|
+
} else {
|
|
1734
|
+
error(message);
|
|
1735
|
+
}
|
|
1736
|
+
exit(code);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
// src/commands/sites/update.ts
|
|
1741
|
+
import { log as log11 } from "@clack/prompts";
|
|
1742
|
+
async function update(options, deps = {}) {
|
|
1743
|
+
const command = "sites update";
|
|
1744
|
+
const success = deps.logSuccess ?? ((s) => log11.success(s));
|
|
1745
|
+
const error = deps.logError ?? ((s) => log11.error(s));
|
|
1746
|
+
const exit = deps.exit ?? exitWithCode;
|
|
1747
|
+
try {
|
|
1748
|
+
if (!options.slug || options.slug.trim().length === 0) {
|
|
1749
|
+
throw new UsageError("slug is required (positional argument)");
|
|
1750
|
+
}
|
|
1751
|
+
const teams = parseTeamsFlag(options.team);
|
|
1752
|
+
if (teams.length === 0) {
|
|
1753
|
+
throw new UsageError(
|
|
1754
|
+
"--team is required with at least one slug; use `sites rm` to remove a site"
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
const { client } = await setupClient(deps);
|
|
1758
|
+
const row = await client.updateSite({
|
|
1759
|
+
slug: options.slug,
|
|
1760
|
+
teams
|
|
1761
|
+
});
|
|
1762
|
+
if (options.json) {
|
|
1763
|
+
emitJson8(
|
|
1764
|
+
buildEnvelope(command, true, {
|
|
1765
|
+
slug: row.slug,
|
|
1766
|
+
teams: row.teams,
|
|
1767
|
+
updatedAt: row.updatedAt
|
|
1768
|
+
})
|
|
1769
|
+
);
|
|
1770
|
+
} else {
|
|
1771
|
+
success(
|
|
1772
|
+
[
|
|
1773
|
+
`Updated ${row.slug}`,
|
|
1774
|
+
``,
|
|
1775
|
+
` Slug: ${row.slug}`,
|
|
1776
|
+
` Teams: ${row.teams.join(", ")}`,
|
|
1777
|
+
` Updated at: ${row.updatedAt}`
|
|
1778
|
+
].join("\n")
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
} catch (err) {
|
|
1782
|
+
const { code, message } = wrapProxyError(command, err);
|
|
1783
|
+
if (options.json) {
|
|
1784
|
+
emitJson8(buildErrorEnvelope(command, code, message));
|
|
1785
|
+
} else {
|
|
1786
|
+
error(message);
|
|
1787
|
+
}
|
|
1788
|
+
exit(code);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// src/output/format.ts
|
|
1793
|
+
import { log as log12 } from "@clack/prompts";
|
|
1794
|
+
|
|
1438
1795
|
// src/output/redact.ts
|
|
1439
1796
|
var AWS_KEY_PREFIX_PATTERN = /(?:AKIA|ASIA|AROA|AIDA|ACCA|ANPA|ABIA|AGPA)[A-Z0-9]{12,}/g;
|
|
1440
1797
|
var URL_CREDS_PATTERN = /https?:\/\/[^@\s]+@/g;
|
|
@@ -1492,18 +1849,18 @@ function outputError(ctx, code, message, issues) {
|
|
|
1492
1849
|
);
|
|
1493
1850
|
process.stdout.write(JSON.stringify(envelope) + "\n");
|
|
1494
1851
|
} else {
|
|
1495
|
-
|
|
1852
|
+
log12.error(redactedMessage);
|
|
1496
1853
|
}
|
|
1497
1854
|
}
|
|
1498
1855
|
|
|
1499
1856
|
// src/cli.ts
|
|
1500
|
-
var version = true ? "0.
|
|
1857
|
+
var version = true ? "0.5.1" : "0.0.0";
|
|
1501
1858
|
function handleActionError(command, json, err) {
|
|
1502
1859
|
const ctx = { json, command };
|
|
1503
1860
|
const message = err instanceof Error ? err.message : "unknown error";
|
|
1504
1861
|
const code = err instanceof CliError ? err.exitCode : EXIT_USAGE;
|
|
1505
1862
|
outputError(ctx, code, message);
|
|
1506
|
-
exitWithCode(code
|
|
1863
|
+
exitWithCode(code);
|
|
1507
1864
|
}
|
|
1508
1865
|
function findFirstPositional(args) {
|
|
1509
1866
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -1515,7 +1872,75 @@ function findFirstPositional(args) {
|
|
|
1515
1872
|
function run(argv = process.argv) {
|
|
1516
1873
|
const args = argv.slice(2);
|
|
1517
1874
|
const firstPosIdx = findFirstPositional(args);
|
|
1518
|
-
const
|
|
1875
|
+
const namespace = firstPosIdx >= 0 ? args[firstPosIdx] : void 0;
|
|
1876
|
+
const isStatic = namespace === "static";
|
|
1877
|
+
const isSites = namespace === "sites";
|
|
1878
|
+
if (isSites) {
|
|
1879
|
+
const sitesArgs = [
|
|
1880
|
+
...args.slice(0, firstPosIdx),
|
|
1881
|
+
...args.slice(firstPosIdx + 1)
|
|
1882
|
+
];
|
|
1883
|
+
const sitesCli = cac("universe sites");
|
|
1884
|
+
sitesCli.command("register <slug>", "Register a new static site (staff only)").option("--json", "Output as JSON").option(
|
|
1885
|
+
"--team <name>",
|
|
1886
|
+
"GitHub team slug (repeatable, or comma-separated). Defaults to staff."
|
|
1887
|
+
).action(
|
|
1888
|
+
async (slug, flags) => {
|
|
1889
|
+
try {
|
|
1890
|
+
await register({
|
|
1891
|
+
json: flags.json ?? false,
|
|
1892
|
+
slug,
|
|
1893
|
+
team: flags.team
|
|
1894
|
+
});
|
|
1895
|
+
} catch (err) {
|
|
1896
|
+
handleActionError("sites register", flags.json ?? false, err);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
);
|
|
1900
|
+
sitesCli.command("ls", "List sites in the registry").option("--json", "Output as JSON").option(
|
|
1901
|
+
"--mine",
|
|
1902
|
+
"Filter to sites your GitHub identity is authorized for"
|
|
1903
|
+
).action(async (flags) => {
|
|
1904
|
+
try {
|
|
1905
|
+
await ls2({
|
|
1906
|
+
json: flags.json ?? false,
|
|
1907
|
+
mine: flags.mine ?? false
|
|
1908
|
+
});
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
handleActionError("sites ls", flags.json ?? false, err);
|
|
1911
|
+
}
|
|
1912
|
+
});
|
|
1913
|
+
sitesCli.command(
|
|
1914
|
+
"update <slug>",
|
|
1915
|
+
"Replace the teams list for an existing site (staff only)"
|
|
1916
|
+
).option("--json", "Output as JSON").option(
|
|
1917
|
+
"--team <name>",
|
|
1918
|
+
"GitHub team slug (repeatable, or comma-separated). Required."
|
|
1919
|
+
).action(
|
|
1920
|
+
async (slug, flags) => {
|
|
1921
|
+
try {
|
|
1922
|
+
await update({
|
|
1923
|
+
json: flags.json ?? false,
|
|
1924
|
+
slug,
|
|
1925
|
+
team: flags.team
|
|
1926
|
+
});
|
|
1927
|
+
} catch (err) {
|
|
1928
|
+
handleActionError("sites update", flags.json ?? false, err);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
);
|
|
1932
|
+
sitesCli.command("rm <slug>", "Remove a site from the registry (staff only)").option("--json", "Output as JSON").action(async (slug, flags) => {
|
|
1933
|
+
try {
|
|
1934
|
+
await rm2({ json: flags.json ?? false, slug });
|
|
1935
|
+
} catch (err) {
|
|
1936
|
+
handleActionError("sites rm", flags.json ?? false, err);
|
|
1937
|
+
}
|
|
1938
|
+
});
|
|
1939
|
+
sitesCli.help();
|
|
1940
|
+
sitesCli.version(version);
|
|
1941
|
+
sitesCli.parse(["node", "universe-sites", ...sitesArgs]);
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1519
1944
|
if (isStatic) {
|
|
1520
1945
|
const staticArgs = [
|
|
1521
1946
|
...args.slice(0, firstPosIdx),
|
|
@@ -1598,6 +2023,7 @@ function run(argv = process.argv) {
|
|
|
1598
2023
|
}
|
|
1599
2024
|
});
|
|
1600
2025
|
cli.command("static <subcommand>", "Static site deployment commands");
|
|
2026
|
+
cli.command("sites <subcommand>", "Static site registry commands");
|
|
1601
2027
|
cli.help();
|
|
1602
2028
|
cli.version(version);
|
|
1603
2029
|
cli.parse(argv);
|