@freecodecamp/universe-cli 0.11.0 → 0.13.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 +516 -161
- package/package.json +30 -27
package/dist/index.cjs
CHANGED
|
@@ -9609,7 +9609,7 @@ var require_public_api = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
9609
9609
|
exports.stringify = stringify;
|
|
9610
9610
|
}));
|
|
9611
9611
|
//#endregion
|
|
9612
|
-
//#region src/commands/create/layer-composition/build-compose-
|
|
9612
|
+
//#region src/commands/create/layer-composition/build-compose-yaml.ts
|
|
9613
9613
|
var import_dist = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
9614
9614
|
var composer = require_composer();
|
|
9615
9615
|
var Document = require_Document();
|
|
@@ -9656,7 +9656,7 @@ var import_dist = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
9656
9656
|
exports.visit = visit.visit;
|
|
9657
9657
|
exports.visitAsync = visit.visitAsync;
|
|
9658
9658
|
})))();
|
|
9659
|
-
const
|
|
9659
|
+
const buildComposeYaml = (framework, packageManager) => {
|
|
9660
9660
|
const { port } = framework;
|
|
9661
9661
|
const portMapping = `${port}:${port}`;
|
|
9662
9662
|
const syncEntries = framework.watchSync.map((entry) => ({
|
|
@@ -9677,6 +9677,28 @@ const buildComposeDevYaml = (framework, packageManager) => {
|
|
|
9677
9677
|
ports: [portMapping]
|
|
9678
9678
|
} } });
|
|
9679
9679
|
};
|
|
9680
|
+
const buildDevcontainerComposeYaml = () => `services:
|
|
9681
|
+
devcontainer:
|
|
9682
|
+
build:
|
|
9683
|
+
context: ..
|
|
9684
|
+
target: dev
|
|
9685
|
+
command: ["sleep", "infinity"]
|
|
9686
|
+
volumes:
|
|
9687
|
+
# First mount the source
|
|
9688
|
+
- type: bind
|
|
9689
|
+
source: ..
|
|
9690
|
+
target: /app
|
|
9691
|
+
consistency: cached
|
|
9692
|
+
# Then mount an anonymous volume to hold node_modules. Otherwise the bind mount causes the host node_modules to be used.
|
|
9693
|
+
- type: volume
|
|
9694
|
+
target: /app/node_modules
|
|
9695
|
+
`;
|
|
9696
|
+
const buildDevcontainerJson = () => `{
|
|
9697
|
+
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json",
|
|
9698
|
+
"dockerComposeFile": ["./docker-compose.yml"],
|
|
9699
|
+
"service": "devcontainer",
|
|
9700
|
+
"workspaceFolder": "/app"
|
|
9701
|
+
}`;
|
|
9680
9702
|
//#endregion
|
|
9681
9703
|
//#region src/output/exit-codes.ts
|
|
9682
9704
|
/**
|
|
@@ -9804,7 +9826,7 @@ const composeLayerFiles = (layers, pmPreinstall) => {
|
|
|
9804
9826
|
//#region src/commands/create/layer-composition/layer-template-renderer.ts
|
|
9805
9827
|
var LayerTemplateRenderer = class {
|
|
9806
9828
|
render(template, context) {
|
|
9807
|
-
return template.replaceAll("{{name}}", context.name).replaceAll("{{port}}", String(context.port)).replaceAll("{{runtime}}", context.runtime).replaceAll("{{framework}}", context.framework);
|
|
9829
|
+
return template.replaceAll("{{name}}", context.name).replaceAll("{{port}}", String(context.port)).replaceAll("{{runtime}}", context.runtime).replaceAll("{{framework}}", context.framework).replaceAll("{{pmVersion}}", context.pmVersion);
|
|
9808
9830
|
}
|
|
9809
9831
|
};
|
|
9810
9832
|
//#endregion
|
|
@@ -9864,23 +9886,24 @@ const buildDockerfileData = (runtime, framework, packageManager) => {
|
|
|
9864
9886
|
const renderDockerfile = (data) => `FROM ${data.baseImage} AS base\nWORKDIR /app\n\nFROM base AS package-manager\n${data.pmInstall} \n\nFROM package-manager AS dev\n${data.devInstall}\n${data.devCopySource}\nCMD ${JSON.stringify(data.devCmd)}\n`;
|
|
9865
9887
|
const resolveWithLayers = (input, layers, labels) => {
|
|
9866
9888
|
const resolvedLayers = resolveOrderedLayers(input, layers);
|
|
9867
|
-
const
|
|
9889
|
+
const pmData = input.packageManager !== void 0 ? layers["package-managers"][input.packageManager] : void 0;
|
|
9890
|
+
const composedFiles = composeLayerFiles(resolvedLayers, pmData?.preinstall);
|
|
9868
9891
|
const renderer = new LayerTemplateRenderer();
|
|
9869
9892
|
const frameworkData = layers.frameworks?.[input.framework];
|
|
9870
9893
|
const context = {
|
|
9871
9894
|
framework: getLabel(labels, "framework", input.framework),
|
|
9872
9895
|
name: input.name,
|
|
9896
|
+
pmVersion: pmData?.pmVersion ?? "",
|
|
9873
9897
|
port: frameworkData?.port ?? 0,
|
|
9874
9898
|
runtime: getLabel(labels, "runtime", input.runtime)
|
|
9875
9899
|
};
|
|
9876
9900
|
const renderedFiles = Object.fromEntries(Object.entries(composedFiles).map(([filePath, content]) => [filePath, renderer.render(content, context)]));
|
|
9877
9901
|
const runtimeData = layers.runtime?.[input.runtime];
|
|
9878
|
-
if (runtimeData !== void 0 && frameworkData !== void 0 &&
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
}
|
|
9902
|
+
if (runtimeData !== void 0 && frameworkData !== void 0 && pmData !== void 0) {
|
|
9903
|
+
renderedFiles["Dockerfile"] = renderer.render(renderDockerfile(buildDockerfileData(runtimeData, frameworkData, pmData)), context);
|
|
9904
|
+
renderedFiles["compose.yaml"] = renderer.render(buildComposeYaml(frameworkData, pmData), context);
|
|
9905
|
+
renderedFiles[".devcontainer/docker-compose.yml"] = buildDevcontainerComposeYaml();
|
|
9906
|
+
renderedFiles[".devcontainer/devcontainer.json"] = buildDevcontainerJson();
|
|
9884
9907
|
}
|
|
9885
9908
|
return {
|
|
9886
9909
|
files: renderedFiles,
|
|
@@ -9905,10 +9928,10 @@ var PackageManagerService = class {
|
|
|
9905
9928
|
this.adapters = adapters;
|
|
9906
9929
|
}
|
|
9907
9930
|
async specifyDeps(options) {
|
|
9908
|
-
const { manager, projectDirectory } = options;
|
|
9931
|
+
const { manager, pmVersion, projectDirectory } = options;
|
|
9909
9932
|
const adapter = this.adapters[manager];
|
|
9910
9933
|
if (!adapter) throw new Error(`Unknown package manager: ${manager}`);
|
|
9911
|
-
await adapter.specifyDeps(projectDirectory);
|
|
9934
|
+
await adapter.specifyDeps(projectDirectory, pmVersion);
|
|
9912
9935
|
}
|
|
9913
9936
|
};
|
|
9914
9937
|
//#endregion
|
|
@@ -9933,7 +9956,7 @@ Please check that the output format of list() has not changed, and that extractV
|
|
|
9933
9956
|
if (devDependencies !== void 0) pinned.devDependencies = pin(devDependencies);
|
|
9934
9957
|
return pinned;
|
|
9935
9958
|
};
|
|
9936
|
-
const createPackageSpecifier = (config) => ({ async
|
|
9959
|
+
const createPackageSpecifier = (config) => ({ async run(projectDirectory) {
|
|
9937
9960
|
const { lockfileName, runner, deleteBeforeFirstInstall } = config;
|
|
9938
9961
|
const lockfilePath = (0, node_path.join)(projectDirectory, lockfileName);
|
|
9939
9962
|
if (deleteBeforeFirstInstall) try {
|
|
@@ -9965,11 +9988,11 @@ const createPackageSpecifier = (config) => ({ async specifyDeps(projectDirectory
|
|
|
9965
9988
|
} });
|
|
9966
9989
|
//#endregion
|
|
9967
9990
|
//#region src/commands/create/package-manager/docker-runner.ts
|
|
9968
|
-
const execFileAsync$
|
|
9991
|
+
const execFileAsync$3 = (0, node_util.promisify)(node_child_process.execFile);
|
|
9969
9992
|
const IMAGE_TAG = "universe-runner:pm";
|
|
9970
9993
|
const ensureImageBuilt = async (cwd) => {
|
|
9971
9994
|
const dockerfile = (0, node_path.resolve)(cwd, "Dockerfile");
|
|
9972
|
-
await execFileAsync$
|
|
9995
|
+
await execFileAsync$3("docker", [
|
|
9973
9996
|
"build",
|
|
9974
9997
|
"--file",
|
|
9975
9998
|
dockerfile,
|
|
@@ -9982,7 +10005,7 @@ const ensureImageBuilt = async (cwd) => {
|
|
|
9982
10005
|
};
|
|
9983
10006
|
const runCmdForFiles = async (cwd, cmd, inputs, outputs) => {
|
|
9984
10007
|
await ensureImageBuilt(cwd);
|
|
9985
|
-
const { stdout: idRaw } = await execFileAsync$
|
|
10008
|
+
const { stdout: idRaw } = await execFileAsync$3("docker", [
|
|
9986
10009
|
"create",
|
|
9987
10010
|
"-w",
|
|
9988
10011
|
"/app",
|
|
@@ -9991,28 +10014,28 @@ const runCmdForFiles = async (cwd, cmd, inputs, outputs) => {
|
|
|
9991
10014
|
], { encoding: "utf8" });
|
|
9992
10015
|
const id = idRaw.trim();
|
|
9993
10016
|
try {
|
|
9994
|
-
await Promise.all(inputs.map((file) => execFileAsync$
|
|
10017
|
+
await Promise.all(inputs.map((file) => execFileAsync$3("docker", [
|
|
9995
10018
|
"cp",
|
|
9996
10019
|
`${cwd}/${file}`,
|
|
9997
10020
|
`${id}:/app/${file}`
|
|
9998
10021
|
], { encoding: "utf8" })));
|
|
9999
|
-
await execFileAsync$
|
|
10022
|
+
await execFileAsync$3("docker", [
|
|
10000
10023
|
"start",
|
|
10001
10024
|
"-a",
|
|
10002
10025
|
id
|
|
10003
10026
|
], { encoding: "utf8" });
|
|
10004
|
-
await Promise.all(outputs.map((file) => execFileAsync$
|
|
10027
|
+
await Promise.all(outputs.map((file) => execFileAsync$3("docker", [
|
|
10005
10028
|
"cp",
|
|
10006
10029
|
`${id}:/app/${file}`,
|
|
10007
10030
|
`${cwd}/${file}`
|
|
10008
10031
|
], { encoding: "utf8" })));
|
|
10009
10032
|
} finally {
|
|
10010
|
-
await execFileAsync$
|
|
10033
|
+
await execFileAsync$3("docker", ["rm", id], { encoding: "utf8" });
|
|
10011
10034
|
}
|
|
10012
10035
|
};
|
|
10013
10036
|
const runCmdForStdout = async (cwd, cmd, inputs) => {
|
|
10014
10037
|
await ensureImageBuilt(cwd);
|
|
10015
|
-
const { stdout: idRaw } = await execFileAsync$
|
|
10038
|
+
const { stdout: idRaw } = await execFileAsync$3("docker", [
|
|
10016
10039
|
"create",
|
|
10017
10040
|
"-w",
|
|
10018
10041
|
"/app",
|
|
@@ -10021,19 +10044,19 @@ const runCmdForStdout = async (cwd, cmd, inputs) => {
|
|
|
10021
10044
|
], { encoding: "utf8" });
|
|
10022
10045
|
const id = idRaw.trim();
|
|
10023
10046
|
try {
|
|
10024
|
-
await Promise.all(inputs.map((file) => execFileAsync$
|
|
10047
|
+
await Promise.all(inputs.map((file) => execFileAsync$3("docker", [
|
|
10025
10048
|
"cp",
|
|
10026
10049
|
`${cwd}/${file}`,
|
|
10027
10050
|
`${id}:/app/${file}`
|
|
10028
10051
|
], { encoding: "utf8" })));
|
|
10029
|
-
const { stdout } = await execFileAsync$
|
|
10052
|
+
const { stdout } = await execFileAsync$3("docker", [
|
|
10030
10053
|
"start",
|
|
10031
10054
|
"-a",
|
|
10032
10055
|
id
|
|
10033
10056
|
], { encoding: "utf8" });
|
|
10034
10057
|
return stdout;
|
|
10035
10058
|
} finally {
|
|
10036
|
-
await execFileAsync$
|
|
10059
|
+
await execFileAsync$3("docker", ["rm", id], { encoding: "utf8" });
|
|
10037
10060
|
}
|
|
10038
10061
|
};
|
|
10039
10062
|
//#endregion
|
|
@@ -10067,17 +10090,17 @@ const extractVersions$1 = (listOutput) => {
|
|
|
10067
10090
|
return versions;
|
|
10068
10091
|
};
|
|
10069
10092
|
var BunPackageManager = class {
|
|
10070
|
-
|
|
10093
|
+
runner;
|
|
10071
10094
|
constructor(runner = bunRunner) {
|
|
10072
|
-
this.
|
|
10095
|
+
this.runner = runner;
|
|
10096
|
+
}
|
|
10097
|
+
specifyDeps(projectDirectory, _pmVersion) {
|
|
10098
|
+
return createPackageSpecifier({
|
|
10073
10099
|
deleteBeforeFirstInstall: true,
|
|
10074
10100
|
extractVersions: extractVersions$1,
|
|
10075
10101
|
lockfileName: LOCKFILE$1,
|
|
10076
|
-
runner
|
|
10077
|
-
});
|
|
10078
|
-
}
|
|
10079
|
-
specifyDeps(projectDirectory) {
|
|
10080
|
-
return this.impl.specifyDeps(projectDirectory);
|
|
10102
|
+
runner: this.runner
|
|
10103
|
+
}).run(projectDirectory);
|
|
10081
10104
|
}
|
|
10082
10105
|
};
|
|
10083
10106
|
//#endregion
|
|
@@ -10088,13 +10111,13 @@ var BunPackageManager = class {
|
|
|
10088
10111
|
*/
|
|
10089
10112
|
const LOCKFILE = "pnpm-lock.yaml";
|
|
10090
10113
|
const MANIFESTS = ["package.json", "pnpm-workspace.yaml"];
|
|
10091
|
-
const
|
|
10114
|
+
const defaultRunnerFactory = (pmVersion) => ({
|
|
10092
10115
|
async installLockfileOnly(cwd) {
|
|
10093
10116
|
await runCmdForFiles(cwd, [
|
|
10094
|
-
"
|
|
10095
|
-
"
|
|
10096
|
-
|
|
10097
|
-
], MANIFESTS, [LOCKFILE]);
|
|
10117
|
+
"sh",
|
|
10118
|
+
"-c",
|
|
10119
|
+
`corepack use pnpm@${pmVersion} && pnpm install --lockfile-only`
|
|
10120
|
+
], MANIFESTS, [LOCKFILE, "package.json"]);
|
|
10098
10121
|
},
|
|
10099
10122
|
list(cwd) {
|
|
10100
10123
|
return runCmdForStdout(cwd, [
|
|
@@ -10105,7 +10128,7 @@ const defaultPnpmRunner = {
|
|
|
10105
10128
|
"--lockfile-only"
|
|
10106
10129
|
], [...MANIFESTS, LOCKFILE]);
|
|
10107
10130
|
}
|
|
10108
|
-
};
|
|
10131
|
+
});
|
|
10109
10132
|
const extractVersions = (listOutput) => {
|
|
10110
10133
|
const root = JSON.parse(listOutput)[0] ?? {};
|
|
10111
10134
|
const versions = {};
|
|
@@ -10114,17 +10137,17 @@ const extractVersions = (listOutput) => {
|
|
|
10114
10137
|
return versions;
|
|
10115
10138
|
};
|
|
10116
10139
|
var PnpmPackageManager = class {
|
|
10117
|
-
|
|
10118
|
-
constructor(
|
|
10119
|
-
this.
|
|
10140
|
+
createRunner;
|
|
10141
|
+
constructor(createRunner = defaultRunnerFactory) {
|
|
10142
|
+
this.createRunner = createRunner;
|
|
10143
|
+
}
|
|
10144
|
+
async specifyDeps(projectDirectory, pmVersion) {
|
|
10145
|
+
await createPackageSpecifier({
|
|
10120
10146
|
deleteBeforeFirstInstall: false,
|
|
10121
10147
|
extractVersions,
|
|
10122
10148
|
lockfileName: LOCKFILE,
|
|
10123
|
-
runner
|
|
10124
|
-
});
|
|
10125
|
-
}
|
|
10126
|
-
specifyDeps(projectDirectory) {
|
|
10127
|
-
return this.impl.specifyDeps(projectDirectory);
|
|
10149
|
+
runner: this.createRunner(pmVersion)
|
|
10150
|
+
}).run(projectDirectory);
|
|
10128
10151
|
}
|
|
10129
10152
|
};
|
|
10130
10153
|
//#endregion
|
|
@@ -11372,13 +11395,13 @@ var ClackPrompt = class {
|
|
|
11372
11395
|
};
|
|
11373
11396
|
//#endregion
|
|
11374
11397
|
//#region src/commands/create/io/git-repo-initialiser.ts
|
|
11375
|
-
const execFileAsync$
|
|
11376
|
-
const defaultRun = async (command, args, cwd) => {
|
|
11377
|
-
await execFileAsync$
|
|
11398
|
+
const execFileAsync$2 = (0, node_util.promisify)(node_child_process.execFile);
|
|
11399
|
+
const defaultRun$1 = async (command, args, cwd) => {
|
|
11400
|
+
await execFileAsync$2(command, args, { cwd });
|
|
11378
11401
|
};
|
|
11379
11402
|
var GitRepoInitialiser = class {
|
|
11380
11403
|
run;
|
|
11381
|
-
constructor(run = defaultRun) {
|
|
11404
|
+
constructor(run = defaultRun$1) {
|
|
11382
11405
|
this.run = run;
|
|
11383
11406
|
}
|
|
11384
11407
|
async initialise(projectDirectory) {
|
|
@@ -11396,6 +11419,41 @@ var GitRepoInitialiser = class {
|
|
|
11396
11419
|
}
|
|
11397
11420
|
};
|
|
11398
11421
|
//#endregion
|
|
11422
|
+
//#region src/commands/create/io/npx-skill-installer.ts
|
|
11423
|
+
const execFileAsync$1 = (0, node_util.promisify)(node_child_process.execFile);
|
|
11424
|
+
const defaultRun = async (command, args, cwd) => {
|
|
11425
|
+
await execFileAsync$1(command, args, { cwd });
|
|
11426
|
+
};
|
|
11427
|
+
var NpxSkillInstaller = class {
|
|
11428
|
+
run;
|
|
11429
|
+
constructor(run = defaultRun) {
|
|
11430
|
+
this.run = run;
|
|
11431
|
+
}
|
|
11432
|
+
async installSkills(skills, projectDirectory) {
|
|
11433
|
+
const skillsByRepo = /* @__PURE__ */ new Map();
|
|
11434
|
+
for (const { repo, skill } of skills) {
|
|
11435
|
+
const existing = skillsByRepo.get(repo);
|
|
11436
|
+
if (existing === void 0) skillsByRepo.set(repo, [skill]);
|
|
11437
|
+
else existing.push(skill);
|
|
11438
|
+
}
|
|
11439
|
+
try {
|
|
11440
|
+
for (const [repo, repoSkills] of skillsByRepo) {
|
|
11441
|
+
const args = [
|
|
11442
|
+
"--yes",
|
|
11443
|
+
"skills",
|
|
11444
|
+
"add",
|
|
11445
|
+
"--yes",
|
|
11446
|
+
repo,
|
|
11447
|
+
...repoSkills.flatMap((skill) => ["--skill", skill])
|
|
11448
|
+
];
|
|
11449
|
+
await this.run("npx", args, projectDirectory);
|
|
11450
|
+
}
|
|
11451
|
+
} catch (error) {
|
|
11452
|
+
throw new ConfigError(error.message);
|
|
11453
|
+
}
|
|
11454
|
+
}
|
|
11455
|
+
};
|
|
11456
|
+
//#endregion
|
|
11399
11457
|
//#region node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
|
|
11400
11458
|
var _a$1;
|
|
11401
11459
|
function $constructor(name, initializer, params) {
|
|
@@ -15712,6 +15770,15 @@ const silentLogger = {
|
|
|
15712
15770
|
warn: () => {}
|
|
15713
15771
|
};
|
|
15714
15772
|
//#endregion
|
|
15773
|
+
//#region src/output/spinner.ts
|
|
15774
|
+
const clackSpinner = () => vt();
|
|
15775
|
+
const silentSpinner = () => ({
|
|
15776
|
+
error: () => {},
|
|
15777
|
+
start: () => {},
|
|
15778
|
+
message: () => {},
|
|
15779
|
+
stop: () => {}
|
|
15780
|
+
});
|
|
15781
|
+
//#endregion
|
|
15715
15782
|
//#region src/output/envelope.ts
|
|
15716
15783
|
function buildEnvelope(command, success, data) {
|
|
15717
15784
|
return {
|
|
@@ -15866,6 +15933,7 @@ const PackageManagerSchema = record(literal(["bun", "pnpm"]), strictObject({
|
|
|
15866
15933
|
lockfile: string(),
|
|
15867
15934
|
manifests: array(string()),
|
|
15868
15935
|
pmInstall: string(),
|
|
15936
|
+
pmVersion: string().regex(/^\d+\.\d+\.\d+$/, "pmVersion must be a semver version (major.minor.patch), e.g. 1.2.3"),
|
|
15869
15937
|
preinstall: string().optional()
|
|
15870
15938
|
}));
|
|
15871
15939
|
const ServiceSchema = record(literal([
|
|
@@ -15877,6 +15945,10 @@ const FrameworkSchema = record(string(), strictObject({
|
|
|
15877
15945
|
devCopySource: string(),
|
|
15878
15946
|
files: record(string(), string()),
|
|
15879
15947
|
port: number(),
|
|
15948
|
+
skills: array(strictObject({
|
|
15949
|
+
repo: string(),
|
|
15950
|
+
skill: string()
|
|
15951
|
+
})).optional(),
|
|
15880
15952
|
watchSync: array(strictObject({
|
|
15881
15953
|
path: string(),
|
|
15882
15954
|
target: string()
|
|
@@ -15892,19 +15964,23 @@ const LabelsSchema = record(union([
|
|
|
15892
15964
|
literal("service")
|
|
15893
15965
|
]), record(string(), string()));
|
|
15894
15966
|
//#endregion
|
|
15967
|
+
//#region src/commands/create/layer-composition/assets.json
|
|
15968
|
+
var templateVersion = "0.4.1";
|
|
15969
|
+
var templateUrl = "https://github.com/freeCodeCamp-Universe/templates/releases/download/app-templates-v{{version}}/app-templates-{{version}}.tar.gz";
|
|
15970
|
+
//#endregion
|
|
15971
|
+
//#region src/commands/create/layer-composition/assets.ts
|
|
15972
|
+
const defaultTemplateVersion = templateVersion;
|
|
15973
|
+
const resolveTemplateUrl = (version) => templateUrl.replaceAll("{{version}}", version);
|
|
15974
|
+
//#endregion
|
|
15895
15975
|
//#region src/commands/create/layer-composition/template-cache.ts
|
|
15896
|
-
const APP_DIR$
|
|
15897
|
-
const TEMPLATES_DIR = "templates";
|
|
15976
|
+
const APP_DIR$3 = "universe-cli";
|
|
15977
|
+
const TEMPLATES_DIR$1 = "templates";
|
|
15898
15978
|
const cacheBase = () => {
|
|
15899
15979
|
const xdg = process.env["XDG_CACHE_HOME"];
|
|
15900
15980
|
if (xdg && xdg.length > 0) return xdg;
|
|
15901
15981
|
return (0, node_path.join)((0, node_os.homedir)(), ".cache");
|
|
15902
15982
|
};
|
|
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));
|
|
15983
|
+
const templateCacheDir = (version) => (0, node_path.join)(cacheBase(), APP_DIR$3, TEMPLATES_DIR$1, version);
|
|
15908
15984
|
//#endregion
|
|
15909
15985
|
//#region src/commands/create/layer-composition/template-provider.ts
|
|
15910
15986
|
const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
|
|
@@ -15919,7 +15995,7 @@ const EXPECTED_LAYER_FILES = [
|
|
|
15919
15995
|
const EXPECTED_ROOT_ENTRIES = ["labels.json", "layers"];
|
|
15920
15996
|
const readEnv = () => ({
|
|
15921
15997
|
UNIVERSE_TEMPLATES_DIR: process.env["UNIVERSE_TEMPLATES_DIR"],
|
|
15922
|
-
|
|
15998
|
+
UNIVERSE_TEMPLATES_VERSION: process.env["UNIVERSE_TEMPLATES_VERSION"]
|
|
15923
15999
|
});
|
|
15924
16000
|
const validateEntries = (dir, expected, actual) => {
|
|
15925
16001
|
const expectedSet = new Set(expected);
|
|
@@ -15996,7 +16072,7 @@ const fetchTarball = async (url, fetchImpl) => {
|
|
|
15996
16072
|
} catch {
|
|
15997
16073
|
throw new ConfigError("Templates not cached. Run `universe templates fetch` or check network.");
|
|
15998
16074
|
}
|
|
15999
|
-
if (response.status === 404) throw new ConfigError(`Template not found at ${url}. Check
|
|
16075
|
+
if (response.status === 404) throw new ConfigError(`Template not found at ${url}. Check UNIVERSE_TEMPLATES_VERSION.`);
|
|
16000
16076
|
if (!response.ok) throw new ConfigError(`Failed to fetch templates from ${url}: HTTP ${String(response.status)}`);
|
|
16001
16077
|
const arrayBuffer = await response.arrayBuffer();
|
|
16002
16078
|
return Buffer.from(arrayBuffer);
|
|
@@ -16045,24 +16121,169 @@ var RemoteTemplateProvider = class {
|
|
|
16045
16121
|
this.cacheBaseOverride = cacheBaseOverride;
|
|
16046
16122
|
this.fetchImpl = fetchImpl;
|
|
16047
16123
|
}
|
|
16048
|
-
resolveCacheDir(
|
|
16049
|
-
if (this.cacheBaseOverride !== void 0) return (0, node_path.join)(this.cacheBaseOverride
|
|
16050
|
-
return templateCacheDir(
|
|
16124
|
+
resolveCacheDir(version) {
|
|
16125
|
+
if (this.cacheBaseOverride !== void 0) return (0, node_path.join)(this.cacheBaseOverride, version);
|
|
16126
|
+
return templateCacheDir(version);
|
|
16051
16127
|
}
|
|
16052
16128
|
async loadLayers(options) {
|
|
16053
|
-
const { UNIVERSE_TEMPLATES_DIR,
|
|
16129
|
+
const { UNIVERSE_TEMPLATES_DIR, UNIVERSE_TEMPLATES_VERSION } = this.env();
|
|
16054
16130
|
if (UNIVERSE_TEMPLATES_DIR !== void 0 && UNIVERSE_TEMPLATES_DIR.length > 0) return loadFromDir(UNIVERSE_TEMPLATES_DIR);
|
|
16055
|
-
|
|
16056
|
-
const
|
|
16131
|
+
const version = UNIVERSE_TEMPLATES_VERSION && UNIVERSE_TEMPLATES_VERSION.length > 0 ? UNIVERSE_TEMPLATES_VERSION : defaultTemplateVersion;
|
|
16132
|
+
const url = resolveTemplateUrl(version);
|
|
16133
|
+
const cacheDir = this.resolveCacheDir(version);
|
|
16057
16134
|
if (options?.forceFetch) await (0, node_fs_promises.rm)(cacheDir, {
|
|
16058
16135
|
force: true,
|
|
16059
16136
|
recursive: true
|
|
16060
16137
|
});
|
|
16061
16138
|
if (await cacheHit(cacheDir)) return loadFromDir(cacheDir);
|
|
16062
|
-
return fetchAndCache(
|
|
16139
|
+
return fetchAndCache(url, cacheDir, this.fetchImpl);
|
|
16063
16140
|
}
|
|
16064
16141
|
};
|
|
16065
16142
|
//#endregion
|
|
16143
|
+
//#region src/lib/version-utils.ts
|
|
16144
|
+
const DEFAULT_TTL_MS = 3600 * 1e3;
|
|
16145
|
+
function ttlMs() {
|
|
16146
|
+
const raw = process.env["UNIVERSE_UPDATE_TTL_MS"];
|
|
16147
|
+
if (raw !== void 0) {
|
|
16148
|
+
const n = Number.parseInt(raw, 10);
|
|
16149
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
16150
|
+
}
|
|
16151
|
+
return DEFAULT_TTL_MS;
|
|
16152
|
+
}
|
|
16153
|
+
function isDisabled() {
|
|
16154
|
+
const v = process.env["UNIVERSE_NO_UPDATE_CHECK"];
|
|
16155
|
+
return v === "1" || v === "true";
|
|
16156
|
+
}
|
|
16157
|
+
function parseCache(raw) {
|
|
16158
|
+
let parsed;
|
|
16159
|
+
try {
|
|
16160
|
+
parsed = JSON.parse(raw);
|
|
16161
|
+
} catch {
|
|
16162
|
+
return null;
|
|
16163
|
+
}
|
|
16164
|
+
if (typeof parsed !== "object" || parsed === null || !("latest" in parsed) || !("lastCheck" in parsed)) return null;
|
|
16165
|
+
const { latest, lastCheck } = parsed;
|
|
16166
|
+
if (typeof latest !== "string" || typeof lastCheck !== "number") return null;
|
|
16167
|
+
return {
|
|
16168
|
+
latest,
|
|
16169
|
+
lastCheck
|
|
16170
|
+
};
|
|
16171
|
+
}
|
|
16172
|
+
function compareVersions(a, b) {
|
|
16173
|
+
const pa = parseVersion(a);
|
|
16174
|
+
const pb = parseVersion(b);
|
|
16175
|
+
if (pa === null || pb === null) return 0;
|
|
16176
|
+
for (let i = 0; i < 3; i += 1) {
|
|
16177
|
+
const ai = pa[i] ?? 0;
|
|
16178
|
+
const bi = pb[i] ?? 0;
|
|
16179
|
+
if (ai < bi) return -1;
|
|
16180
|
+
if (ai > bi) return 1;
|
|
16181
|
+
}
|
|
16182
|
+
return 0;
|
|
16183
|
+
}
|
|
16184
|
+
function parseVersion(s) {
|
|
16185
|
+
const parts = (s.split("-")[0] ?? "").split(".");
|
|
16186
|
+
if (parts.length !== 3) return null;
|
|
16187
|
+
const nums = parts.map((p) => Number.parseInt(p, 10));
|
|
16188
|
+
if (nums.some((n) => Number.isNaN(n))) return null;
|
|
16189
|
+
return [
|
|
16190
|
+
nums[0],
|
|
16191
|
+
nums[1],
|
|
16192
|
+
nums[2]
|
|
16193
|
+
];
|
|
16194
|
+
}
|
|
16195
|
+
function useColor() {
|
|
16196
|
+
if (process.env["NO_COLOR"] && process.env["NO_COLOR"].length > 0) return false;
|
|
16197
|
+
return process.stderr.isTTY === true;
|
|
16198
|
+
}
|
|
16199
|
+
function paint(s, code, color) {
|
|
16200
|
+
if (!color) return s;
|
|
16201
|
+
return `\x1b[${code}m${s}\x1b[0m`;
|
|
16202
|
+
}
|
|
16203
|
+
//#endregion
|
|
16204
|
+
//#region src/lib/template-version-check.ts
|
|
16205
|
+
const APP_DIR$2 = "universe-cli";
|
|
16206
|
+
const TEMPLATES_DIR = "templates";
|
|
16207
|
+
const CACHE_FILE$1 = "template-version-check.json";
|
|
16208
|
+
const GITHUB_LATEST_URL = "https://api.github.com/repos/freeCodeCamp-Universe/templates/releases/latest";
|
|
16209
|
+
const FETCH_TIMEOUT_MS$1 = 3e3;
|
|
16210
|
+
const TAG_PREFIX = "app-templates-v";
|
|
16211
|
+
function templateCheckCachePath() {
|
|
16212
|
+
return (0, node_path.join)(cacheBase(), APP_DIR$2, TEMPLATES_DIR, CACHE_FILE$1);
|
|
16213
|
+
}
|
|
16214
|
+
async function readTemplateCache() {
|
|
16215
|
+
try {
|
|
16216
|
+
return parseCache(await (0, node_fs_promises.readFile)(templateCheckCachePath(), "utf-8"));
|
|
16217
|
+
} catch {
|
|
16218
|
+
return null;
|
|
16219
|
+
}
|
|
16220
|
+
}
|
|
16221
|
+
async function writeTemplateCache(c) {
|
|
16222
|
+
const path = templateCheckCachePath();
|
|
16223
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), {
|
|
16224
|
+
recursive: true,
|
|
16225
|
+
mode: 448
|
|
16226
|
+
});
|
|
16227
|
+
await (0, node_fs_promises.writeFile)(path, JSON.stringify(c), { mode: 420 });
|
|
16228
|
+
}
|
|
16229
|
+
async function fetchLatestTemplateVersion() {
|
|
16230
|
+
const ctl = new AbortController();
|
|
16231
|
+
const timer = setTimeout(() => ctl.abort(), FETCH_TIMEOUT_MS$1);
|
|
16232
|
+
try {
|
|
16233
|
+
const res = await fetch(GITHUB_LATEST_URL, {
|
|
16234
|
+
signal: ctl.signal,
|
|
16235
|
+
headers: { accept: "application/vnd.github+json" }
|
|
16236
|
+
});
|
|
16237
|
+
if (!res.ok) return null;
|
|
16238
|
+
const body = await res.json();
|
|
16239
|
+
if (typeof body.tag_name !== "string") return null;
|
|
16240
|
+
if (!body.tag_name.startsWith(TAG_PREFIX)) return null;
|
|
16241
|
+
return body.tag_name.slice(15);
|
|
16242
|
+
} catch {
|
|
16243
|
+
return null;
|
|
16244
|
+
} finally {
|
|
16245
|
+
clearTimeout(timer);
|
|
16246
|
+
}
|
|
16247
|
+
}
|
|
16248
|
+
async function checkTemplateVersion(currentVersion, now = Date.now()) {
|
|
16249
|
+
if (isDisabled()) return null;
|
|
16250
|
+
const cache = await readTemplateCache();
|
|
16251
|
+
if (cache !== null && now - cache.lastCheck < ttlMs()) {
|
|
16252
|
+
if (compareVersions(currentVersion, cache.latest) < 0) return {
|
|
16253
|
+
current: currentVersion,
|
|
16254
|
+
latest: cache.latest
|
|
16255
|
+
};
|
|
16256
|
+
return null;
|
|
16257
|
+
}
|
|
16258
|
+
const latest = await fetchLatestTemplateVersion();
|
|
16259
|
+
if (latest === null) return null;
|
|
16260
|
+
try {
|
|
16261
|
+
await writeTemplateCache({
|
|
16262
|
+
latest,
|
|
16263
|
+
lastCheck: now
|
|
16264
|
+
});
|
|
16265
|
+
} catch {}
|
|
16266
|
+
if (compareVersions(currentVersion, latest) < 0) return {
|
|
16267
|
+
current: currentVersion,
|
|
16268
|
+
latest
|
|
16269
|
+
};
|
|
16270
|
+
return null;
|
|
16271
|
+
}
|
|
16272
|
+
function formatTemplateNotice(n, color = useColor()) {
|
|
16273
|
+
const dim = (s) => paint(s, "2", color);
|
|
16274
|
+
const yellow = (s) => paint(s, "33", color);
|
|
16275
|
+
const cyan = (s) => paint(s, "36", color);
|
|
16276
|
+
const bar = dim("│");
|
|
16277
|
+
return [
|
|
16278
|
+
"",
|
|
16279
|
+
bar,
|
|
16280
|
+
`${yellow("▲")} Newer templates available: ${dim(n.current)} → ${cyan(n.latest)}`,
|
|
16281
|
+
`${bar} Set ${cyan(`UNIVERSE_TEMPLATES_VERSION=${n.latest}`)} to use them.`,
|
|
16282
|
+
dim("└"),
|
|
16283
|
+
""
|
|
16284
|
+
].join("\n");
|
|
16285
|
+
}
|
|
16286
|
+
//#endregion
|
|
16066
16287
|
//#region src/commands/create/index.ts
|
|
16067
16288
|
const defaultFilesystemWriter = new LocalProjectWriter();
|
|
16068
16289
|
const create$1 = async (options, deps = {}) => {
|
|
@@ -16076,7 +16297,19 @@ const create$1 = async (options, deps = {}) => {
|
|
|
16076
16297
|
});
|
|
16077
16298
|
const platformManifestGenerator = deps.platformManifestGenerator ?? new PlatformManifestService();
|
|
16078
16299
|
const repoInitialiser = deps.repoInitialiser ?? new GitRepoInitialiser();
|
|
16300
|
+
const skillInstaller = deps.skillInstaller ?? new NpxSkillInstaller();
|
|
16301
|
+
const isTTY = process.stdin.isTTY;
|
|
16302
|
+
const spinner = deps.spinner ?? (options.json || !isTTY ? silentSpinner() : clackSpinner());
|
|
16079
16303
|
try {
|
|
16304
|
+
const templatesDir = process.env["UNIVERSE_TEMPLATES_DIR"];
|
|
16305
|
+
if (!(templatesDir && templatesDir.length > 0)) {
|
|
16306
|
+
const envVersion = process.env["UNIVERSE_TEMPLATES_VERSION"];
|
|
16307
|
+
const effectiveVersion = envVersion && envVersion.length > 0 ? envVersion : defaultTemplateVersion;
|
|
16308
|
+
try {
|
|
16309
|
+
const notice = await checkTemplateVersion(effectiveVersion);
|
|
16310
|
+
if (notice) process.stderr.write(formatTemplateNotice(notice));
|
|
16311
|
+
} catch {}
|
|
16312
|
+
}
|
|
16080
16313
|
const templateProvider = deps.templateProvider ?? new RemoteTemplateProvider();
|
|
16081
16314
|
const { labels, registry } = await templateProvider.loadLayers({ forceFetch: options.forceFetch });
|
|
16082
16315
|
const interactive = (deps.isTTY ?? Boolean(process.stdin.isTTY)) && !options.yes && !options.json;
|
|
@@ -16109,20 +16342,38 @@ const create$1 = async (options, deps = {}) => {
|
|
|
16109
16342
|
...pm !== void 0 ? { packageManager: pm } : {}
|
|
16110
16343
|
};
|
|
16111
16344
|
}
|
|
16345
|
+
spinner.start("Preparing your project");
|
|
16112
16346
|
const validatedInput = validator.validateCreateInput(selections);
|
|
16347
|
+
spinner.message("Composing project layers");
|
|
16113
16348
|
const resolvedLayers = await layerResolver.resolveLayers(validatedInput);
|
|
16114
16349
|
const targetDirectory = `${cwd}/${validatedInput.name}`;
|
|
16115
16350
|
const projectFiles = {
|
|
16116
16351
|
...resolvedLayers.files,
|
|
16117
16352
|
"platform.yaml": platformManifestGenerator.generatePlatformManifest(validatedInput)
|
|
16118
16353
|
};
|
|
16354
|
+
spinner.message("Writing project files");
|
|
16119
16355
|
await filesystemWriter.writeProject(targetDirectory, projectFiles);
|
|
16120
16356
|
const manager = validatedInput.packageManager;
|
|
16121
|
-
if (manager !== void 0)
|
|
16122
|
-
manager
|
|
16123
|
-
|
|
16124
|
-
|
|
16357
|
+
if (manager !== void 0) {
|
|
16358
|
+
spinner.message(`Pinning dependencies with ${manager}`);
|
|
16359
|
+
await packageManager.specifyDeps({
|
|
16360
|
+
manager,
|
|
16361
|
+
pmVersion: registry["package-managers"][manager]?.pmVersion ?? "",
|
|
16362
|
+
projectDirectory: targetDirectory
|
|
16363
|
+
});
|
|
16364
|
+
}
|
|
16365
|
+
const skills = registry.frameworks[validatedInput.framework]?.skills;
|
|
16366
|
+
if (skills && skills.length > 0) {
|
|
16367
|
+
spinner.message("Installing skills");
|
|
16368
|
+
try {
|
|
16369
|
+
await skillInstaller.installSkills(skills, targetDirectory);
|
|
16370
|
+
} catch (err) {
|
|
16371
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
16372
|
+
}
|
|
16373
|
+
}
|
|
16374
|
+
spinner.message("Initialising git repository");
|
|
16125
16375
|
await repoInitialiser.initialise(targetDirectory);
|
|
16376
|
+
spinner.stop("Project scaffolded");
|
|
16126
16377
|
if (options.json) {
|
|
16127
16378
|
emitJson(buildEnvelope("create", true, {
|
|
16128
16379
|
path: targetDirectory,
|
|
@@ -16135,9 +16386,11 @@ const create$1 = async (options, deps = {}) => {
|
|
|
16135
16386
|
}));
|
|
16136
16387
|
return;
|
|
16137
16388
|
}
|
|
16138
|
-
|
|
16139
|
-
|
|
16389
|
+
const startInstruction = `Project scaffolded. cd into ${validatedInput.name} and run \`docker compose up --watch\` to start the project`;
|
|
16390
|
+
if (interactive) logger.success(startInstruction);
|
|
16391
|
+
else logger.info(startInstruction);
|
|
16140
16392
|
} catch (err) {
|
|
16393
|
+
spinner.error("Create failed");
|
|
16141
16394
|
const code = err instanceof CliError ? err.exitCode : 10;
|
|
16142
16395
|
const message = err instanceof Error ? err.message : String(err);
|
|
16143
16396
|
outputError({
|
|
@@ -16566,6 +16819,21 @@ function suggest(target, candidates, threshold = 2) {
|
|
|
16566
16819
|
}
|
|
16567
16820
|
return bestD <= threshold ? best : null;
|
|
16568
16821
|
}
|
|
16822
|
+
const auditRowArraySchema = array(object({
|
|
16823
|
+
id: number(),
|
|
16824
|
+
occurredAt: string(),
|
|
16825
|
+
actor: string(),
|
|
16826
|
+
action: string(),
|
|
16827
|
+
site: string().optional(),
|
|
16828
|
+
deployId: string().optional(),
|
|
16829
|
+
outcome: string(),
|
|
16830
|
+
requestId: string().optional(),
|
|
16831
|
+
detail: record(string(), unknown()).optional()
|
|
16832
|
+
}));
|
|
16833
|
+
const deploySummaryArraySchema = array(object({
|
|
16834
|
+
deployId: string(),
|
|
16835
|
+
actor: string().optional()
|
|
16836
|
+
}));
|
|
16569
16837
|
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
16838
|
const visibilitySchema = _enum(["public", "private"]);
|
|
16571
16839
|
const repoStatusSchema = _enum([
|
|
@@ -16844,7 +17112,7 @@ function createProxyClient(cfg) {
|
|
|
16844
17112
|
Authorization: await userBearer(),
|
|
16845
17113
|
Accept: "application/json"
|
|
16846
17114
|
}
|
|
16847
|
-
});
|
|
17115
|
+
}, (raw) => deploySummaryArraySchema.parse(raw));
|
|
16848
17116
|
},
|
|
16849
17117
|
async getAlias(req) {
|
|
16850
17118
|
const url = `${base}/api/site/${encodeURIComponent(req.site)}/alias/${encodeURIComponent(req.mode)}`;
|
|
@@ -16968,6 +17236,23 @@ function createProxyClient(cfg) {
|
|
|
16968
17236
|
}
|
|
16969
17237
|
}, (raw) => repoRowArraySchema.parse(raw));
|
|
16970
17238
|
},
|
|
17239
|
+
async listAudit(req) {
|
|
17240
|
+
const params = new URLSearchParams();
|
|
17241
|
+
if (req?.site) params.set("site", req.site);
|
|
17242
|
+
if (req?.actor) params.set("actor", req.actor);
|
|
17243
|
+
if (req?.action) params.set("action", req.action);
|
|
17244
|
+
if (req?.since) params.set("since", req.since);
|
|
17245
|
+
if (req?.limit !== void 0) params.set("limit", String(req.limit));
|
|
17246
|
+
if (req?.offset !== void 0) params.set("offset", String(req.offset));
|
|
17247
|
+
const qs = params.toString();
|
|
17248
|
+
return call(`${base}/api/audit${qs ? `?${qs}` : ""}`, {
|
|
17249
|
+
method: "GET",
|
|
17250
|
+
headers: {
|
|
17251
|
+
Authorization: await userBearer(),
|
|
17252
|
+
Accept: "application/json"
|
|
17253
|
+
}
|
|
17254
|
+
}, (raw) => auditRowArraySchema.parse(raw));
|
|
17255
|
+
},
|
|
16971
17256
|
async getRepoRequest(id) {
|
|
16972
17257
|
return call(`${base}/api/repo/${encodeURIComponent(id)}`, {
|
|
16973
17258
|
method: "GET",
|
|
@@ -17869,24 +18154,26 @@ async function readSiteFromYaml(cwd, read) {
|
|
|
17869
18154
|
if (!r.ok) throw new ConfigError(r.error);
|
|
17870
18155
|
return r.value.site;
|
|
17871
18156
|
}
|
|
17872
|
-
function formatTable$
|
|
18157
|
+
function formatTable$2(deploys) {
|
|
17873
18158
|
const header = [
|
|
17874
18159
|
"DEPLOY ID",
|
|
17875
18160
|
"TIMESTAMP",
|
|
17876
18161
|
"SHA",
|
|
17877
|
-
"STATE"
|
|
18162
|
+
"STATE",
|
|
18163
|
+
"ACTOR"
|
|
17878
18164
|
];
|
|
17879
18165
|
const rows = deploys.map((d) => [
|
|
17880
18166
|
d.deployId,
|
|
17881
18167
|
d.timestamp ? d.timestamp.replace("T", " ").replace("Z", "") : "—",
|
|
17882
18168
|
d.sha ?? "—",
|
|
17883
|
-
d.state ?? "—"
|
|
18169
|
+
d.state ?? "—",
|
|
18170
|
+
d.actor ?? "—"
|
|
17884
18171
|
]);
|
|
17885
18172
|
const widths = header.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
17886
18173
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
|
|
17887
18174
|
return [fmt(header), ...rows.map(fmt)].join("\n");
|
|
17888
18175
|
}
|
|
17889
|
-
async function ls$
|
|
18176
|
+
async function ls$3(options, deps = {}) {
|
|
17890
18177
|
const cwd = deps.cwd ?? process.cwd();
|
|
17891
18178
|
const env = deps.env ?? process.env;
|
|
17892
18179
|
const readYaml = deps.readPlatformYaml ?? defaultReadPlatformYaml$2;
|
|
@@ -17907,7 +18194,8 @@ async function ls$2(options, deps = {}) {
|
|
|
17907
18194
|
getAuthToken: () => identity.token,
|
|
17908
18195
|
timeoutMs: parseFetchTimeoutMs(env)
|
|
17909
18196
|
});
|
|
17910
|
-
const
|
|
18197
|
+
const sorted = [...await client.siteDeploys({ site })].sort((a, b) => b.deployId.localeCompare(a.deployId));
|
|
18198
|
+
const parsed = sorted.map((d) => parseDeployId(d.deployId));
|
|
17911
18199
|
let previewId = null;
|
|
17912
18200
|
let productionId = null;
|
|
17913
18201
|
if (parsed.length > 0) {
|
|
@@ -17921,9 +18209,10 @@ async function ls$2(options, deps = {}) {
|
|
|
17921
18209
|
previewId = preview?.deployId ?? null;
|
|
17922
18210
|
productionId = production?.deployId ?? null;
|
|
17923
18211
|
}
|
|
17924
|
-
const deploys = parsed.map((d) => ({
|
|
18212
|
+
const deploys = parsed.map((d, i) => ({
|
|
17925
18213
|
...d,
|
|
17926
|
-
state: deployState(d.deployId, previewId, productionId)
|
|
18214
|
+
state: deployState(d.deployId, previewId, productionId),
|
|
18215
|
+
actor: sorted[i]?.actor
|
|
17927
18216
|
}));
|
|
17928
18217
|
if (options.json) {
|
|
17929
18218
|
emitJson(buildEnvelope("ls", true, {
|
|
@@ -17941,7 +18230,7 @@ async function ls$2(options, deps = {}) {
|
|
|
17941
18230
|
info(`(no deploys for ${site})`);
|
|
17942
18231
|
return;
|
|
17943
18232
|
}
|
|
17944
|
-
success(formatTable$
|
|
18233
|
+
success(formatTable$2(deploys));
|
|
17945
18234
|
} catch (err) {
|
|
17946
18235
|
const { code, message } = wrapProxyError("ls", err);
|
|
17947
18236
|
outputError({
|
|
@@ -18241,7 +18530,7 @@ function parseTeamsFlag(raw) {
|
|
|
18241
18530
|
* Resolve identity + construct a proxy client. Throws CredentialError
|
|
18242
18531
|
* if no identity is available — caller wraps via wrapProxyError.
|
|
18243
18532
|
*/
|
|
18244
|
-
async function setupClient$
|
|
18533
|
+
async function setupClient$2(deps) {
|
|
18245
18534
|
const env = deps.env ?? process.env;
|
|
18246
18535
|
const resolveId = deps.resolveIdentity ?? resolveIdentity;
|
|
18247
18536
|
const mkClient = deps.createProxyClient ?? createProxyClient;
|
|
@@ -18258,7 +18547,7 @@ async function setupClient$1(deps) {
|
|
|
18258
18547
|
}
|
|
18259
18548
|
//#endregion
|
|
18260
18549
|
//#region src/commands/sites/ls.ts
|
|
18261
|
-
function formatTable(rows) {
|
|
18550
|
+
function formatTable$1(rows) {
|
|
18262
18551
|
if (rows.length === 0) return "No registered sites.";
|
|
18263
18552
|
const headers = [
|
|
18264
18553
|
"SLUG",
|
|
@@ -18276,13 +18565,13 @@ function formatTable(rows) {
|
|
|
18276
18565
|
const fmt = (row) => row.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
|
|
18277
18566
|
return [fmt(headers), ...cells.map(fmt)].join("\n");
|
|
18278
18567
|
}
|
|
18279
|
-
async function ls$
|
|
18568
|
+
async function ls$2(options, deps = {}) {
|
|
18280
18569
|
const command = "sites ls";
|
|
18281
18570
|
const success = deps.logSuccess ?? ((s) => R.message(s));
|
|
18282
18571
|
const error = deps.logError ?? ((s) => R.error(s));
|
|
18283
18572
|
const exit = deps.exit ?? exitWithCode;
|
|
18284
18573
|
try {
|
|
18285
|
-
const { client, identitySource } = await setupClient$
|
|
18574
|
+
const { client, identitySource } = await setupClient$2(deps);
|
|
18286
18575
|
let rows = await client.listSites();
|
|
18287
18576
|
let scope = "all";
|
|
18288
18577
|
if (options.mine) {
|
|
@@ -18297,7 +18586,7 @@ async function ls$1(options, deps = {}) {
|
|
|
18297
18586
|
sites: rows,
|
|
18298
18587
|
identitySource
|
|
18299
18588
|
}));
|
|
18300
|
-
else success(formatTable(rows));
|
|
18589
|
+
else success(formatTable$1(rows));
|
|
18301
18590
|
} catch (err) {
|
|
18302
18591
|
const { code, message } = wrapProxyError(command, err);
|
|
18303
18592
|
outputError({
|
|
@@ -18317,7 +18606,7 @@ async function register(options, deps = {}) {
|
|
|
18317
18606
|
try {
|
|
18318
18607
|
if (!options.slug || options.slug.trim().length === 0) throw new UsageError("slug is required (positional argument)");
|
|
18319
18608
|
const teams = parseTeamsFlag(options.team);
|
|
18320
|
-
const { client, identitySource } = await setupClient$
|
|
18609
|
+
const { client, identitySource } = await setupClient$2(deps);
|
|
18321
18610
|
const row = await client.registerSite({
|
|
18322
18611
|
slug: options.slug,
|
|
18323
18612
|
teams: teams.length > 0 ? teams : void 0
|
|
@@ -18355,7 +18644,7 @@ async function rm$1(options, deps = {}) {
|
|
|
18355
18644
|
const exit = deps.exit ?? exitWithCode;
|
|
18356
18645
|
try {
|
|
18357
18646
|
if (!options.slug || options.slug.trim().length === 0) throw new UsageError("slug is required (positional argument)");
|
|
18358
|
-
const { client, identitySource } = await setupClient$
|
|
18647
|
+
const { client, identitySource } = await setupClient$2(deps);
|
|
18359
18648
|
await client.deleteSite({ slug: options.slug });
|
|
18360
18649
|
if (options.json) emitJson(buildEnvelope(command, true, {
|
|
18361
18650
|
slug: options.slug,
|
|
@@ -18388,7 +18677,7 @@ async function update(options, deps = {}) {
|
|
|
18388
18677
|
if (!options.slug || options.slug.trim().length === 0) throw new UsageError("slug is required (positional argument)");
|
|
18389
18678
|
const teams = parseTeamsFlag(options.team);
|
|
18390
18679
|
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$
|
|
18680
|
+
const { client, identitySource } = await setupClient$2(deps);
|
|
18392
18681
|
const row = await client.updateSite({
|
|
18393
18682
|
slug: options.slug,
|
|
18394
18683
|
teams
|
|
@@ -18427,7 +18716,7 @@ const defaultRepoPrompts = {
|
|
|
18427
18716
|
* Resolve identity + construct a proxy client. Throws CredentialError
|
|
18428
18717
|
* when no identity is available — the caller wraps via wrapProxyError.
|
|
18429
18718
|
*/
|
|
18430
|
-
async function setupClient(deps) {
|
|
18719
|
+
async function setupClient$1(deps) {
|
|
18431
18720
|
const env = deps.env ?? process.env;
|
|
18432
18721
|
const resolveId = deps.resolveIdentity ?? resolveIdentity;
|
|
18433
18722
|
const mkClient = deps.createProxyClient ?? createProxyClient;
|
|
@@ -18447,6 +18736,26 @@ async function setupClient(deps) {
|
|
|
18447
18736
|
* Render repo-request rows as an aligned text table. Returns `emptyMsg`
|
|
18448
18737
|
* when there are no rows (the caller passes a status-specific phrase).
|
|
18449
18738
|
*/
|
|
18739
|
+
function humanizeDuration(ms) {
|
|
18740
|
+
const s = Math.floor(ms / 1e3);
|
|
18741
|
+
if (s < 60) return `${s}s`;
|
|
18742
|
+
const m = Math.floor(s / 60);
|
|
18743
|
+
if (m < 60) return `${m}m`;
|
|
18744
|
+
const h = Math.floor(m / 60);
|
|
18745
|
+
if (h < 24) return `${h}h${m % 60}m`;
|
|
18746
|
+
return `${Math.floor(h / 24)}d${h % 24}h`;
|
|
18747
|
+
}
|
|
18748
|
+
const RESOLVED_STATUSES = new Set([
|
|
18749
|
+
"active",
|
|
18750
|
+
"rejected",
|
|
18751
|
+
"failed"
|
|
18752
|
+
]);
|
|
18753
|
+
function resolveLatency(r) {
|
|
18754
|
+
if (!RESOLVED_STATUSES.has(r.status)) return "";
|
|
18755
|
+
const ms = Date.parse(r.updatedAt) - Date.parse(r.createdAt);
|
|
18756
|
+
if (!Number.isFinite(ms) || ms < 0) return "";
|
|
18757
|
+
return humanizeDuration(ms);
|
|
18758
|
+
}
|
|
18450
18759
|
function formatRepoTable(rows, emptyMsg = "No repo requests.") {
|
|
18451
18760
|
if (rows.length === 0) return emptyMsg;
|
|
18452
18761
|
const headers = [
|
|
@@ -18455,7 +18764,9 @@ function formatRepoTable(rows, emptyMsg = "No repo requests.") {
|
|
|
18455
18764
|
"VIS",
|
|
18456
18765
|
"STATUS",
|
|
18457
18766
|
"REQUESTED BY",
|
|
18458
|
-
"REQUESTED AT"
|
|
18767
|
+
"REQUESTED AT",
|
|
18768
|
+
"APPROVER",
|
|
18769
|
+
"LATENCY"
|
|
18459
18770
|
];
|
|
18460
18771
|
const cells = rows.map((r) => [
|
|
18461
18772
|
r.id,
|
|
@@ -18463,7 +18774,9 @@ function formatRepoTable(rows, emptyMsg = "No repo requests.") {
|
|
|
18463
18774
|
r.visibility,
|
|
18464
18775
|
r.status,
|
|
18465
18776
|
r.requestedBy,
|
|
18466
|
-
r.createdAt
|
|
18777
|
+
r.createdAt,
|
|
18778
|
+
r.approver ?? "",
|
|
18779
|
+
resolveLatency(r)
|
|
18467
18780
|
]);
|
|
18468
18781
|
const widths = headers.map((h, i) => Math.max(h.length, ...cells.map((row) => row[i]?.length ?? 0)));
|
|
18469
18782
|
const fmt = (row) => row.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
|
|
@@ -18482,7 +18795,7 @@ async function approve(options, deps = {}) {
|
|
|
18482
18795
|
let identitySource;
|
|
18483
18796
|
try {
|
|
18484
18797
|
if (!options.id || options.id.trim().length === 0) throw new UsageError("request id is required (positional argument)");
|
|
18485
|
-
const setup = await setupClient(deps);
|
|
18798
|
+
const setup = await setupClient$1(deps);
|
|
18486
18799
|
const client = setup.client;
|
|
18487
18800
|
identitySource = setup.identitySource;
|
|
18488
18801
|
if (!options.json && !options.yes) {
|
|
@@ -18600,7 +18913,7 @@ async function create(options, deps = {}) {
|
|
|
18600
18913
|
let client;
|
|
18601
18914
|
const ensureClient = async () => {
|
|
18602
18915
|
if (!client) {
|
|
18603
|
-
const setup = await setupClient(deps);
|
|
18916
|
+
const setup = await setupClient$1(deps);
|
|
18604
18917
|
client = setup.client;
|
|
18605
18918
|
identitySource = setup.identitySource;
|
|
18606
18919
|
}
|
|
@@ -18702,7 +19015,7 @@ async function create(options, deps = {}) {
|
|
|
18702
19015
|
//#region src/commands/repo/ls.ts
|
|
18703
19016
|
/** Closed set accepted by `--status`: the row statuses plus `all`. */
|
|
18704
19017
|
const LS_STATUSES = [...repoStatusSchema.options, "all"];
|
|
18705
|
-
async function ls(options, deps = {}) {
|
|
19018
|
+
async function ls$1(options, deps = {}) {
|
|
18706
19019
|
const command = "repo ls";
|
|
18707
19020
|
const message = deps.logMessage ?? ((s) => R.message(s));
|
|
18708
19021
|
const error = deps.logError ?? ((s) => R.error(s));
|
|
@@ -18711,7 +19024,7 @@ async function ls(options, deps = {}) {
|
|
|
18711
19024
|
try {
|
|
18712
19025
|
const requestedStatus = options.all ? "all" : options.status;
|
|
18713
19026
|
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);
|
|
19027
|
+
const setup = await setupClient$1(deps);
|
|
18715
19028
|
const client = setup.client;
|
|
18716
19029
|
identitySource = setup.identitySource;
|
|
18717
19030
|
const rows = await client.listRepoRequests({
|
|
@@ -18753,7 +19066,7 @@ async function reject(options, deps = {}) {
|
|
|
18753
19066
|
let identitySource;
|
|
18754
19067
|
try {
|
|
18755
19068
|
if (!options.id || options.id.trim().length === 0) throw new UsageError("request id is required (positional argument)");
|
|
18756
|
-
const setup = await setupClient(deps);
|
|
19069
|
+
const setup = await setupClient$1(deps);
|
|
18757
19070
|
const client = setup.client;
|
|
18758
19071
|
identitySource = setup.identitySource;
|
|
18759
19072
|
if (!options.json && !options.yes) {
|
|
@@ -18806,7 +19119,7 @@ async function rm(options, deps = {}) {
|
|
|
18806
19119
|
let identitySource;
|
|
18807
19120
|
try {
|
|
18808
19121
|
if (!options.id || options.id.trim().length === 0) throw new UsageError("request id is required (positional argument)");
|
|
18809
|
-
const setup = await setupClient(deps);
|
|
19122
|
+
const setup = await setupClient$1(deps);
|
|
18810
19123
|
const client = setup.client;
|
|
18811
19124
|
identitySource = setup.identitySource;
|
|
18812
19125
|
if (!options.json && !options.yes) {
|
|
@@ -18864,7 +19177,7 @@ async function status(options, deps = {}) {
|
|
|
18864
19177
|
let identitySource;
|
|
18865
19178
|
try {
|
|
18866
19179
|
if (!options.id || options.id.trim().length === 0) throw new UsageError("request id is required (positional argument)");
|
|
18867
|
-
const setup = await setupClient(deps);
|
|
19180
|
+
const setup = await setupClient$1(deps);
|
|
18868
19181
|
const client = setup.client;
|
|
18869
19182
|
identitySource = setup.identitySource;
|
|
18870
19183
|
const row = await client.getRepoRequest(options.id);
|
|
@@ -18888,22 +19201,97 @@ async function status(options, deps = {}) {
|
|
|
18888
19201
|
}
|
|
18889
19202
|
}
|
|
18890
19203
|
//#endregion
|
|
19204
|
+
//#region src/commands/audit/_shared.ts
|
|
19205
|
+
async function setupClient(deps) {
|
|
19206
|
+
const env = deps.env ?? process.env;
|
|
19207
|
+
const resolveId = deps.resolveIdentity ?? resolveIdentity;
|
|
19208
|
+
const mkClient = deps.createProxyClient ?? createProxyClient;
|
|
19209
|
+
const identity = await resolveId({ env });
|
|
19210
|
+
if (!identity) throw new CredentialError("No GitHub identity available. Run `universe login`, set $GITHUB_TOKEN, or install the gh CLI.");
|
|
19211
|
+
return {
|
|
19212
|
+
client: mkClient({
|
|
19213
|
+
baseUrl: env["UNIVERSE_PROXY_URL"] ?? "https://uploads.freecode.camp",
|
|
19214
|
+
getAuthToken: () => identity.token,
|
|
19215
|
+
timeoutMs: parseFetchTimeoutMs(env)
|
|
19216
|
+
}),
|
|
19217
|
+
identitySource: identity.source
|
|
19218
|
+
};
|
|
19219
|
+
}
|
|
19220
|
+
//#endregion
|
|
19221
|
+
//#region src/commands/audit/ls.ts
|
|
19222
|
+
function formatTable(rows) {
|
|
19223
|
+
if (rows.length === 0) return "No audit events.";
|
|
19224
|
+
const headers = [
|
|
19225
|
+
"OCCURRED AT",
|
|
19226
|
+
"ACTOR",
|
|
19227
|
+
"ACTION",
|
|
19228
|
+
"TARGET",
|
|
19229
|
+
"OUTCOME"
|
|
19230
|
+
];
|
|
19231
|
+
const cells = rows.map((r) => [
|
|
19232
|
+
r.occurredAt,
|
|
19233
|
+
r.actor,
|
|
19234
|
+
r.action,
|
|
19235
|
+
targetOf(r),
|
|
19236
|
+
r.outcome
|
|
19237
|
+
]);
|
|
19238
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...cells.map((row) => row[i]?.length ?? 0)));
|
|
19239
|
+
const fmt = (row) => row.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ");
|
|
19240
|
+
return [fmt(headers), ...cells.map(fmt)].join("\n");
|
|
19241
|
+
}
|
|
19242
|
+
function targetFromDetail(r) {
|
|
19243
|
+
const name = r.detail?.["name"];
|
|
19244
|
+
return typeof name === "string" ? name : "";
|
|
19245
|
+
}
|
|
19246
|
+
function targetOf(r) {
|
|
19247
|
+
if (r.site && r.deployId) return `${r.site}/${r.deployId}`;
|
|
19248
|
+
return r.site || r.deployId || targetFromDetail(r);
|
|
19249
|
+
}
|
|
19250
|
+
async function ls(options, deps = {}) {
|
|
19251
|
+
const command = "audit ls";
|
|
19252
|
+
const message = deps.logMessage ?? ((s) => R.message(s));
|
|
19253
|
+
const error = deps.logError ?? ((s) => R.error(s));
|
|
19254
|
+
const exit = deps.exit ?? exitWithCode;
|
|
19255
|
+
let identitySource;
|
|
19256
|
+
try {
|
|
19257
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 0)) throw new UsageError("--limit must be a non-negative integer");
|
|
19258
|
+
const setup = await setupClient(deps);
|
|
19259
|
+
identitySource = setup.identitySource;
|
|
19260
|
+
const rows = await setup.client.listAudit({
|
|
19261
|
+
site: options.site,
|
|
19262
|
+
actor: options.actor,
|
|
19263
|
+
action: options.action,
|
|
19264
|
+
since: options.since,
|
|
19265
|
+
limit: options.limit
|
|
19266
|
+
});
|
|
19267
|
+
if (options.json) emitJson(buildEnvelope(command, true, {
|
|
19268
|
+
count: rows.length,
|
|
19269
|
+
events: rows,
|
|
19270
|
+
identitySource
|
|
19271
|
+
}));
|
|
19272
|
+
else message(formatTable(rows));
|
|
19273
|
+
} catch (err) {
|
|
19274
|
+
const { code, message: msg, kind, requestId } = wrapProxyError(command, err);
|
|
19275
|
+
outputError({
|
|
19276
|
+
json: options.json,
|
|
19277
|
+
command
|
|
19278
|
+
}, code, msg, {
|
|
19279
|
+
logError: error,
|
|
19280
|
+
kind,
|
|
19281
|
+
requestId,
|
|
19282
|
+
extras: identitySource ? { identitySource } : void 0
|
|
19283
|
+
});
|
|
19284
|
+
exit(code);
|
|
19285
|
+
}
|
|
19286
|
+
}
|
|
19287
|
+
//#endregion
|
|
18891
19288
|
//#region src/lib/update-notifier.ts
|
|
18892
19289
|
const APP_DIR = "universe-cli";
|
|
18893
19290
|
const CACHE_FILE = "update-check.json";
|
|
18894
19291
|
const PKG_NAME = "@freecodecamp/universe-cli";
|
|
18895
19292
|
const NPM_LATEST_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
18896
|
-
const DEFAULT_TTL_MS = 3600 * 1e3;
|
|
18897
19293
|
const FETCH_TIMEOUT_MS = 3e3;
|
|
18898
19294
|
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
19295
|
function latestUrl() {
|
|
18908
19296
|
const override = process.env["UNIVERSE_UPDATE_URL"];
|
|
18909
19297
|
return override && override.length > 0 ? override : NPM_LATEST_URL;
|
|
@@ -18916,25 +19304,6 @@ function configBase() {
|
|
|
18916
19304
|
function cachePath() {
|
|
18917
19305
|
return (0, node_path.join)(configBase(), APP_DIR, CACHE_FILE);
|
|
18918
19306
|
}
|
|
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
19307
|
async function readCache() {
|
|
18939
19308
|
try {
|
|
18940
19309
|
return parseCache(await (0, node_fs_promises.readFile)(cachePath(), "utf-8"));
|
|
@@ -18974,29 +19343,6 @@ async function fetchLatest() {
|
|
|
18974
19343
|
clearTimeout(timer);
|
|
18975
19344
|
}
|
|
18976
19345
|
}
|
|
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
19346
|
async function refreshIfStale(now = Date.now(), options = {}) {
|
|
19001
19347
|
if (isDisabled()) return;
|
|
19002
19348
|
if (!options.force) {
|
|
@@ -19042,14 +19388,6 @@ function getNoticeSync(current) {
|
|
|
19042
19388
|
latest: cache.latest
|
|
19043
19389
|
};
|
|
19044
19390
|
}
|
|
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
19391
|
function formatNotice(n, color = useColor()) {
|
|
19054
19392
|
const dim = (s) => paint(s, "2", color);
|
|
19055
19393
|
const yellow = (s) => paint(s, "33", color);
|
|
@@ -19081,7 +19419,7 @@ function installExitNotice(current) {
|
|
|
19081
19419
|
}
|
|
19082
19420
|
//#endregion
|
|
19083
19421
|
//#region src/cli.ts
|
|
19084
|
-
const version = "0.
|
|
19422
|
+
const version = "0.13.0";
|
|
19085
19423
|
function handleActionError(command, json, err) {
|
|
19086
19424
|
const ctx = {
|
|
19087
19425
|
json,
|
|
@@ -19125,6 +19463,7 @@ async function run(argv = process.argv) {
|
|
|
19125
19463
|
const sitesCli = namespaceGroup("sites", "Static site registry commands");
|
|
19126
19464
|
const staticCli = namespaceGroup("static", "Static site deployment commands");
|
|
19127
19465
|
const repoCli = namespaceGroup("repo", "Repository creation + approval queue commands");
|
|
19466
|
+
const auditCli = namespaceGroup("audit", "Audit trail query commands");
|
|
19128
19467
|
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
19468
|
const opts = cmd.optsWithGlobals();
|
|
19130
19469
|
try {
|
|
@@ -19140,7 +19479,7 @@ async function run(argv = process.argv) {
|
|
|
19140
19479
|
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
19480
|
const opts = cmd.optsWithGlobals();
|
|
19142
19481
|
try {
|
|
19143
|
-
await ls$
|
|
19482
|
+
await ls$2({
|
|
19144
19483
|
json: opts.json ?? false,
|
|
19145
19484
|
mine: opts.mine ?? false
|
|
19146
19485
|
});
|
|
@@ -19189,7 +19528,7 @@ async function run(argv = process.argv) {
|
|
|
19189
19528
|
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
19529
|
const opts = cmd.optsWithGlobals();
|
|
19191
19530
|
try {
|
|
19192
|
-
await ls({
|
|
19531
|
+
await ls$1({
|
|
19193
19532
|
json: opts.json ?? false,
|
|
19194
19533
|
status: opts.status,
|
|
19195
19534
|
mine: opts.mine ?? false,
|
|
@@ -19284,7 +19623,7 @@ async function run(argv = process.argv) {
|
|
|
19284
19623
|
staticCli.command("ls").description("List recent deploys for a site").option("--site <site>", "Override site from platform.yaml").action(async (_opts, cmd) => {
|
|
19285
19624
|
const opts = cmd.optsWithGlobals();
|
|
19286
19625
|
try {
|
|
19287
|
-
await ls$
|
|
19626
|
+
await ls$3({
|
|
19288
19627
|
json: opts.json ?? false,
|
|
19289
19628
|
site: opts.site
|
|
19290
19629
|
});
|
|
@@ -19351,9 +19690,25 @@ async function run(argv = process.argv) {
|
|
|
19351
19690
|
handleActionError("whoami", opts.json ?? false, err);
|
|
19352
19691
|
}
|
|
19353
19692
|
});
|
|
19693
|
+
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) => {
|
|
19694
|
+
const opts = cmd.optsWithGlobals();
|
|
19695
|
+
try {
|
|
19696
|
+
await ls({
|
|
19697
|
+
json: opts.json ?? false,
|
|
19698
|
+
actor: opts.actor,
|
|
19699
|
+
action: opts.action,
|
|
19700
|
+
site: opts.site,
|
|
19701
|
+
since: opts.since,
|
|
19702
|
+
limit: opts.limit
|
|
19703
|
+
});
|
|
19704
|
+
} catch (err) {
|
|
19705
|
+
handleActionError("audit ls", opts.json ?? false, err);
|
|
19706
|
+
}
|
|
19707
|
+
});
|
|
19354
19708
|
cli.addCommand(staticCli);
|
|
19355
19709
|
cli.addCommand(sitesCli);
|
|
19356
19710
|
cli.addCommand(repoCli);
|
|
19711
|
+
cli.addCommand(auditCli);
|
|
19357
19712
|
if (args.length === 0) {
|
|
19358
19713
|
cli.outputHelp();
|
|
19359
19714
|
return;
|
package/package.json
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freecodecamp/universe-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "Static site deployment CLI for the freeCodeCamp Universe platform",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"deployment",
|
|
8
|
+
"freecodecamp",
|
|
9
|
+
"static-site"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/freeCodeCamp-Universe/universe-cli#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/freeCodeCamp-Universe/universe-cli/issues"
|
|
13
14
|
},
|
|
14
|
-
"author": "Mrugesh Mohapatra <noreply@mrugesh.dev> (https://mrugesh.dev/)",
|
|
15
15
|
"license": "BSD-3-Clause",
|
|
16
|
+
"author": "Mrugesh Mohapatra <noreply@mrugesh.dev> (https://mrugesh.dev/)",
|
|
16
17
|
"repository": {
|
|
17
18
|
"type": "git",
|
|
18
19
|
"url": "git+https://github.com/freeCodeCamp-Universe/universe-cli.git"
|
|
19
20
|
},
|
|
20
|
-
"
|
|
21
|
-
"
|
|
21
|
+
"bin": {
|
|
22
|
+
"universe": "dist/index.cjs"
|
|
22
23
|
},
|
|
23
24
|
"files": [
|
|
24
25
|
"dist/",
|
|
@@ -26,8 +27,20 @@
|
|
|
26
27
|
"LICENSE"
|
|
27
28
|
],
|
|
28
29
|
"type": "module",
|
|
29
|
-
"
|
|
30
|
-
|
|
30
|
+
"main": "dist/index.cjs",
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:smoke": "UNIVERSE_E2E_REAL=1 vitest run tests/e2e/smoke-real-artemis.test.ts",
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"lint": "oxlint src tests",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"prepare": "husky",
|
|
41
|
+
"dev": "tsx src/index.ts",
|
|
42
|
+
"fmt": "oxfmt",
|
|
43
|
+
"fmt:check": "oxfmt --check"
|
|
31
44
|
},
|
|
32
45
|
"dependencies": {
|
|
33
46
|
"@clack/prompts": "^1.5.0",
|
|
@@ -38,6 +51,7 @@
|
|
|
38
51
|
"devDependencies": {
|
|
39
52
|
"@types/node": "^24.12.2",
|
|
40
53
|
"husky": "^9.1.7",
|
|
54
|
+
"oxfmt": "^0.59.0",
|
|
41
55
|
"oxlint": "^1.68.0",
|
|
42
56
|
"postject": "1.0.0-alpha.6",
|
|
43
57
|
"tsdown": "0.22.1",
|
|
@@ -45,19 +59,8 @@
|
|
|
45
59
|
"typescript": "^5.9.3",
|
|
46
60
|
"vitest": "^3.2.4"
|
|
47
61
|
},
|
|
48
|
-
"packageManager": "pnpm@10.33.0",
|
|
49
62
|
"engines": {
|
|
50
63
|
"node": ">=24.0.0"
|
|
51
64
|
},
|
|
52
|
-
"
|
|
53
|
-
"homepage": "https://github.com/freeCodeCamp-Universe/universe-cli#readme",
|
|
54
|
-
"bugs": {
|
|
55
|
-
"url": "https://github.com/freeCodeCamp-Universe/universe-cli/issues"
|
|
56
|
-
},
|
|
57
|
-
"keywords": [
|
|
58
|
-
"cli",
|
|
59
|
-
"static-site",
|
|
60
|
-
"deployment",
|
|
61
|
-
"freecodecamp"
|
|
62
|
-
]
|
|
65
|
+
"packageManager": "pnpm@10.33.0"
|
|
63
66
|
}
|