@freecodecamp/universe-cli 0.5.1 → 0.6.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 +3 -43
- package/dist/index.cjs +844 -106
- package/dist/index.js +180 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -440,6 +440,13 @@ var ProxyError = class extends CliError {
|
|
|
440
440
|
this.exitCode = mapExitCode(status);
|
|
441
441
|
}
|
|
442
442
|
};
|
|
443
|
+
var AliasDriftError = class extends ProxyError {
|
|
444
|
+
current;
|
|
445
|
+
constructor(message, current) {
|
|
446
|
+
super(409, "alias_drift", message);
|
|
447
|
+
this.current = current;
|
|
448
|
+
}
|
|
449
|
+
};
|
|
443
450
|
function mapExitCode(status) {
|
|
444
451
|
if (status === 401 || status === 403) return EXIT_CREDENTIALS;
|
|
445
452
|
if (status === 422 || status === 0 || status >= 500) return EXIT_STORAGE;
|
|
@@ -475,9 +482,11 @@ async function readErrorEnvelope(response) {
|
|
|
475
482
|
};
|
|
476
483
|
}
|
|
477
484
|
if (isProxyErrorEnvelope(raw) && raw.error) {
|
|
485
|
+
const current = typeof raw.current === "string" ? raw.current : void 0;
|
|
478
486
|
return {
|
|
479
487
|
code: raw.error.code ?? `http_${status}`,
|
|
480
|
-
message: raw.error.message ?? response.statusText ?? "request failed"
|
|
488
|
+
message: raw.error.message ?? response.statusText ?? "request failed",
|
|
489
|
+
...current === void 0 ? {} : { current }
|
|
481
490
|
};
|
|
482
491
|
}
|
|
483
492
|
return {
|
|
@@ -485,6 +494,12 @@ async function readErrorEnvelope(response) {
|
|
|
485
494
|
message: response.statusText || "request failed"
|
|
486
495
|
};
|
|
487
496
|
}
|
|
497
|
+
function throwProxyError(status, env) {
|
|
498
|
+
if (status === 409 && env.code === "alias_drift") {
|
|
499
|
+
throw new AliasDriftError(env.message, env.current ?? "");
|
|
500
|
+
}
|
|
501
|
+
throw new ProxyError(status, env.code, env.message);
|
|
502
|
+
}
|
|
488
503
|
function stripTrailingSlash(url) {
|
|
489
504
|
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
490
505
|
}
|
|
@@ -505,7 +520,7 @@ function createProxyClient(cfg) {
|
|
|
505
520
|
}
|
|
506
521
|
if (!response.ok) {
|
|
507
522
|
const env = await readErrorEnvelope(response);
|
|
508
|
-
|
|
523
|
+
throwProxyError(response.status, env);
|
|
509
524
|
}
|
|
510
525
|
if (response.status === 204) {
|
|
511
526
|
return void 0;
|
|
@@ -569,18 +584,55 @@ function createProxyClient(cfg) {
|
|
|
569
584
|
}
|
|
570
585
|
});
|
|
571
586
|
},
|
|
587
|
+
async getAlias(req) {
|
|
588
|
+
const url = `${base}/api/site/${encodeURIComponent(req.site)}/alias/${encodeURIComponent(req.mode)}`;
|
|
589
|
+
let response;
|
|
590
|
+
try {
|
|
591
|
+
response = await fetchImpl(url, {
|
|
592
|
+
method: "GET",
|
|
593
|
+
headers: {
|
|
594
|
+
Authorization: await userBearer(),
|
|
595
|
+
Accept: "application/json"
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
} catch (err) {
|
|
599
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
600
|
+
throw new ProxyError(
|
|
601
|
+
0,
|
|
602
|
+
"network_error",
|
|
603
|
+
`proxy unreachable: ${message}`
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
if (response.status === 404) return null;
|
|
607
|
+
if (!response.ok) {
|
|
608
|
+
const env = await readErrorEnvelope(response);
|
|
609
|
+
throwProxyError(response.status, env);
|
|
610
|
+
}
|
|
611
|
+
return await response.json();
|
|
612
|
+
},
|
|
572
613
|
async sitePromote(req) {
|
|
573
614
|
const url = `${base}/api/site/${encodeURIComponent(req.site)}/promote`;
|
|
615
|
+
const headers = {
|
|
616
|
+
Authorization: await userBearer(),
|
|
617
|
+
Accept: "application/json"
|
|
618
|
+
};
|
|
619
|
+
const body = {};
|
|
620
|
+
if (req.deployId !== void 0) body.deployId = req.deployId;
|
|
621
|
+
if (req.expectedCurrent !== void 0)
|
|
622
|
+
body.expectedCurrent = req.expectedCurrent;
|
|
623
|
+
const hasBody = Object.keys(body).length > 0;
|
|
624
|
+
if (hasBody) headers["Content-Type"] = "application/json";
|
|
574
625
|
return call(url, {
|
|
575
626
|
method: "POST",
|
|
576
|
-
headers
|
|
577
|
-
|
|
578
|
-
Accept: "application/json"
|
|
579
|
-
}
|
|
627
|
+
headers,
|
|
628
|
+
...hasBody ? { body: JSON.stringify(body) } : {}
|
|
580
629
|
});
|
|
581
630
|
},
|
|
582
631
|
async siteRollback(req) {
|
|
583
632
|
const url = `${base}/api/site/${encodeURIComponent(req.site)}/rollback`;
|
|
633
|
+
const body = { to: req.to };
|
|
634
|
+
if (req.expectedCurrent !== void 0)
|
|
635
|
+
body.expectedCurrent = req.expectedCurrent;
|
|
584
636
|
return call(url, {
|
|
585
637
|
method: "POST",
|
|
586
638
|
headers: {
|
|
@@ -588,7 +640,7 @@ function createProxyClient(cfg) {
|
|
|
588
640
|
Accept: "application/json",
|
|
589
641
|
"Content-Type": "application/json"
|
|
590
642
|
},
|
|
591
|
-
body: JSON.stringify(
|
|
643
|
+
body: JSON.stringify(body)
|
|
592
644
|
});
|
|
593
645
|
},
|
|
594
646
|
async registerSite(req) {
|
|
@@ -1225,7 +1277,7 @@ var defaultReadPlatformYaml2 = async (cwd) => {
|
|
|
1225
1277
|
function emitJson4(envelope) {
|
|
1226
1278
|
process.stdout.write(JSON.stringify(envelope) + "\n");
|
|
1227
1279
|
}
|
|
1228
|
-
var DEPLOY_ID_RE = /^(\d{8})-(\d{6})-(
|
|
1280
|
+
var DEPLOY_ID_RE = /^(\d{8})-(\d{6})-(\S+)$/;
|
|
1229
1281
|
function parseDeployId(deployId) {
|
|
1230
1282
|
const m = DEPLOY_ID_RE.exec(deployId);
|
|
1231
1283
|
if (!m) return { deployId, timestamp: null, sha: null };
|
|
@@ -1327,7 +1379,12 @@ async function ls(options, deps = {}) {
|
|
|
1327
1379
|
// src/commands/promote.ts
|
|
1328
1380
|
import { readFile as readFile4 } from "fs/promises";
|
|
1329
1381
|
import { resolve as resolve4 } from "path";
|
|
1330
|
-
import { log as log5 } from "@clack/prompts";
|
|
1382
|
+
import { confirm, isCancel, log as log5 } from "@clack/prompts";
|
|
1383
|
+
var defaultPromptConfirm = async (msg) => {
|
|
1384
|
+
const r = await confirm({ message: msg, initialValue: false });
|
|
1385
|
+
if (isCancel(r)) return false;
|
|
1386
|
+
return r === true;
|
|
1387
|
+
};
|
|
1331
1388
|
var defaultReadPlatformYaml3 = async (cwd) => {
|
|
1332
1389
|
return readFile4(resolve4(cwd, "platform.yaml"), "utf-8");
|
|
1333
1390
|
};
|
|
@@ -1359,6 +1416,7 @@ async function promote(options, deps = {}) {
|
|
|
1359
1416
|
const success = deps.logSuccess ?? ((s) => log5.success(s));
|
|
1360
1417
|
const error = deps.logError ?? ((s) => log5.error(s));
|
|
1361
1418
|
const exit = deps.exit ?? exitWithCode;
|
|
1419
|
+
const promptConfirm = deps.promptConfirm ?? defaultPromptConfirm;
|
|
1362
1420
|
try {
|
|
1363
1421
|
const identity = await resolveId({ env });
|
|
1364
1422
|
if (!identity) {
|
|
@@ -1374,12 +1432,75 @@ async function promote(options, deps = {}) {
|
|
|
1374
1432
|
});
|
|
1375
1433
|
let result;
|
|
1376
1434
|
if (options.from) {
|
|
1377
|
-
|
|
1435
|
+
const prod = await client.getAlias({
|
|
1378
1436
|
site: config.site,
|
|
1379
|
-
|
|
1437
|
+
mode: "production"
|
|
1380
1438
|
});
|
|
1439
|
+
const initialExpected = prod?.deployId ?? "";
|
|
1440
|
+
try {
|
|
1441
|
+
result = await client.siteRollback({
|
|
1442
|
+
site: config.site,
|
|
1443
|
+
to: options.from,
|
|
1444
|
+
expectedCurrent: initialExpected
|
|
1445
|
+
});
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
if (!(err instanceof AliasDriftError)) throw err;
|
|
1448
|
+
if (options.json) throw err;
|
|
1449
|
+
error(
|
|
1450
|
+
`drift: production moved to ${err.current}, expected ${initialExpected}`
|
|
1451
|
+
);
|
|
1452
|
+
const ok = await promptConfirm(
|
|
1453
|
+
`Retry promote --from with expectedCurrent='${err.current}'?`
|
|
1454
|
+
);
|
|
1455
|
+
if (!ok) throw err;
|
|
1456
|
+
result = await client.siteRollback({
|
|
1457
|
+
site: config.site,
|
|
1458
|
+
to: options.from,
|
|
1459
|
+
expectedCurrent: err.current
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1381
1462
|
} else {
|
|
1382
|
-
|
|
1463
|
+
const preview = await client.getAlias({
|
|
1464
|
+
site: config.site,
|
|
1465
|
+
mode: "preview"
|
|
1466
|
+
});
|
|
1467
|
+
if (preview === null) {
|
|
1468
|
+
throw new ConfigError(
|
|
1469
|
+
"no preview alias to promote \u2014 run `universe static deploy` first"
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
const prod = await client.getAlias({
|
|
1473
|
+
site: config.site,
|
|
1474
|
+
mode: "production"
|
|
1475
|
+
});
|
|
1476
|
+
if (!options.json) {
|
|
1477
|
+
success(
|
|
1478
|
+
`Promoting ${preview.deployId} \u2192 ${prod?.deployId ?? "<none>"}`
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
const initialExpected = prod?.deployId ?? "";
|
|
1482
|
+
try {
|
|
1483
|
+
result = await client.sitePromote({
|
|
1484
|
+
site: config.site,
|
|
1485
|
+
deployId: preview.deployId,
|
|
1486
|
+
expectedCurrent: initialExpected
|
|
1487
|
+
});
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
if (!(err instanceof AliasDriftError)) throw err;
|
|
1490
|
+
if (options.json) throw err;
|
|
1491
|
+
error(
|
|
1492
|
+
`drift: production moved to ${err.current}, expected ${initialExpected}`
|
|
1493
|
+
);
|
|
1494
|
+
const ok = await promptConfirm(
|
|
1495
|
+
`Retry promote with expectedCurrent='${err.current}'?`
|
|
1496
|
+
);
|
|
1497
|
+
if (!ok) throw err;
|
|
1498
|
+
result = await client.sitePromote({
|
|
1499
|
+
site: config.site,
|
|
1500
|
+
deployId: preview.deployId,
|
|
1501
|
+
expectedCurrent: err.current
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1383
1504
|
}
|
|
1384
1505
|
if (options.json) {
|
|
1385
1506
|
emitJson5(
|
|
@@ -1406,7 +1527,12 @@ async function promote(options, deps = {}) {
|
|
|
1406
1527
|
} catch (err) {
|
|
1407
1528
|
const { code, message } = wrapProxyError("promote", err);
|
|
1408
1529
|
if (options.json) {
|
|
1409
|
-
|
|
1530
|
+
const envelope = buildErrorEnvelope("promote", code, message);
|
|
1531
|
+
if (err instanceof AliasDriftError) {
|
|
1532
|
+
emitJson5({ ...envelope, current: err.current });
|
|
1533
|
+
} else {
|
|
1534
|
+
emitJson5(envelope);
|
|
1535
|
+
}
|
|
1410
1536
|
} else {
|
|
1411
1537
|
error(message);
|
|
1412
1538
|
}
|
|
@@ -1417,7 +1543,12 @@ async function promote(options, deps = {}) {
|
|
|
1417
1543
|
// src/commands/rollback.ts
|
|
1418
1544
|
import { readFile as readFile5 } from "fs/promises";
|
|
1419
1545
|
import { resolve as resolve5 } from "path";
|
|
1420
|
-
import { log as log6 } from "@clack/prompts";
|
|
1546
|
+
import { confirm as confirm2, isCancel as isCancel2, log as log6 } from "@clack/prompts";
|
|
1547
|
+
var defaultPromptConfirm2 = async (msg) => {
|
|
1548
|
+
const r = await confirm2({ message: msg, initialValue: false });
|
|
1549
|
+
if (isCancel2(r)) return false;
|
|
1550
|
+
return r === true;
|
|
1551
|
+
};
|
|
1421
1552
|
var defaultReadPlatformYaml4 = async (cwd) => {
|
|
1422
1553
|
return readFile5(resolve5(cwd, "platform.yaml"), "utf-8");
|
|
1423
1554
|
};
|
|
@@ -1449,6 +1580,7 @@ async function rollback(options, deps = {}) {
|
|
|
1449
1580
|
const success = deps.logSuccess ?? ((s) => log6.success(s));
|
|
1450
1581
|
const error = deps.logError ?? ((s) => log6.error(s));
|
|
1451
1582
|
const exit = deps.exit ?? exitWithCode;
|
|
1583
|
+
const promptConfirm = deps.promptConfirm ?? defaultPromptConfirm2;
|
|
1452
1584
|
try {
|
|
1453
1585
|
if (!options.to || options.to.trim().length === 0) {
|
|
1454
1586
|
throw new UsageError(
|
|
@@ -1467,10 +1599,35 @@ async function rollback(options, deps = {}) {
|
|
|
1467
1599
|
baseUrl,
|
|
1468
1600
|
getAuthToken: () => identity.token
|
|
1469
1601
|
});
|
|
1470
|
-
const
|
|
1602
|
+
const to = options.to.trim();
|
|
1603
|
+
const prod = await client.getAlias({
|
|
1471
1604
|
site: config.site,
|
|
1472
|
-
|
|
1605
|
+
mode: "production"
|
|
1473
1606
|
});
|
|
1607
|
+
const initialExpected = prod?.deployId ?? "";
|
|
1608
|
+
let result;
|
|
1609
|
+
try {
|
|
1610
|
+
result = await client.siteRollback({
|
|
1611
|
+
site: config.site,
|
|
1612
|
+
to,
|
|
1613
|
+
expectedCurrent: initialExpected
|
|
1614
|
+
});
|
|
1615
|
+
} catch (err) {
|
|
1616
|
+
if (!(err instanceof AliasDriftError)) throw err;
|
|
1617
|
+
if (options.json) throw err;
|
|
1618
|
+
error(
|
|
1619
|
+
`drift: production moved to ${err.current}, expected ${initialExpected}`
|
|
1620
|
+
);
|
|
1621
|
+
const ok = await promptConfirm(
|
|
1622
|
+
`Retry rollback with expectedCurrent='${err.current}'?`
|
|
1623
|
+
);
|
|
1624
|
+
if (!ok) throw err;
|
|
1625
|
+
result = await client.siteRollback({
|
|
1626
|
+
site: config.site,
|
|
1627
|
+
to,
|
|
1628
|
+
expectedCurrent: err.current
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1474
1631
|
if (options.json) {
|
|
1475
1632
|
emitJson6(
|
|
1476
1633
|
buildEnvelope("rollback", true, {
|
|
@@ -1494,7 +1651,12 @@ async function rollback(options, deps = {}) {
|
|
|
1494
1651
|
} catch (err) {
|
|
1495
1652
|
const { code, message } = wrapProxyError("rollback", err);
|
|
1496
1653
|
if (options.json) {
|
|
1497
|
-
|
|
1654
|
+
const envelope = buildErrorEnvelope("rollback", code, message);
|
|
1655
|
+
if (err instanceof AliasDriftError) {
|
|
1656
|
+
emitJson6({ ...envelope, current: err.current });
|
|
1657
|
+
} else {
|
|
1658
|
+
emitJson6(envelope);
|
|
1659
|
+
}
|
|
1498
1660
|
} else {
|
|
1499
1661
|
error(message);
|
|
1500
1662
|
}
|
|
@@ -1854,7 +2016,7 @@ function outputError(ctx, code, message, issues) {
|
|
|
1854
2016
|
}
|
|
1855
2017
|
|
|
1856
2018
|
// src/cli.ts
|
|
1857
|
-
var version = true ? "0.
|
|
2019
|
+
var version = true ? "0.6.0" : "0.0.0";
|
|
1858
2020
|
function handleActionError(command, json, err) {
|
|
1859
2021
|
const ctx = { json, command };
|
|
1860
2022
|
const message = err instanceof Error ? err.message : "unknown error";
|