@freecodecamp/universe-cli 0.11.0 → 0.12.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/dist/index.cjs +357 -105
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -15892,19 +15892,23 @@ const LabelsSchema = record(union([
|
|
|
15892
15892
|
literal("service")
|
|
15893
15893
|
]), record(string(), string()));
|
|
15894
15894
|
//#endregion
|
|
15895
|
+
//#region src/commands/create/layer-composition/assets.json
|
|
15896
|
+
var templateVersion = "0.2.0";
|
|
15897
|
+
var templateUrl = "https://github.com/freeCodeCamp-Universe/templates/releases/download/app-templates-v{{version}}/app-templates-{{version}}.tar.gz";
|
|
15898
|
+
//#endregion
|
|
15899
|
+
//#region src/commands/create/layer-composition/assets.ts
|
|
15900
|
+
const defaultTemplateVersion = templateVersion;
|
|
15901
|
+
const resolveTemplateUrl = (version) => templateUrl.replaceAll("{{version}}", version);
|
|
15902
|
+
//#endregion
|
|
15895
15903
|
//#region src/commands/create/layer-composition/template-cache.ts
|
|
15896
|
-
const APP_DIR$
|
|
15897
|
-
const TEMPLATES_DIR = "templates";
|
|
15904
|
+
const APP_DIR$3 = "universe-cli";
|
|
15905
|
+
const TEMPLATES_DIR$1 = "templates";
|
|
15898
15906
|
const cacheBase = () => {
|
|
15899
15907
|
const xdg = process.env["XDG_CACHE_HOME"];
|
|
15900
15908
|
if (xdg && xdg.length > 0) return xdg;
|
|
15901
15909
|
return (0, node_path.join)((0, node_os.homedir)(), ".cache");
|
|
15902
15910
|
};
|
|
15903
|
-
const
|
|
15904
|
-
const filename = url.split("/").at(-1) ?? url;
|
|
15905
|
-
return filename.match(/^templates-(.+)\.tar\.gz$/)?.[1] ?? filename;
|
|
15906
|
-
};
|
|
15907
|
-
const templateCacheDir = (url) => (0, node_path.join)(cacheBase(), APP_DIR$2, TEMPLATES_DIR, versionFromUrl(url));
|
|
15911
|
+
const templateCacheDir = (version) => (0, node_path.join)(cacheBase(), APP_DIR$3, TEMPLATES_DIR$1, version);
|
|
15908
15912
|
//#endregion
|
|
15909
15913
|
//#region src/commands/create/layer-composition/template-provider.ts
|
|
15910
15914
|
const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
|
|
@@ -15919,7 +15923,7 @@ const EXPECTED_LAYER_FILES = [
|
|
|
15919
15923
|
const EXPECTED_ROOT_ENTRIES = ["labels.json", "layers"];
|
|
15920
15924
|
const readEnv = () => ({
|
|
15921
15925
|
UNIVERSE_TEMPLATES_DIR: process.env["UNIVERSE_TEMPLATES_DIR"],
|
|
15922
|
-
|
|
15926
|
+
UNIVERSE_TEMPLATES_VERSION: process.env["UNIVERSE_TEMPLATES_VERSION"]
|
|
15923
15927
|
});
|
|
15924
15928
|
const validateEntries = (dir, expected, actual) => {
|
|
15925
15929
|
const expectedSet = new Set(expected);
|
|
@@ -15996,7 +16000,7 @@ const fetchTarball = async (url, fetchImpl) => {
|
|
|
15996
16000
|
} catch {
|
|
15997
16001
|
throw new ConfigError("Templates not cached. Run `universe templates fetch` or check network.");
|
|
15998
16002
|
}
|
|
15999
|
-
if (response.status === 404) throw new ConfigError(`Template not found at ${url}. Check
|
|
16003
|
+
if (response.status === 404) throw new ConfigError(`Template not found at ${url}. Check UNIVERSE_TEMPLATES_VERSION.`);
|
|
16000
16004
|
if (!response.ok) throw new ConfigError(`Failed to fetch templates from ${url}: HTTP ${String(response.status)}`);
|
|
16001
16005
|
const arrayBuffer = await response.arrayBuffer();
|
|
16002
16006
|
return Buffer.from(arrayBuffer);
|
|
@@ -16045,24 +16049,169 @@ var RemoteTemplateProvider = class {
|
|
|
16045
16049
|
this.cacheBaseOverride = cacheBaseOverride;
|
|
16046
16050
|
this.fetchImpl = fetchImpl;
|
|
16047
16051
|
}
|
|
16048
|
-
resolveCacheDir(
|
|
16049
|
-
if (this.cacheBaseOverride !== void 0) return (0, node_path.join)(this.cacheBaseOverride
|
|
16050
|
-
return templateCacheDir(
|
|
16052
|
+
resolveCacheDir(version) {
|
|
16053
|
+
if (this.cacheBaseOverride !== void 0) return (0, node_path.join)(this.cacheBaseOverride, version);
|
|
16054
|
+
return templateCacheDir(version);
|
|
16051
16055
|
}
|
|
16052
16056
|
async loadLayers(options) {
|
|
16053
|
-
const { UNIVERSE_TEMPLATES_DIR,
|
|
16057
|
+
const { UNIVERSE_TEMPLATES_DIR, UNIVERSE_TEMPLATES_VERSION } = this.env();
|
|
16054
16058
|
if (UNIVERSE_TEMPLATES_DIR !== void 0 && UNIVERSE_TEMPLATES_DIR.length > 0) return loadFromDir(UNIVERSE_TEMPLATES_DIR);
|
|
16055
|
-
|
|
16056
|
-
const
|
|
16059
|
+
const version = UNIVERSE_TEMPLATES_VERSION && UNIVERSE_TEMPLATES_VERSION.length > 0 ? UNIVERSE_TEMPLATES_VERSION : defaultTemplateVersion;
|
|
16060
|
+
const url = resolveTemplateUrl(version);
|
|
16061
|
+
const cacheDir = this.resolveCacheDir(version);
|
|
16057
16062
|
if (options?.forceFetch) await (0, node_fs_promises.rm)(cacheDir, {
|
|
16058
16063
|
force: true,
|
|
16059
16064
|
recursive: true
|
|
16060
16065
|
});
|
|
16061
16066
|
if (await cacheHit(cacheDir)) return loadFromDir(cacheDir);
|
|
16062
|
-
return fetchAndCache(
|
|
16067
|
+
return fetchAndCache(url, cacheDir, this.fetchImpl);
|
|
16063
16068
|
}
|
|
16064
16069
|
};
|
|
16065
16070
|
//#endregion
|
|
16071
|
+
//#region src/lib/version-utils.ts
|
|
16072
|
+
const DEFAULT_TTL_MS = 3600 * 1e3;
|
|
16073
|
+
function ttlMs() {
|
|
16074
|
+
const raw = process.env["UNIVERSE_UPDATE_TTL_MS"];
|
|
16075
|
+
if (raw !== void 0) {
|
|
16076
|
+
const n = Number.parseInt(raw, 10);
|
|
16077
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
16078
|
+
}
|
|
16079
|
+
return DEFAULT_TTL_MS;
|
|
16080
|
+
}
|
|
16081
|
+
function isDisabled() {
|
|
16082
|
+
const v = process.env["UNIVERSE_NO_UPDATE_CHECK"];
|
|
16083
|
+
return v === "1" || v === "true";
|
|
16084
|
+
}
|
|
16085
|
+
function parseCache(raw) {
|
|
16086
|
+
let parsed;
|
|
16087
|
+
try {
|
|
16088
|
+
parsed = JSON.parse(raw);
|
|
16089
|
+
} catch {
|
|
16090
|
+
return null;
|
|
16091
|
+
}
|
|
16092
|
+
if (typeof parsed !== "object" || parsed === null || !("latest" in parsed) || !("lastCheck" in parsed)) return null;
|
|
16093
|
+
const { latest, lastCheck } = parsed;
|
|
16094
|
+
if (typeof latest !== "string" || typeof lastCheck !== "number") return null;
|
|
16095
|
+
return {
|
|
16096
|
+
latest,
|
|
16097
|
+
lastCheck
|
|
16098
|
+
};
|
|
16099
|
+
}
|
|
16100
|
+
function compareVersions(a, b) {
|
|
16101
|
+
const pa = parseVersion(a);
|
|
16102
|
+
const pb = parseVersion(b);
|
|
16103
|
+
if (pa === null || pb === null) return 0;
|
|
16104
|
+
for (let i = 0; i < 3; i += 1) {
|
|
16105
|
+
const ai = pa[i] ?? 0;
|
|
16106
|
+
const bi = pb[i] ?? 0;
|
|
16107
|
+
if (ai < bi) return -1;
|
|
16108
|
+
if (ai > bi) return 1;
|
|
16109
|
+
}
|
|
16110
|
+
return 0;
|
|
16111
|
+
}
|
|
16112
|
+
function parseVersion(s) {
|
|
16113
|
+
const parts = (s.split("-")[0] ?? "").split(".");
|
|
16114
|
+
if (parts.length !== 3) return null;
|
|
16115
|
+
const nums = parts.map((p) => Number.parseInt(p, 10));
|
|
16116
|
+
if (nums.some((n) => Number.isNaN(n))) return null;
|
|
16117
|
+
return [
|
|
16118
|
+
nums[0],
|
|
16119
|
+
nums[1],
|
|
16120
|
+
nums[2]
|
|
16121
|
+
];
|
|
16122
|
+
}
|
|
16123
|
+
function useColor() {
|
|
16124
|
+
if (process.env["NO_COLOR"] && process.env["NO_COLOR"].length > 0) return false;
|
|
16125
|
+
return process.stderr.isTTY === true;
|
|
16126
|
+
}
|
|
16127
|
+
function paint(s, code, color) {
|
|
16128
|
+
if (!color) return s;
|
|
16129
|
+
return `\x1b[${code}m${s}\x1b[0m`;
|
|
16130
|
+
}
|
|
16131
|
+
//#endregion
|
|
16132
|
+
//#region src/lib/template-version-check.ts
|
|
16133
|
+
const APP_DIR$2 = "universe-cli";
|
|
16134
|
+
const TEMPLATES_DIR = "templates";
|
|
16135
|
+
const CACHE_FILE$1 = "template-version-check.json";
|
|
16136
|
+
const GITHUB_LATEST_URL = "https://api.github.com/repos/freeCodeCamp-Universe/templates/releases/latest";
|
|
16137
|
+
const FETCH_TIMEOUT_MS$1 = 3e3;
|
|
16138
|
+
const TAG_PREFIX = "app-templates-v";
|
|
16139
|
+
function templateCheckCachePath() {
|
|
16140
|
+
return (0, node_path.join)(cacheBase(), APP_DIR$2, TEMPLATES_DIR, CACHE_FILE$1);
|
|
16141
|
+
}
|
|
16142
|
+
async function readTemplateCache() {
|
|
16143
|
+
try {
|
|
16144
|
+
return parseCache(await (0, node_fs_promises.readFile)(templateCheckCachePath(), "utf-8"));
|
|
16145
|
+
} catch {
|
|
16146
|
+
return null;
|
|
16147
|
+
}
|
|
16148
|
+
}
|
|
16149
|
+
async function writeTemplateCache(c) {
|
|
16150
|
+
const path = templateCheckCachePath();
|
|
16151
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), {
|
|
16152
|
+
recursive: true,
|
|
16153
|
+
mode: 448
|
|
16154
|
+
});
|
|
16155
|
+
await (0, node_fs_promises.writeFile)(path, JSON.stringify(c), { mode: 420 });
|
|
16156
|
+
}
|
|
16157
|
+
async function fetchLatestTemplateVersion() {
|
|
16158
|
+
const ctl = new AbortController();
|
|
16159
|
+
const timer = setTimeout(() => ctl.abort(), FETCH_TIMEOUT_MS$1);
|
|
16160
|
+
try {
|
|
16161
|
+
const res = await fetch(GITHUB_LATEST_URL, {
|
|
16162
|
+
signal: ctl.signal,
|
|
16163
|
+
headers: { accept: "application/vnd.github+json" }
|
|
16164
|
+
});
|
|
16165
|
+
if (!res.ok) return null;
|
|
16166
|
+
const body = await res.json();
|
|
16167
|
+
if (typeof body.tag_name !== "string") return null;
|
|
16168
|
+
if (!body.tag_name.startsWith(TAG_PREFIX)) return null;
|
|
16169
|
+
return body.tag_name.slice(15);
|
|
16170
|
+
} catch {
|
|
16171
|
+
return null;
|
|
16172
|
+
} finally {
|
|
16173
|
+
clearTimeout(timer);
|
|
16174
|
+
}
|
|
16175
|
+
}
|
|
16176
|
+
async function checkTemplateVersion(currentVersion, now = Date.now()) {
|
|
16177
|
+
if (isDisabled()) return null;
|
|
16178
|
+
const cache = await readTemplateCache();
|
|
16179
|
+
if (cache !== null && now - cache.lastCheck < ttlMs()) {
|
|
16180
|
+
if (compareVersions(currentVersion, cache.latest) < 0) return {
|
|
16181
|
+
current: currentVersion,
|
|
16182
|
+
latest: cache.latest
|
|
16183
|
+
};
|
|
16184
|
+
return null;
|
|
16185
|
+
}
|
|
16186
|
+
const latest = await fetchLatestTemplateVersion();
|
|
16187
|
+
if (latest === null) return null;
|
|
16188
|
+
try {
|
|
16189
|
+
await writeTemplateCache({
|
|
16190
|
+
latest,
|
|
16191
|
+
lastCheck: now
|
|
16192
|
+
});
|
|
16193
|
+
} catch {}
|
|
16194
|
+
if (compareVersions(currentVersion, latest) < 0) return {
|
|
16195
|
+
current: currentVersion,
|
|
16196
|
+
latest
|
|
16197
|
+
};
|
|
16198
|
+
return null;
|
|
16199
|
+
}
|
|
16200
|
+
function formatTemplateNotice(n, color = useColor()) {
|
|
16201
|
+
const dim = (s) => paint(s, "2", color);
|
|
16202
|
+
const yellow = (s) => paint(s, "33", color);
|
|
16203
|
+
const cyan = (s) => paint(s, "36", color);
|
|
16204
|
+
const bar = dim("│");
|
|
16205
|
+
return [
|
|
16206
|
+
"",
|
|
16207
|
+
bar,
|
|
16208
|
+
`${yellow("▲")} Newer templates available: ${dim(n.current)} → ${cyan(n.latest)}`,
|
|
16209
|
+
`${bar} Set ${cyan(`UNIVERSE_TEMPLATES_VERSION=${n.latest}`)} to use them.`,
|
|
16210
|
+
dim("└"),
|
|
16211
|
+
""
|
|
16212
|
+
].join("\n");
|
|
16213
|
+
}
|
|
16214
|
+
//#endregion
|
|
16066
16215
|
//#region src/commands/create/index.ts
|
|
16067
16216
|
const defaultFilesystemWriter = new LocalProjectWriter();
|
|
16068
16217
|
const create$1 = async (options, deps = {}) => {
|
|
@@ -16077,6 +16226,15 @@ const create$1 = async (options, deps = {}) => {
|
|
|
16077
16226
|
const platformManifestGenerator = deps.platformManifestGenerator ?? new PlatformManifestService();
|
|
16078
16227
|
const repoInitialiser = deps.repoInitialiser ?? new GitRepoInitialiser();
|
|
16079
16228
|
try {
|
|
16229
|
+
const templatesDir = process.env["UNIVERSE_TEMPLATES_DIR"];
|
|
16230
|
+
if (!(templatesDir && templatesDir.length > 0)) {
|
|
16231
|
+
const envVersion = process.env["UNIVERSE_TEMPLATES_VERSION"];
|
|
16232
|
+
const effectiveVersion = envVersion && envVersion.length > 0 ? envVersion : defaultTemplateVersion;
|
|
16233
|
+
try {
|
|
16234
|
+
const notice = await checkTemplateVersion(effectiveVersion);
|
|
16235
|
+
if (notice) process.stderr.write(formatTemplateNotice(notice));
|
|
16236
|
+
} catch {}
|
|
16237
|
+
}
|
|
16080
16238
|
const templateProvider = deps.templateProvider ?? new RemoteTemplateProvider();
|
|
16081
16239
|
const { labels, registry } = await templateProvider.loadLayers({ forceFetch: options.forceFetch });
|
|
16082
16240
|
const interactive = (deps.isTTY ?? Boolean(process.stdin.isTTY)) && !options.yes && !options.json;
|
|
@@ -16566,6 +16724,17 @@ function suggest(target, candidates, threshold = 2) {
|
|
|
16566
16724
|
}
|
|
16567
16725
|
return bestD <= threshold ? best : null;
|
|
16568
16726
|
}
|
|
16727
|
+
const auditRowArraySchema = array(object({
|
|
16728
|
+
id: number(),
|
|
16729
|
+
occurredAt: string(),
|
|
16730
|
+
actor: string(),
|
|
16731
|
+
action: string(),
|
|
16732
|
+
site: string().optional(),
|
|
16733
|
+
deployId: string().optional(),
|
|
16734
|
+
outcome: string(),
|
|
16735
|
+
requestId: string().optional(),
|
|
16736
|
+
detail: record(string(), unknown()).optional()
|
|
16737
|
+
}));
|
|
16569
16738
|
const repoName = string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,99}$/, "must start with a letter or digit, then letters, digits, '.', '_' or '-' (max 100 chars)");
|
|
16570
16739
|
const visibilitySchema = _enum(["public", "private"]);
|
|
16571
16740
|
const repoStatusSchema = _enum([
|
|
@@ -16968,6 +17137,23 @@ function createProxyClient(cfg) {
|
|
|
16968
17137
|
}
|
|
16969
17138
|
}, (raw) => repoRowArraySchema.parse(raw));
|
|
16970
17139
|
},
|
|
17140
|
+
async listAudit(req) {
|
|
17141
|
+
const params = new URLSearchParams();
|
|
17142
|
+
if (req?.site) params.set("site", req.site);
|
|
17143
|
+
if (req?.actor) params.set("actor", req.actor);
|
|
17144
|
+
if (req?.action) params.set("action", req.action);
|
|
17145
|
+
if (req?.since) params.set("since", req.since);
|
|
17146
|
+
if (req?.limit !== void 0) params.set("limit", String(req.limit));
|
|
17147
|
+
if (req?.offset !== void 0) params.set("offset", String(req.offset));
|
|
17148
|
+
const qs = params.toString();
|
|
17149
|
+
return call(`${base}/api/audit${qs ? `?${qs}` : ""}`, {
|
|
17150
|
+
method: "GET",
|
|
17151
|
+
headers: {
|
|
17152
|
+
Authorization: await userBearer(),
|
|
17153
|
+
Accept: "application/json"
|
|
17154
|
+
}
|
|
17155
|
+
}, (raw) => auditRowArraySchema.parse(raw));
|
|
17156
|
+
},
|
|
16971
17157
|
async getRepoRequest(id) {
|
|
16972
17158
|
return call(`${base}/api/repo/${encodeURIComponent(id)}`, {
|
|
16973
17159
|
method: "GET",
|
|
@@ -17869,24 +18055,26 @@ async function readSiteFromYaml(cwd, read) {
|
|
|
17869
18055
|
if (!r.ok) throw new ConfigError(r.error);
|
|
17870
18056
|
return r.value.site;
|
|
17871
18057
|
}
|
|
17872
|
-
function formatTable$
|
|
18058
|
+
function formatTable$2(deploys) {
|
|
17873
18059
|
const header = [
|
|
17874
18060
|
"DEPLOY ID",
|
|
17875
18061
|
"TIMESTAMP",
|
|
17876
18062
|
"SHA",
|
|
17877
|
-
"STATE"
|
|
18063
|
+
"STATE",
|
|
18064
|
+
"ACTOR"
|
|
17878
18065
|
];
|
|
17879
18066
|
const rows = deploys.map((d) => [
|
|
17880
18067
|
d.deployId,
|
|
17881
18068
|
d.timestamp ? d.timestamp.replace("T", " ").replace("Z", "") : "—",
|
|
17882
18069
|
d.sha ?? "—",
|
|
17883
|
-
d.state ?? "—"
|
|
18070
|
+
d.state ?? "—",
|
|
18071
|
+
d.actor ?? "—"
|
|
17884
18072
|
]);
|
|
17885
18073
|
const widths = header.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
17886
18074
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
|
|
17887
18075
|
return [fmt(header), ...rows.map(fmt)].join("\n");
|
|
17888
18076
|
}
|
|
17889
|
-
async function ls$
|
|
18077
|
+
async function ls$3(options, deps = {}) {
|
|
17890
18078
|
const cwd = deps.cwd ?? process.cwd();
|
|
17891
18079
|
const env = deps.env ?? process.env;
|
|
17892
18080
|
const readYaml = deps.readPlatformYaml ?? defaultReadPlatformYaml$2;
|
|
@@ -17907,7 +18095,8 @@ async function ls$2(options, deps = {}) {
|
|
|
17907
18095
|
getAuthToken: () => identity.token,
|
|
17908
18096
|
timeoutMs: parseFetchTimeoutMs(env)
|
|
17909
18097
|
});
|
|
17910
|
-
const
|
|
18098
|
+
const sorted = [...await client.siteDeploys({ site })].sort((a, b) => b.deployId.localeCompare(a.deployId));
|
|
18099
|
+
const parsed = sorted.map((d) => parseDeployId(d.deployId));
|
|
17911
18100
|
let previewId = null;
|
|
17912
18101
|
let productionId = null;
|
|
17913
18102
|
if (parsed.length > 0) {
|
|
@@ -17921,9 +18110,10 @@ async function ls$2(options, deps = {}) {
|
|
|
17921
18110
|
previewId = preview?.deployId ?? null;
|
|
17922
18111
|
productionId = production?.deployId ?? null;
|
|
17923
18112
|
}
|
|
17924
|
-
const deploys = parsed.map((d) => ({
|
|
18113
|
+
const deploys = parsed.map((d, i) => ({
|
|
17925
18114
|
...d,
|
|
17926
|
-
state: deployState(d.deployId, previewId, productionId)
|
|
18115
|
+
state: deployState(d.deployId, previewId, productionId),
|
|
18116
|
+
actor: sorted[i]?.actor
|
|
17927
18117
|
}));
|
|
17928
18118
|
if (options.json) {
|
|
17929
18119
|
emitJson(buildEnvelope("ls", true, {
|
|
@@ -17941,7 +18131,7 @@ async function ls$2(options, deps = {}) {
|
|
|
17941
18131
|
info(`(no deploys for ${site})`);
|
|
17942
18132
|
return;
|
|
17943
18133
|
}
|
|
17944
|
-
success(formatTable$
|
|
18134
|
+
success(formatTable$2(deploys));
|
|
17945
18135
|
} catch (err) {
|
|
17946
18136
|
const { code, message } = wrapProxyError("ls", err);
|
|
17947
18137
|
outputError({
|
|
@@ -18241,7 +18431,7 @@ function parseTeamsFlag(raw) {
|
|
|
18241
18431
|
* Resolve identity + construct a proxy client. Throws CredentialError
|
|
18242
18432
|
* if no identity is available — caller wraps via wrapProxyError.
|
|
18243
18433
|
*/
|
|
18244
|
-
async function setupClient$
|
|
18434
|
+
async function setupClient$2(deps) {
|
|
18245
18435
|
const env = deps.env ?? process.env;
|
|
18246
18436
|
const resolveId = deps.resolveIdentity ?? resolveIdentity;
|
|
18247
18437
|
const mkClient = deps.createProxyClient ?? createProxyClient;
|
|
@@ -18258,7 +18448,7 @@ async function setupClient$1(deps) {
|
|
|
18258
18448
|
}
|
|
18259
18449
|
//#endregion
|
|
18260
18450
|
//#region src/commands/sites/ls.ts
|
|
18261
|
-
function formatTable(rows) {
|
|
18451
|
+
function formatTable$1(rows) {
|
|
18262
18452
|
if (rows.length === 0) return "No registered sites.";
|
|
18263
18453
|
const headers = [
|
|
18264
18454
|
"SLUG",
|
|
@@ -18276,13 +18466,13 @@ function formatTable(rows) {
|
|
|
18276
18466
|
const fmt = (row) => row.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
|
|
18277
18467
|
return [fmt(headers), ...cells.map(fmt)].join("\n");
|
|
18278
18468
|
}
|
|
18279
|
-
async function ls$
|
|
18469
|
+
async function ls$2(options, deps = {}) {
|
|
18280
18470
|
const command = "sites ls";
|
|
18281
18471
|
const success = deps.logSuccess ?? ((s) => R.message(s));
|
|
18282
18472
|
const error = deps.logError ?? ((s) => R.error(s));
|
|
18283
18473
|
const exit = deps.exit ?? exitWithCode;
|
|
18284
18474
|
try {
|
|
18285
|
-
const { client, identitySource } = await setupClient$
|
|
18475
|
+
const { client, identitySource } = await setupClient$2(deps);
|
|
18286
18476
|
let rows = await client.listSites();
|
|
18287
18477
|
let scope = "all";
|
|
18288
18478
|
if (options.mine) {
|
|
@@ -18297,7 +18487,7 @@ async function ls$1(options, deps = {}) {
|
|
|
18297
18487
|
sites: rows,
|
|
18298
18488
|
identitySource
|
|
18299
18489
|
}));
|
|
18300
|
-
else success(formatTable(rows));
|
|
18490
|
+
else success(formatTable$1(rows));
|
|
18301
18491
|
} catch (err) {
|
|
18302
18492
|
const { code, message } = wrapProxyError(command, err);
|
|
18303
18493
|
outputError({
|
|
@@ -18317,7 +18507,7 @@ async function register(options, deps = {}) {
|
|
|
18317
18507
|
try {
|
|
18318
18508
|
if (!options.slug || options.slug.trim().length === 0) throw new UsageError("slug is required (positional argument)");
|
|
18319
18509
|
const teams = parseTeamsFlag(options.team);
|
|
18320
|
-
const { client, identitySource } = await setupClient$
|
|
18510
|
+
const { client, identitySource } = await setupClient$2(deps);
|
|
18321
18511
|
const row = await client.registerSite({
|
|
18322
18512
|
slug: options.slug,
|
|
18323
18513
|
teams: teams.length > 0 ? teams : void 0
|
|
@@ -18355,7 +18545,7 @@ async function rm$1(options, deps = {}) {
|
|
|
18355
18545
|
const exit = deps.exit ?? exitWithCode;
|
|
18356
18546
|
try {
|
|
18357
18547
|
if (!options.slug || options.slug.trim().length === 0) throw new UsageError("slug is required (positional argument)");
|
|
18358
|
-
const { client, identitySource } = await setupClient$
|
|
18548
|
+
const { client, identitySource } = await setupClient$2(deps);
|
|
18359
18549
|
await client.deleteSite({ slug: options.slug });
|
|
18360
18550
|
if (options.json) emitJson(buildEnvelope(command, true, {
|
|
18361
18551
|
slug: options.slug,
|
|
@@ -18388,7 +18578,7 @@ async function update(options, deps = {}) {
|
|
|
18388
18578
|
if (!options.slug || options.slug.trim().length === 0) throw new UsageError("slug is required (positional argument)");
|
|
18389
18579
|
const teams = parseTeamsFlag(options.team);
|
|
18390
18580
|
if (teams.length === 0) throw new UsageError("--team is required with at least one slug; use `sites rm` to remove a site");
|
|
18391
|
-
const { client, identitySource } = await setupClient$
|
|
18581
|
+
const { client, identitySource } = await setupClient$2(deps);
|
|
18392
18582
|
const row = await client.updateSite({
|
|
18393
18583
|
slug: options.slug,
|
|
18394
18584
|
teams
|
|
@@ -18427,7 +18617,7 @@ const defaultRepoPrompts = {
|
|
|
18427
18617
|
* Resolve identity + construct a proxy client. Throws CredentialError
|
|
18428
18618
|
* when no identity is available — the caller wraps via wrapProxyError.
|
|
18429
18619
|
*/
|
|
18430
|
-
async function setupClient(deps) {
|
|
18620
|
+
async function setupClient$1(deps) {
|
|
18431
18621
|
const env = deps.env ?? process.env;
|
|
18432
18622
|
const resolveId = deps.resolveIdentity ?? resolveIdentity;
|
|
18433
18623
|
const mkClient = deps.createProxyClient ?? createProxyClient;
|
|
@@ -18447,6 +18637,26 @@ async function setupClient(deps) {
|
|
|
18447
18637
|
* Render repo-request rows as an aligned text table. Returns `emptyMsg`
|
|
18448
18638
|
* when there are no rows (the caller passes a status-specific phrase).
|
|
18449
18639
|
*/
|
|
18640
|
+
function humanizeDuration(ms) {
|
|
18641
|
+
const s = Math.floor(ms / 1e3);
|
|
18642
|
+
if (s < 60) return `${s}s`;
|
|
18643
|
+
const m = Math.floor(s / 60);
|
|
18644
|
+
if (m < 60) return `${m}m`;
|
|
18645
|
+
const h = Math.floor(m / 60);
|
|
18646
|
+
if (h < 24) return `${h}h${m % 60}m`;
|
|
18647
|
+
return `${Math.floor(h / 24)}d${h % 24}h`;
|
|
18648
|
+
}
|
|
18649
|
+
const RESOLVED_STATUSES = new Set([
|
|
18650
|
+
"active",
|
|
18651
|
+
"rejected",
|
|
18652
|
+
"failed"
|
|
18653
|
+
]);
|
|
18654
|
+
function resolveLatency(r) {
|
|
18655
|
+
if (!RESOLVED_STATUSES.has(r.status)) return "";
|
|
18656
|
+
const ms = Date.parse(r.updatedAt) - Date.parse(r.createdAt);
|
|
18657
|
+
if (!Number.isFinite(ms) || ms < 0) return "";
|
|
18658
|
+
return humanizeDuration(ms);
|
|
18659
|
+
}
|
|
18450
18660
|
function formatRepoTable(rows, emptyMsg = "No repo requests.") {
|
|
18451
18661
|
if (rows.length === 0) return emptyMsg;
|
|
18452
18662
|
const headers = [
|
|
@@ -18455,7 +18665,9 @@ function formatRepoTable(rows, emptyMsg = "No repo requests.") {
|
|
|
18455
18665
|
"VIS",
|
|
18456
18666
|
"STATUS",
|
|
18457
18667
|
"REQUESTED BY",
|
|
18458
|
-
"REQUESTED AT"
|
|
18668
|
+
"REQUESTED AT",
|
|
18669
|
+
"APPROVER",
|
|
18670
|
+
"LATENCY"
|
|
18459
18671
|
];
|
|
18460
18672
|
const cells = rows.map((r) => [
|
|
18461
18673
|
r.id,
|
|
@@ -18463,7 +18675,9 @@ function formatRepoTable(rows, emptyMsg = "No repo requests.") {
|
|
|
18463
18675
|
r.visibility,
|
|
18464
18676
|
r.status,
|
|
18465
18677
|
r.requestedBy,
|
|
18466
|
-
r.createdAt
|
|
18678
|
+
r.createdAt,
|
|
18679
|
+
r.approver ?? "",
|
|
18680
|
+
resolveLatency(r)
|
|
18467
18681
|
]);
|
|
18468
18682
|
const widths = headers.map((h, i) => Math.max(h.length, ...cells.map((row) => row[i]?.length ?? 0)));
|
|
18469
18683
|
const fmt = (row) => row.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
|
|
@@ -18482,7 +18696,7 @@ async function approve(options, deps = {}) {
|
|
|
18482
18696
|
let identitySource;
|
|
18483
18697
|
try {
|
|
18484
18698
|
if (!options.id || options.id.trim().length === 0) throw new UsageError("request id is required (positional argument)");
|
|
18485
|
-
const setup = await setupClient(deps);
|
|
18699
|
+
const setup = await setupClient$1(deps);
|
|
18486
18700
|
const client = setup.client;
|
|
18487
18701
|
identitySource = setup.identitySource;
|
|
18488
18702
|
if (!options.json && !options.yes) {
|
|
@@ -18600,7 +18814,7 @@ async function create(options, deps = {}) {
|
|
|
18600
18814
|
let client;
|
|
18601
18815
|
const ensureClient = async () => {
|
|
18602
18816
|
if (!client) {
|
|
18603
|
-
const setup = await setupClient(deps);
|
|
18817
|
+
const setup = await setupClient$1(deps);
|
|
18604
18818
|
client = setup.client;
|
|
18605
18819
|
identitySource = setup.identitySource;
|
|
18606
18820
|
}
|
|
@@ -18702,7 +18916,7 @@ async function create(options, deps = {}) {
|
|
|
18702
18916
|
//#region src/commands/repo/ls.ts
|
|
18703
18917
|
/** Closed set accepted by `--status`: the row statuses plus `all`. */
|
|
18704
18918
|
const LS_STATUSES = [...repoStatusSchema.options, "all"];
|
|
18705
|
-
async function ls(options, deps = {}) {
|
|
18919
|
+
async function ls$1(options, deps = {}) {
|
|
18706
18920
|
const command = "repo ls";
|
|
18707
18921
|
const message = deps.logMessage ?? ((s) => R.message(s));
|
|
18708
18922
|
const error = deps.logError ?? ((s) => R.error(s));
|
|
@@ -18711,7 +18925,7 @@ async function ls(options, deps = {}) {
|
|
|
18711
18925
|
try {
|
|
18712
18926
|
const requestedStatus = options.all ? "all" : options.status;
|
|
18713
18927
|
if (requestedStatus !== void 0 && !LS_STATUSES.includes(requestedStatus)) throw new UsageError(`invalid --status "${requestedStatus}": must be one of ${LS_STATUSES.join(", ")}`);
|
|
18714
|
-
const setup = await setupClient(deps);
|
|
18928
|
+
const setup = await setupClient$1(deps);
|
|
18715
18929
|
const client = setup.client;
|
|
18716
18930
|
identitySource = setup.identitySource;
|
|
18717
18931
|
const rows = await client.listRepoRequests({
|
|
@@ -18753,7 +18967,7 @@ async function reject(options, deps = {}) {
|
|
|
18753
18967
|
let identitySource;
|
|
18754
18968
|
try {
|
|
18755
18969
|
if (!options.id || options.id.trim().length === 0) throw new UsageError("request id is required (positional argument)");
|
|
18756
|
-
const setup = await setupClient(deps);
|
|
18970
|
+
const setup = await setupClient$1(deps);
|
|
18757
18971
|
const client = setup.client;
|
|
18758
18972
|
identitySource = setup.identitySource;
|
|
18759
18973
|
if (!options.json && !options.yes) {
|
|
@@ -18806,7 +19020,7 @@ async function rm(options, deps = {}) {
|
|
|
18806
19020
|
let identitySource;
|
|
18807
19021
|
try {
|
|
18808
19022
|
if (!options.id || options.id.trim().length === 0) throw new UsageError("request id is required (positional argument)");
|
|
18809
|
-
const setup = await setupClient(deps);
|
|
19023
|
+
const setup = await setupClient$1(deps);
|
|
18810
19024
|
const client = setup.client;
|
|
18811
19025
|
identitySource = setup.identitySource;
|
|
18812
19026
|
if (!options.json && !options.yes) {
|
|
@@ -18864,7 +19078,7 @@ async function status(options, deps = {}) {
|
|
|
18864
19078
|
let identitySource;
|
|
18865
19079
|
try {
|
|
18866
19080
|
if (!options.id || options.id.trim().length === 0) throw new UsageError("request id is required (positional argument)");
|
|
18867
|
-
const setup = await setupClient(deps);
|
|
19081
|
+
const setup = await setupClient$1(deps);
|
|
18868
19082
|
const client = setup.client;
|
|
18869
19083
|
identitySource = setup.identitySource;
|
|
18870
19084
|
const row = await client.getRepoRequest(options.id);
|
|
@@ -18888,22 +19102,93 @@ async function status(options, deps = {}) {
|
|
|
18888
19102
|
}
|
|
18889
19103
|
}
|
|
18890
19104
|
//#endregion
|
|
19105
|
+
//#region src/commands/audit/_shared.ts
|
|
19106
|
+
async function setupClient(deps) {
|
|
19107
|
+
const env = deps.env ?? process.env;
|
|
19108
|
+
const resolveId = deps.resolveIdentity ?? resolveIdentity;
|
|
19109
|
+
const mkClient = deps.createProxyClient ?? createProxyClient;
|
|
19110
|
+
const identity = await resolveId({ env });
|
|
19111
|
+
if (!identity) throw new CredentialError("No GitHub identity available. Run `universe login`, set $GITHUB_TOKEN, or install the gh CLI.");
|
|
19112
|
+
return {
|
|
19113
|
+
client: mkClient({
|
|
19114
|
+
baseUrl: env["UNIVERSE_PROXY_URL"] ?? "https://uploads.freecode.camp",
|
|
19115
|
+
getAuthToken: () => identity.token,
|
|
19116
|
+
timeoutMs: parseFetchTimeoutMs(env)
|
|
19117
|
+
}),
|
|
19118
|
+
identitySource: identity.source
|
|
19119
|
+
};
|
|
19120
|
+
}
|
|
19121
|
+
//#endregion
|
|
19122
|
+
//#region src/commands/audit/ls.ts
|
|
19123
|
+
function formatTable(rows) {
|
|
19124
|
+
if (rows.length === 0) return "No audit events.";
|
|
19125
|
+
const headers = [
|
|
19126
|
+
"OCCURRED AT",
|
|
19127
|
+
"ACTOR",
|
|
19128
|
+
"ACTION",
|
|
19129
|
+
"TARGET",
|
|
19130
|
+
"OUTCOME"
|
|
19131
|
+
];
|
|
19132
|
+
const cells = rows.map((r) => [
|
|
19133
|
+
r.occurredAt,
|
|
19134
|
+
r.actor,
|
|
19135
|
+
r.action,
|
|
19136
|
+
r.site || r.deployId || targetFromDetail(r),
|
|
19137
|
+
r.outcome
|
|
19138
|
+
]);
|
|
19139
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...cells.map((row) => row[i]?.length ?? 0)));
|
|
19140
|
+
const fmt = (row) => row.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
|
|
19141
|
+
return [fmt(headers), ...cells.map(fmt)].join("\n");
|
|
19142
|
+
}
|
|
19143
|
+
function targetFromDetail(r) {
|
|
19144
|
+
const name = r.detail?.["name"];
|
|
19145
|
+
return typeof name === "string" ? name : "";
|
|
19146
|
+
}
|
|
19147
|
+
async function ls(options, deps = {}) {
|
|
19148
|
+
const command = "audit ls";
|
|
19149
|
+
const message = deps.logMessage ?? ((s) => R.message(s));
|
|
19150
|
+
const error = deps.logError ?? ((s) => R.error(s));
|
|
19151
|
+
const exit = deps.exit ?? exitWithCode;
|
|
19152
|
+
let identitySource;
|
|
19153
|
+
try {
|
|
19154
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 0)) throw new UsageError("--limit must be a non-negative integer");
|
|
19155
|
+
const setup = await setupClient(deps);
|
|
19156
|
+
identitySource = setup.identitySource;
|
|
19157
|
+
const rows = await setup.client.listAudit({
|
|
19158
|
+
site: options.site,
|
|
19159
|
+
actor: options.actor,
|
|
19160
|
+
action: options.action,
|
|
19161
|
+
since: options.since,
|
|
19162
|
+
limit: options.limit
|
|
19163
|
+
});
|
|
19164
|
+
if (options.json) emitJson(buildEnvelope(command, true, {
|
|
19165
|
+
count: rows.length,
|
|
19166
|
+
events: rows,
|
|
19167
|
+
identitySource
|
|
19168
|
+
}));
|
|
19169
|
+
else message(formatTable(rows));
|
|
19170
|
+
} catch (err) {
|
|
19171
|
+
const { code, message: msg, kind, requestId } = wrapProxyError(command, err);
|
|
19172
|
+
outputError({
|
|
19173
|
+
json: options.json,
|
|
19174
|
+
command
|
|
19175
|
+
}, code, msg, {
|
|
19176
|
+
logError: error,
|
|
19177
|
+
kind,
|
|
19178
|
+
requestId,
|
|
19179
|
+
extras: identitySource ? { identitySource } : void 0
|
|
19180
|
+
});
|
|
19181
|
+
exit(code);
|
|
19182
|
+
}
|
|
19183
|
+
}
|
|
19184
|
+
//#endregion
|
|
18891
19185
|
//#region src/lib/update-notifier.ts
|
|
18892
19186
|
const APP_DIR = "universe-cli";
|
|
18893
19187
|
const CACHE_FILE = "update-check.json";
|
|
18894
19188
|
const PKG_NAME = "@freecodecamp/universe-cli";
|
|
18895
19189
|
const NPM_LATEST_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
18896
|
-
const DEFAULT_TTL_MS = 3600 * 1e3;
|
|
18897
19190
|
const FETCH_TIMEOUT_MS = 3e3;
|
|
18898
19191
|
const REFRESH_ENV = "UNIVERSE_REFRESH_WORKER";
|
|
18899
|
-
function ttlMs() {
|
|
18900
|
-
const raw = process.env["UNIVERSE_UPDATE_TTL_MS"];
|
|
18901
|
-
if (raw !== void 0) {
|
|
18902
|
-
const n = Number.parseInt(raw, 10);
|
|
18903
|
-
if (Number.isFinite(n) && n >= 0) return n;
|
|
18904
|
-
}
|
|
18905
|
-
return DEFAULT_TTL_MS;
|
|
18906
|
-
}
|
|
18907
19192
|
function latestUrl() {
|
|
18908
19193
|
const override = process.env["UNIVERSE_UPDATE_URL"];
|
|
18909
19194
|
return override && override.length > 0 ? override : NPM_LATEST_URL;
|
|
@@ -18916,25 +19201,6 @@ function configBase() {
|
|
|
18916
19201
|
function cachePath() {
|
|
18917
19202
|
return (0, node_path.join)(configBase(), APP_DIR, CACHE_FILE);
|
|
18918
19203
|
}
|
|
18919
|
-
function isDisabled() {
|
|
18920
|
-
const v = process.env["UNIVERSE_NO_UPDATE_CHECK"];
|
|
18921
|
-
return v === "1" || v === "true";
|
|
18922
|
-
}
|
|
18923
|
-
function parseCache(raw) {
|
|
18924
|
-
let parsed;
|
|
18925
|
-
try {
|
|
18926
|
-
parsed = JSON.parse(raw);
|
|
18927
|
-
} catch {
|
|
18928
|
-
return null;
|
|
18929
|
-
}
|
|
18930
|
-
if (typeof parsed !== "object" || parsed === null || !("latest" in parsed) || !("lastCheck" in parsed)) return null;
|
|
18931
|
-
const { latest, lastCheck } = parsed;
|
|
18932
|
-
if (typeof latest !== "string" || typeof lastCheck !== "number") return null;
|
|
18933
|
-
return {
|
|
18934
|
-
latest,
|
|
18935
|
-
lastCheck
|
|
18936
|
-
};
|
|
18937
|
-
}
|
|
18938
19204
|
async function readCache() {
|
|
18939
19205
|
try {
|
|
18940
19206
|
return parseCache(await (0, node_fs_promises.readFile)(cachePath(), "utf-8"));
|
|
@@ -18974,29 +19240,6 @@ async function fetchLatest() {
|
|
|
18974
19240
|
clearTimeout(timer);
|
|
18975
19241
|
}
|
|
18976
19242
|
}
|
|
18977
|
-
function compareVersions(a, b) {
|
|
18978
|
-
const pa = parseVersion(a);
|
|
18979
|
-
const pb = parseVersion(b);
|
|
18980
|
-
if (pa === null || pb === null) return 0;
|
|
18981
|
-
for (let i = 0; i < 3; i += 1) {
|
|
18982
|
-
const ai = pa[i] ?? 0;
|
|
18983
|
-
const bi = pb[i] ?? 0;
|
|
18984
|
-
if (ai < bi) return -1;
|
|
18985
|
-
if (ai > bi) return 1;
|
|
18986
|
-
}
|
|
18987
|
-
return 0;
|
|
18988
|
-
}
|
|
18989
|
-
function parseVersion(s) {
|
|
18990
|
-
const parts = (s.split("-")[0] ?? "").split(".");
|
|
18991
|
-
if (parts.length !== 3) return null;
|
|
18992
|
-
const nums = parts.map((p) => Number.parseInt(p, 10));
|
|
18993
|
-
if (nums.some((n) => Number.isNaN(n))) return null;
|
|
18994
|
-
return [
|
|
18995
|
-
nums[0],
|
|
18996
|
-
nums[1],
|
|
18997
|
-
nums[2]
|
|
18998
|
-
];
|
|
18999
|
-
}
|
|
19000
19243
|
async function refreshIfStale(now = Date.now(), options = {}) {
|
|
19001
19244
|
if (isDisabled()) return;
|
|
19002
19245
|
if (!options.force) {
|
|
@@ -19042,14 +19285,6 @@ function getNoticeSync(current) {
|
|
|
19042
19285
|
latest: cache.latest
|
|
19043
19286
|
};
|
|
19044
19287
|
}
|
|
19045
|
-
function useColor() {
|
|
19046
|
-
if (process.env["NO_COLOR"] && process.env["NO_COLOR"].length > 0) return false;
|
|
19047
|
-
return process.stderr.isTTY === true;
|
|
19048
|
-
}
|
|
19049
|
-
function paint(s, code, color) {
|
|
19050
|
-
if (!color) return s;
|
|
19051
|
-
return `\x1b[${code}m${s}\x1b[0m`;
|
|
19052
|
-
}
|
|
19053
19288
|
function formatNotice(n, color = useColor()) {
|
|
19054
19289
|
const dim = (s) => paint(s, "2", color);
|
|
19055
19290
|
const yellow = (s) => paint(s, "33", color);
|
|
@@ -19081,7 +19316,7 @@ function installExitNotice(current) {
|
|
|
19081
19316
|
}
|
|
19082
19317
|
//#endregion
|
|
19083
19318
|
//#region src/cli.ts
|
|
19084
|
-
const version = "0.
|
|
19319
|
+
const version = "0.12.0";
|
|
19085
19320
|
function handleActionError(command, json, err) {
|
|
19086
19321
|
const ctx = {
|
|
19087
19322
|
json,
|
|
@@ -19125,6 +19360,7 @@ async function run(argv = process.argv) {
|
|
|
19125
19360
|
const sitesCli = namespaceGroup("sites", "Static site registry commands");
|
|
19126
19361
|
const staticCli = namespaceGroup("static", "Static site deployment commands");
|
|
19127
19362
|
const repoCli = namespaceGroup("repo", "Repository creation + approval queue commands");
|
|
19363
|
+
const auditCli = namespaceGroup("audit", "Audit trail query commands");
|
|
19128
19364
|
sitesCli.command("register <slug>").description("Register a new static site (staff only)").option("--team <name>", "GitHub team slug (repeatable, or comma-separated). Defaults to staff.").action(async (slug, _opts, cmd) => {
|
|
19129
19365
|
const opts = cmd.optsWithGlobals();
|
|
19130
19366
|
try {
|
|
@@ -19140,7 +19376,7 @@ async function run(argv = process.argv) {
|
|
|
19140
19376
|
sitesCli.command("ls").description("List sites in the registry").option("--mine", "Filter to sites your GitHub identity is authorized for").action(async (_opts, cmd) => {
|
|
19141
19377
|
const opts = cmd.optsWithGlobals();
|
|
19142
19378
|
try {
|
|
19143
|
-
await ls$
|
|
19379
|
+
await ls$2({
|
|
19144
19380
|
json: opts.json ?? false,
|
|
19145
19381
|
mine: opts.mine ?? false
|
|
19146
19382
|
});
|
|
@@ -19189,7 +19425,7 @@ async function run(argv = process.argv) {
|
|
|
19189
19425
|
repoCli.command("ls").description("List repo requests (default: pending)").option("--status <status>", "pending | approved | active | rejected | failed | all").option("--mine", "Only requests you submitted").option("--all", "Show every state (shorthand for --status all)").action(async (_opts, cmd) => {
|
|
19190
19426
|
const opts = cmd.optsWithGlobals();
|
|
19191
19427
|
try {
|
|
19192
|
-
await ls({
|
|
19428
|
+
await ls$1({
|
|
19193
19429
|
json: opts.json ?? false,
|
|
19194
19430
|
status: opts.status,
|
|
19195
19431
|
mine: opts.mine ?? false,
|
|
@@ -19284,7 +19520,7 @@ async function run(argv = process.argv) {
|
|
|
19284
19520
|
staticCli.command("ls").description("List recent deploys for a site").option("--site <site>", "Override site from platform.yaml").action(async (_opts, cmd) => {
|
|
19285
19521
|
const opts = cmd.optsWithGlobals();
|
|
19286
19522
|
try {
|
|
19287
|
-
await ls$
|
|
19523
|
+
await ls$3({
|
|
19288
19524
|
json: opts.json ?? false,
|
|
19289
19525
|
site: opts.site
|
|
19290
19526
|
});
|
|
@@ -19351,9 +19587,25 @@ async function run(argv = process.argv) {
|
|
|
19351
19587
|
handleActionError("whoami", opts.json ?? false, err);
|
|
19352
19588
|
}
|
|
19353
19589
|
});
|
|
19590
|
+
auditCli.command("ls").description("List durable audit events (who did what)").option("--actor <login>", "Filter by GitHub actor").option("--action <action>", "Filter by action (e.g. repo.approve)").option("--site <slug>", "Filter by site").option("--since <rfc3339>", "Only events at or after this timestamp").option("--limit <n>", "Max rows (default 100, max 500)", (v) => Number.parseInt(v, 10)).action(async (_opts, cmd) => {
|
|
19591
|
+
const opts = cmd.optsWithGlobals();
|
|
19592
|
+
try {
|
|
19593
|
+
await ls({
|
|
19594
|
+
json: opts.json ?? false,
|
|
19595
|
+
actor: opts.actor,
|
|
19596
|
+
action: opts.action,
|
|
19597
|
+
site: opts.site,
|
|
19598
|
+
since: opts.since,
|
|
19599
|
+
limit: opts.limit
|
|
19600
|
+
});
|
|
19601
|
+
} catch (err) {
|
|
19602
|
+
handleActionError("audit ls", opts.json ?? false, err);
|
|
19603
|
+
}
|
|
19604
|
+
});
|
|
19354
19605
|
cli.addCommand(staticCli);
|
|
19355
19606
|
cli.addCommand(sitesCli);
|
|
19356
19607
|
cli.addCommand(repoCli);
|
|
19608
|
+
cli.addCommand(auditCli);
|
|
19357
19609
|
if (args.length === 0) {
|
|
19358
19610
|
cli.outputHelp();
|
|
19359
19611
|
return;
|