@freecodecamp/universe-cli 0.4.0 → 0.5.0
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 +71 -55
- package/dist/index.cjs +450 -35
- package/dist/index.js +458 -37
- package/package.json +2 -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,22 +911,12 @@ 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();
|
|
@@ -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);
|
|
@@ -1191,7 +1317,7 @@ async function ls(options, deps = {}) {
|
|
|
1191
1317
|
} else {
|
|
1192
1318
|
error(message);
|
|
1193
1319
|
}
|
|
1194
|
-
exit(code
|
|
1320
|
+
exit(code);
|
|
1195
1321
|
}
|
|
1196
1322
|
}
|
|
1197
1323
|
|
|
@@ -1279,7 +1405,7 @@ async function promote(options, deps = {}) {
|
|
|
1279
1405
|
} else {
|
|
1280
1406
|
error(message);
|
|
1281
1407
|
}
|
|
1282
|
-
exit(code
|
|
1408
|
+
exit(code);
|
|
1283
1409
|
}
|
|
1284
1410
|
}
|
|
1285
1411
|
|
|
@@ -1367,7 +1493,7 @@ async function rollback(options, deps = {}) {
|
|
|
1367
1493
|
} else {
|
|
1368
1494
|
error(message);
|
|
1369
1495
|
}
|
|
1370
|
-
exit(code
|
|
1496
|
+
exit(code);
|
|
1371
1497
|
}
|
|
1372
1498
|
}
|
|
1373
1499
|
|
|
@@ -1392,7 +1518,7 @@ async function whoami(options, deps = {}) {
|
|
|
1392
1518
|
} else {
|
|
1393
1519
|
error(msg);
|
|
1394
1520
|
}
|
|
1395
|
-
exit(EXIT_CREDENTIALS
|
|
1521
|
+
exit(EXIT_CREDENTIALS);
|
|
1396
1522
|
return;
|
|
1397
1523
|
}
|
|
1398
1524
|
const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL2;
|
|
@@ -1402,21 +1528,22 @@ async function whoami(options, deps = {}) {
|
|
|
1402
1528
|
});
|
|
1403
1529
|
try {
|
|
1404
1530
|
const result = await client.whoami();
|
|
1531
|
+
const count = result.authorizedSites.length;
|
|
1405
1532
|
if (options.json) {
|
|
1406
1533
|
emitJson7(
|
|
1407
1534
|
buildEnvelope("whoami", true, {
|
|
1408
1535
|
login: result.login,
|
|
1409
|
-
|
|
1410
|
-
|
|
1536
|
+
identitySource: identity.source,
|
|
1537
|
+
authorizedSitesCount: count
|
|
1411
1538
|
})
|
|
1412
1539
|
);
|
|
1413
1540
|
} else {
|
|
1414
|
-
const
|
|
1541
|
+
const sitesLine = count === 0 ? "Authorized for 0 sites." : `Authorized for ${count} site${count === 1 ? "" : "s"} \u2014 run \`universe sites ls --mine\``;
|
|
1415
1542
|
success(
|
|
1416
1543
|
[
|
|
1417
1544
|
`Logged in as: ${result.login}`,
|
|
1418
|
-
`
|
|
1419
|
-
|
|
1545
|
+
`Identity source: ${identity.source}`,
|
|
1546
|
+
sitesLine
|
|
1420
1547
|
].join("\n")
|
|
1421
1548
|
);
|
|
1422
1549
|
}
|
|
@@ -1428,13 +1555,238 @@ async function whoami(options, deps = {}) {
|
|
|
1428
1555
|
} else {
|
|
1429
1556
|
error(message);
|
|
1430
1557
|
}
|
|
1431
|
-
exit(exitCode
|
|
1558
|
+
exit(exitCode);
|
|
1432
1559
|
}
|
|
1433
1560
|
}
|
|
1434
1561
|
|
|
1435
|
-
// src/
|
|
1562
|
+
// src/commands/sites/ls.ts
|
|
1436
1563
|
import { log as log8 } from "@clack/prompts";
|
|
1437
1564
|
|
|
1565
|
+
// src/commands/sites/_shared.ts
|
|
1566
|
+
function emitJson8(envelope) {
|
|
1567
|
+
process.stdout.write(JSON.stringify(envelope) + "\n");
|
|
1568
|
+
}
|
|
1569
|
+
function parseTeamsFlag(raw) {
|
|
1570
|
+
if (raw === void 0 || raw === null) return [];
|
|
1571
|
+
const tokens = Array.isArray(raw) ? raw : [raw];
|
|
1572
|
+
return tokens.flatMap((s) => s.split(",")).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1573
|
+
}
|
|
1574
|
+
async function setupClient(deps) {
|
|
1575
|
+
const env = deps.env ?? process.env;
|
|
1576
|
+
const resolveId = deps.resolveIdentity ?? resolveIdentity;
|
|
1577
|
+
const mkClient = deps.createProxyClient ?? createProxyClient;
|
|
1578
|
+
const identity = await resolveId({ env });
|
|
1579
|
+
if (!identity) {
|
|
1580
|
+
throw new CredentialError(
|
|
1581
|
+
"No GitHub identity available. Run `universe login`, set $GITHUB_TOKEN, or install the gh CLI."
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
|
|
1585
|
+
const client = mkClient({
|
|
1586
|
+
baseUrl,
|
|
1587
|
+
getAuthToken: () => identity.token
|
|
1588
|
+
});
|
|
1589
|
+
return { client, identitySource: identity.source };
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// src/commands/sites/ls.ts
|
|
1593
|
+
function formatTable2(rows) {
|
|
1594
|
+
if (rows.length === 0) return "No registered sites.";
|
|
1595
|
+
const headers = ["SLUG", "TEAMS", "CREATED BY", "CREATED AT"];
|
|
1596
|
+
const cells = rows.map((r) => [
|
|
1597
|
+
r.slug,
|
|
1598
|
+
r.teams.join(","),
|
|
1599
|
+
r.createdBy,
|
|
1600
|
+
r.createdAt
|
|
1601
|
+
]);
|
|
1602
|
+
const widths = headers.map(
|
|
1603
|
+
(h, i) => Math.max(h.length, ...cells.map((row) => row[i]?.length ?? 0))
|
|
1604
|
+
);
|
|
1605
|
+
const fmt = (row) => row.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
|
|
1606
|
+
return [fmt(headers), ...cells.map(fmt)].join("\n");
|
|
1607
|
+
}
|
|
1608
|
+
async function ls2(options, deps = {}) {
|
|
1609
|
+
const command = "sites ls";
|
|
1610
|
+
const success = deps.logSuccess ?? ((s) => log8.message(s));
|
|
1611
|
+
const error = deps.logError ?? ((s) => log8.error(s));
|
|
1612
|
+
const exit = deps.exit ?? exitWithCode;
|
|
1613
|
+
try {
|
|
1614
|
+
const { client } = await setupClient(deps);
|
|
1615
|
+
let rows = await client.listSites();
|
|
1616
|
+
let scope = "all";
|
|
1617
|
+
if (options.mine) {
|
|
1618
|
+
const me = await client.whoami();
|
|
1619
|
+
const allowed = new Set(me.authorizedSites);
|
|
1620
|
+
rows = rows.filter((r) => allowed.has(r.slug));
|
|
1621
|
+
scope = "mine";
|
|
1622
|
+
}
|
|
1623
|
+
if (options.json) {
|
|
1624
|
+
emitJson8(
|
|
1625
|
+
buildEnvelope(command, true, {
|
|
1626
|
+
count: rows.length,
|
|
1627
|
+
scope,
|
|
1628
|
+
sites: rows
|
|
1629
|
+
})
|
|
1630
|
+
);
|
|
1631
|
+
} else {
|
|
1632
|
+
success(formatTable2(rows));
|
|
1633
|
+
}
|
|
1634
|
+
} catch (err) {
|
|
1635
|
+
const { code, message } = wrapProxyError(command, err);
|
|
1636
|
+
if (options.json) {
|
|
1637
|
+
emitJson8(buildErrorEnvelope(command, code, message));
|
|
1638
|
+
} else {
|
|
1639
|
+
error(message);
|
|
1640
|
+
}
|
|
1641
|
+
exit(code);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
// src/commands/sites/register.ts
|
|
1646
|
+
import { log as log9 } from "@clack/prompts";
|
|
1647
|
+
async function register(options, deps = {}) {
|
|
1648
|
+
const command = "sites register";
|
|
1649
|
+
const success = deps.logSuccess ?? ((s) => log9.success(s));
|
|
1650
|
+
const error = deps.logError ?? ((s) => log9.error(s));
|
|
1651
|
+
const exit = deps.exit ?? exitWithCode;
|
|
1652
|
+
try {
|
|
1653
|
+
if (!options.slug || options.slug.trim().length === 0) {
|
|
1654
|
+
throw new UsageError("slug is required (positional argument)");
|
|
1655
|
+
}
|
|
1656
|
+
const teams = parseTeamsFlag(options.team);
|
|
1657
|
+
const { client } = await setupClient(deps);
|
|
1658
|
+
const row = await client.registerSite({
|
|
1659
|
+
slug: options.slug,
|
|
1660
|
+
teams: teams.length > 0 ? teams : void 0
|
|
1661
|
+
});
|
|
1662
|
+
if (options.json) {
|
|
1663
|
+
emitJson8(
|
|
1664
|
+
buildEnvelope(command, true, {
|
|
1665
|
+
slug: row.slug,
|
|
1666
|
+
teams: row.teams,
|
|
1667
|
+
createdAt: row.createdAt,
|
|
1668
|
+
createdBy: row.createdBy
|
|
1669
|
+
})
|
|
1670
|
+
);
|
|
1671
|
+
} else {
|
|
1672
|
+
success(
|
|
1673
|
+
[
|
|
1674
|
+
`Registered ${row.slug}`,
|
|
1675
|
+
``,
|
|
1676
|
+
` Slug: ${row.slug}`,
|
|
1677
|
+
` Teams: ${row.teams.join(", ")}`,
|
|
1678
|
+
` Created by: ${row.createdBy}`,
|
|
1679
|
+
` Created at: ${row.createdAt}`
|
|
1680
|
+
].join("\n")
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
} catch (err) {
|
|
1684
|
+
const { code, message } = wrapProxyError(command, err);
|
|
1685
|
+
if (options.json) {
|
|
1686
|
+
emitJson8(buildErrorEnvelope(command, code, message));
|
|
1687
|
+
} else {
|
|
1688
|
+
error(message);
|
|
1689
|
+
}
|
|
1690
|
+
exit(code);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// src/commands/sites/rm.ts
|
|
1695
|
+
import { log as log10 } from "@clack/prompts";
|
|
1696
|
+
async function rm2(options, deps = {}) {
|
|
1697
|
+
const command = "sites rm";
|
|
1698
|
+
const success = deps.logSuccess ?? ((s) => log10.success(s));
|
|
1699
|
+
const error = deps.logError ?? ((s) => log10.error(s));
|
|
1700
|
+
const exit = deps.exit ?? exitWithCode;
|
|
1701
|
+
try {
|
|
1702
|
+
if (!options.slug || options.slug.trim().length === 0) {
|
|
1703
|
+
throw new UsageError("slug is required (positional argument)");
|
|
1704
|
+
}
|
|
1705
|
+
const { client } = await setupClient(deps);
|
|
1706
|
+
await client.deleteSite({ slug: options.slug });
|
|
1707
|
+
if (options.json) {
|
|
1708
|
+
emitJson8(
|
|
1709
|
+
buildEnvelope(command, true, {
|
|
1710
|
+
slug: options.slug,
|
|
1711
|
+
deleted: true
|
|
1712
|
+
})
|
|
1713
|
+
);
|
|
1714
|
+
} else {
|
|
1715
|
+
success(
|
|
1716
|
+
[
|
|
1717
|
+
`Deleted ${options.slug}`,
|
|
1718
|
+
``,
|
|
1719
|
+
` Note: R2 deploy bytes are NOT removed; they age out via the`,
|
|
1720
|
+
` post-GA cleanup cron.`
|
|
1721
|
+
].join("\n")
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
} catch (err) {
|
|
1725
|
+
const { code, message } = wrapProxyError(command, err);
|
|
1726
|
+
if (options.json) {
|
|
1727
|
+
emitJson8(buildErrorEnvelope(command, code, message));
|
|
1728
|
+
} else {
|
|
1729
|
+
error(message);
|
|
1730
|
+
}
|
|
1731
|
+
exit(code);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// src/commands/sites/update.ts
|
|
1736
|
+
import { log as log11 } from "@clack/prompts";
|
|
1737
|
+
async function update(options, deps = {}) {
|
|
1738
|
+
const command = "sites update";
|
|
1739
|
+
const success = deps.logSuccess ?? ((s) => log11.success(s));
|
|
1740
|
+
const error = deps.logError ?? ((s) => log11.error(s));
|
|
1741
|
+
const exit = deps.exit ?? exitWithCode;
|
|
1742
|
+
try {
|
|
1743
|
+
if (!options.slug || options.slug.trim().length === 0) {
|
|
1744
|
+
throw new UsageError("slug is required (positional argument)");
|
|
1745
|
+
}
|
|
1746
|
+
const teams = parseTeamsFlag(options.team);
|
|
1747
|
+
if (teams.length === 0) {
|
|
1748
|
+
throw new UsageError(
|
|
1749
|
+
"--team is required with at least one slug; use `sites rm` to remove a site"
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
const { client } = await setupClient(deps);
|
|
1753
|
+
const row = await client.updateSite({
|
|
1754
|
+
slug: options.slug,
|
|
1755
|
+
teams
|
|
1756
|
+
});
|
|
1757
|
+
if (options.json) {
|
|
1758
|
+
emitJson8(
|
|
1759
|
+
buildEnvelope(command, true, {
|
|
1760
|
+
slug: row.slug,
|
|
1761
|
+
teams: row.teams,
|
|
1762
|
+
updatedAt: row.updatedAt
|
|
1763
|
+
})
|
|
1764
|
+
);
|
|
1765
|
+
} else {
|
|
1766
|
+
success(
|
|
1767
|
+
[
|
|
1768
|
+
`Updated ${row.slug}`,
|
|
1769
|
+
``,
|
|
1770
|
+
` Slug: ${row.slug}`,
|
|
1771
|
+
` Teams: ${row.teams.join(", ")}`,
|
|
1772
|
+
` Updated at: ${row.updatedAt}`
|
|
1773
|
+
].join("\n")
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
} catch (err) {
|
|
1777
|
+
const { code, message } = wrapProxyError(command, err);
|
|
1778
|
+
if (options.json) {
|
|
1779
|
+
emitJson8(buildErrorEnvelope(command, code, message));
|
|
1780
|
+
} else {
|
|
1781
|
+
error(message);
|
|
1782
|
+
}
|
|
1783
|
+
exit(code);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// src/output/format.ts
|
|
1788
|
+
import { log as log12 } from "@clack/prompts";
|
|
1789
|
+
|
|
1438
1790
|
// src/output/redact.ts
|
|
1439
1791
|
var AWS_KEY_PREFIX_PATTERN = /(?:AKIA|ASIA|AROA|AIDA|ACCA|ANPA|ABIA|AGPA)[A-Z0-9]{12,}/g;
|
|
1440
1792
|
var URL_CREDS_PATTERN = /https?:\/\/[^@\s]+@/g;
|
|
@@ -1492,18 +1844,18 @@ function outputError(ctx, code, message, issues) {
|
|
|
1492
1844
|
);
|
|
1493
1845
|
process.stdout.write(JSON.stringify(envelope) + "\n");
|
|
1494
1846
|
} else {
|
|
1495
|
-
|
|
1847
|
+
log12.error(redactedMessage);
|
|
1496
1848
|
}
|
|
1497
1849
|
}
|
|
1498
1850
|
|
|
1499
1851
|
// src/cli.ts
|
|
1500
|
-
var version = true ? "0.
|
|
1852
|
+
var version = true ? "0.5.0" : "0.0.0";
|
|
1501
1853
|
function handleActionError(command, json, err) {
|
|
1502
1854
|
const ctx = { json, command };
|
|
1503
1855
|
const message = err instanceof Error ? err.message : "unknown error";
|
|
1504
1856
|
const code = err instanceof CliError ? err.exitCode : EXIT_USAGE;
|
|
1505
1857
|
outputError(ctx, code, message);
|
|
1506
|
-
exitWithCode(code
|
|
1858
|
+
exitWithCode(code);
|
|
1507
1859
|
}
|
|
1508
1860
|
function findFirstPositional(args) {
|
|
1509
1861
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -1515,7 +1867,75 @@ function findFirstPositional(args) {
|
|
|
1515
1867
|
function run(argv = process.argv) {
|
|
1516
1868
|
const args = argv.slice(2);
|
|
1517
1869
|
const firstPosIdx = findFirstPositional(args);
|
|
1518
|
-
const
|
|
1870
|
+
const namespace = firstPosIdx >= 0 ? args[firstPosIdx] : void 0;
|
|
1871
|
+
const isStatic = namespace === "static";
|
|
1872
|
+
const isSites = namespace === "sites";
|
|
1873
|
+
if (isSites) {
|
|
1874
|
+
const sitesArgs = [
|
|
1875
|
+
...args.slice(0, firstPosIdx),
|
|
1876
|
+
...args.slice(firstPosIdx + 1)
|
|
1877
|
+
];
|
|
1878
|
+
const sitesCli = cac("universe sites");
|
|
1879
|
+
sitesCli.command("register <slug>", "Register a new static site (staff only)").option("--json", "Output as JSON").option(
|
|
1880
|
+
"--team <name>",
|
|
1881
|
+
"GitHub team slug (repeatable, or comma-separated). Defaults to staff."
|
|
1882
|
+
).action(
|
|
1883
|
+
async (slug, flags) => {
|
|
1884
|
+
try {
|
|
1885
|
+
await register({
|
|
1886
|
+
json: flags.json ?? false,
|
|
1887
|
+
slug,
|
|
1888
|
+
team: flags.team
|
|
1889
|
+
});
|
|
1890
|
+
} catch (err) {
|
|
1891
|
+
handleActionError("sites register", flags.json ?? false, err);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
);
|
|
1895
|
+
sitesCli.command("ls", "List sites in the registry").option("--json", "Output as JSON").option(
|
|
1896
|
+
"--mine",
|
|
1897
|
+
"Filter to sites your GitHub identity is authorized for"
|
|
1898
|
+
).action(async (flags) => {
|
|
1899
|
+
try {
|
|
1900
|
+
await ls2({
|
|
1901
|
+
json: flags.json ?? false,
|
|
1902
|
+
mine: flags.mine ?? false
|
|
1903
|
+
});
|
|
1904
|
+
} catch (err) {
|
|
1905
|
+
handleActionError("sites ls", flags.json ?? false, err);
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
sitesCli.command(
|
|
1909
|
+
"update <slug>",
|
|
1910
|
+
"Replace the teams list for an existing site (staff only)"
|
|
1911
|
+
).option("--json", "Output as JSON").option(
|
|
1912
|
+
"--team <name>",
|
|
1913
|
+
"GitHub team slug (repeatable, or comma-separated). Required."
|
|
1914
|
+
).action(
|
|
1915
|
+
async (slug, flags) => {
|
|
1916
|
+
try {
|
|
1917
|
+
await update({
|
|
1918
|
+
json: flags.json ?? false,
|
|
1919
|
+
slug,
|
|
1920
|
+
team: flags.team
|
|
1921
|
+
});
|
|
1922
|
+
} catch (err) {
|
|
1923
|
+
handleActionError("sites update", flags.json ?? false, err);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
);
|
|
1927
|
+
sitesCli.command("rm <slug>", "Remove a site from the registry (staff only)").option("--json", "Output as JSON").action(async (slug, flags) => {
|
|
1928
|
+
try {
|
|
1929
|
+
await rm2({ json: flags.json ?? false, slug });
|
|
1930
|
+
} catch (err) {
|
|
1931
|
+
handleActionError("sites rm", flags.json ?? false, err);
|
|
1932
|
+
}
|
|
1933
|
+
});
|
|
1934
|
+
sitesCli.help();
|
|
1935
|
+
sitesCli.version(version);
|
|
1936
|
+
sitesCli.parse(["node", "universe-sites", ...sitesArgs]);
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1519
1939
|
if (isStatic) {
|
|
1520
1940
|
const staticArgs = [
|
|
1521
1941
|
...args.slice(0, firstPosIdx),
|
|
@@ -1598,6 +2018,7 @@ function run(argv = process.argv) {
|
|
|
1598
2018
|
}
|
|
1599
2019
|
});
|
|
1600
2020
|
cli.command("static <subcommand>", "Static site deployment commands");
|
|
2021
|
+
cli.command("sites <subcommand>", "Static site registry commands");
|
|
1601
2022
|
cli.help();
|
|
1602
2023
|
cli.version(version);
|
|
1603
2024
|
cli.parse(argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freecodecamp/universe-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "vitest run",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"packageManager": "pnpm@10.33.0",
|
|
46
46
|
"engines": {
|
|
47
|
-
"node": ">=
|
|
47
|
+
"node": ">=24.0.0"
|
|
48
48
|
},
|
|
49
49
|
"description": "Static site deployment CLI for the freeCodeCamp Universe platform",
|
|
50
50
|
"homepage": "https://github.com/freeCodeCamp-Universe/universe-cli#readme",
|