@theholocron/cli 5.0.0-alpha.22 → 5.0.0-alpha.23
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/cli.mjs +130 -10
- package/dist/cli.mjs.map +1 -1
- package/package.json +4 -4
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
-
import path, { basename, dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import path, { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { checkbox, input, select } from "@inquirer/prompts";
|
|
6
6
|
import { KNOWN_WORKFLOWS, createAstromech, extractPreviewConfig, readWorkspacePackages, reusableTemplates } from "@theholocron/astromech";
|
|
7
7
|
import { loadTasksConfig } from "@theholocron/astromech/config";
|
|
@@ -1233,6 +1233,30 @@ function commandsInContext(context) {
|
|
|
1233
1233
|
}
|
|
1234
1234
|
//#endregion
|
|
1235
1235
|
//#region src/commands/deploy.ts
|
|
1236
|
+
/**
|
|
1237
|
+
* `holocron deploy` — trigger a deployment via the configured
|
|
1238
|
+
* `deployment` capability.
|
|
1239
|
+
*
|
|
1240
|
+
* Two modes, one command: `runDeploy` (the default — a Git branch,
|
|
1241
|
+
* `triggerDeployment()`) and `runDeployFromFiles` (`--files <dir>` —
|
|
1242
|
+
* a local directory of source files, `deployFunction()`, no linked
|
|
1243
|
+
* repo required — for a consumer with no Git history to deploy from,
|
|
1244
|
+
* e.g. Sentinel, shipped as an npm package). Separate functions, not
|
|
1245
|
+
* one branching `runDeploy`, because their inputs genuinely differ
|
|
1246
|
+
* (`branch` vs `dir`) and their result shapes differ too
|
|
1247
|
+
* (`DeploymentRecord` carries `status`/`branch`; `DeployFunctionResult`
|
|
1248
|
+
* is deliberately minimal, same as `DeployScriptResult` for Workers).
|
|
1249
|
+
*
|
|
1250
|
+
* Errors clearly when `deployment` isn't loaded (no provider declared
|
|
1251
|
+
* in `holocron.config.json`), or when the configured provider doesn't
|
|
1252
|
+
* implement `deployFunction` (it's optional on `Deployment` — not
|
|
1253
|
+
* every provider has a files-based deploy API).
|
|
1254
|
+
*
|
|
1255
|
+
* Dry-run skips the actual deploy call but still verifies the
|
|
1256
|
+
* deployment capability (and, for `--files`, `deployFunction`) is
|
|
1257
|
+
* present, so operators can sanity-check the wiring without spinning
|
|
1258
|
+
* up a build.
|
|
1259
|
+
*/
|
|
1236
1260
|
async function runDeploy(input) {
|
|
1237
1261
|
const print = input.print ?? ((line) => console.log(line));
|
|
1238
1262
|
const logger = input.logger ?? getLogger();
|
|
@@ -1288,6 +1312,88 @@ async function runDeploy(input) {
|
|
|
1288
1312
|
};
|
|
1289
1313
|
}
|
|
1290
1314
|
}
|
|
1315
|
+
const SKIP_DIRS$2 = /* @__PURE__ */ new Set([
|
|
1316
|
+
".git",
|
|
1317
|
+
"node_modules",
|
|
1318
|
+
"dist",
|
|
1319
|
+
".turbo"
|
|
1320
|
+
]);
|
|
1321
|
+
/** Recursively lists absolute file paths under `dir`, skipping VCS/build noise. */
|
|
1322
|
+
function defaultWalkFiles$2(dir) {
|
|
1323
|
+
const results = [];
|
|
1324
|
+
for (const entry of readdirSync(dir)) {
|
|
1325
|
+
if (SKIP_DIRS$2.has(entry)) continue;
|
|
1326
|
+
const full = join(dir, entry);
|
|
1327
|
+
const stat = statSync(full);
|
|
1328
|
+
if (stat.isDirectory()) results.push(...defaultWalkFiles$2(full));
|
|
1329
|
+
else if (stat.isFile()) results.push(full);
|
|
1330
|
+
}
|
|
1331
|
+
return results;
|
|
1332
|
+
}
|
|
1333
|
+
/** `holocron deploy --files <dir>` — the non-git counterpart to `runDeploy`. */
|
|
1334
|
+
async function runDeployFromFiles(input) {
|
|
1335
|
+
const print = input.print ?? ((line) => console.log(line));
|
|
1336
|
+
const logger = input.logger ?? getLogger();
|
|
1337
|
+
const loader = input.loader ?? new PluginLoader(input.loaded.resolved, input.context);
|
|
1338
|
+
await loader.load();
|
|
1339
|
+
assertPluginsResolvable(loader, "deploy");
|
|
1340
|
+
const dryRun = input.context.dryRun ?? false;
|
|
1341
|
+
const walkFiles = input.walkFiles ?? defaultWalkFiles$2;
|
|
1342
|
+
const readFile = input.readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
1343
|
+
const files = {};
|
|
1344
|
+
for (const abs of walkFiles(input.dir)) {
|
|
1345
|
+
const rel = relative(input.dir, abs).split(sep).join("/");
|
|
1346
|
+
files[rel] = readFile(abs);
|
|
1347
|
+
}
|
|
1348
|
+
const fileCount = Object.keys(files).length;
|
|
1349
|
+
logger.info({
|
|
1350
|
+
dir: input.dir,
|
|
1351
|
+
fileCount,
|
|
1352
|
+
target: input.target ?? "preview",
|
|
1353
|
+
projectId: input.projectId,
|
|
1354
|
+
dryRun: dryRun || void 0
|
|
1355
|
+
}, "deploy: start (files)");
|
|
1356
|
+
print(style.header(`Holocron deploy — files=${input.dir} (${fileCount} file${fileCount === 1 ? "" : "s"})${input.target ? `, target=${input.target}` : " (preview)"}${dryRun ? " (dry-run)" : ""}`));
|
|
1357
|
+
if (!loader.has("deployment")) throw new Error("deployment capability is not configured — add a `deployment` provider to holocron.config.json");
|
|
1358
|
+
const deploy = loader.get("deployment");
|
|
1359
|
+
if (!deploy.deployFunction) throw new Error(`deployment provider "${deploy.providerName}" does not support deploying from files — deployFunction() is not implemented`);
|
|
1360
|
+
if (dryRun) {
|
|
1361
|
+
const message = `would: ${deploy.providerName}.deployFunction(projectId=${input.projectId}, files=${fileCount}${input.target ? `, target=${input.target}` : ""})`;
|
|
1362
|
+
print(` ${style.dim(`… ${message}`)}`);
|
|
1363
|
+
return {
|
|
1364
|
+
deployment: null,
|
|
1365
|
+
status: "dry-run",
|
|
1366
|
+
message
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
try {
|
|
1370
|
+
const result = await withSpinner(`Deploying ${fileCount} file${fileCount === 1 ? "" : "s"} from ${input.dir}…`, () => deploy.deployFunction(input.projectId, {
|
|
1371
|
+
files,
|
|
1372
|
+
...input.target ? { target: input.target } : {}
|
|
1373
|
+
}));
|
|
1374
|
+
print(` ${style.success(result.url)}`);
|
|
1375
|
+
logger.info({
|
|
1376
|
+
url: result.url,
|
|
1377
|
+
id: result.deploymentId
|
|
1378
|
+
}, "deploy: triggered (files)");
|
|
1379
|
+
return {
|
|
1380
|
+
deployment: result,
|
|
1381
|
+
status: "ok"
|
|
1382
|
+
};
|
|
1383
|
+
} catch (err) {
|
|
1384
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1385
|
+
print(` ${style.fail(message)}`);
|
|
1386
|
+
logger.warn({
|
|
1387
|
+
dir: input.dir,
|
|
1388
|
+
reason: message
|
|
1389
|
+
}, "deploy: failed (files)");
|
|
1390
|
+
return {
|
|
1391
|
+
deployment: null,
|
|
1392
|
+
status: "fail",
|
|
1393
|
+
message
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1291
1397
|
//#endregion
|
|
1292
1398
|
//#region src/commands/doctor.ts
|
|
1293
1399
|
async function runDoctor(input) {
|
|
@@ -8266,7 +8372,7 @@ try {
|
|
|
8266
8372
|
})).summary.fail > 0) process.exitCode = 1;
|
|
8267
8373
|
}).command("deploy [branch]", "Trigger a deployment via the configured `deployment` capability", (y) => y.positional("branch", {
|
|
8268
8374
|
type: "string",
|
|
8269
|
-
describe: "Git branch to deploy"
|
|
8375
|
+
describe: "Git branch to deploy (omit when using --files)"
|
|
8270
8376
|
}).option("project-id", {
|
|
8271
8377
|
type: "string",
|
|
8272
8378
|
demandOption: true,
|
|
@@ -8275,20 +8381,34 @@ try {
|
|
|
8275
8381
|
type: "string",
|
|
8276
8382
|
choices: ["production", "staging"],
|
|
8277
8383
|
describe: "Named environment to deploy into. Omit for a branch preview."
|
|
8278
|
-
}),
|
|
8384
|
+
}).option("files", {
|
|
8385
|
+
type: "string",
|
|
8386
|
+
describe: "Deploy from a local directory of source files instead of a Git branch — calls deployFunction(), no linked repo required. Conflicts with `branch`."
|
|
8387
|
+
}).conflicts("files", "branch"), async (argv) => {
|
|
8279
8388
|
const tokens = tokenContext(argv.token);
|
|
8280
8389
|
if (!tokens) return;
|
|
8281
|
-
const [branch] = await promptForPositionals(getEntry("deploy"), argv);
|
|
8282
8390
|
const loaded = await loadConfig(argv.cwd);
|
|
8283
8391
|
applyResolvedConfig(argv, loaded.resolved);
|
|
8392
|
+
const context = {
|
|
8393
|
+
repoRoot: argv.cwd,
|
|
8394
|
+
dryRun: argv.dryRun,
|
|
8395
|
+
...tokens,
|
|
8396
|
+
org: resolveOrg(argv, loaded.resolved)
|
|
8397
|
+
};
|
|
8398
|
+
if (argv.files) {
|
|
8399
|
+
if ((await runDeployFromFiles({
|
|
8400
|
+
loaded,
|
|
8401
|
+
context,
|
|
8402
|
+
projectId: argv.projectId,
|
|
8403
|
+
dir: argv.files,
|
|
8404
|
+
...argv.target ? { target: argv.target } : {}
|
|
8405
|
+
})).status === "fail") process.exitCode = 1;
|
|
8406
|
+
return;
|
|
8407
|
+
}
|
|
8408
|
+
const [branch] = await promptForPositionals(getEntry("deploy"), argv);
|
|
8284
8409
|
if ((await runDeploy({
|
|
8285
8410
|
loaded,
|
|
8286
|
-
context
|
|
8287
|
-
repoRoot: argv.cwd,
|
|
8288
|
-
dryRun: argv.dryRun,
|
|
8289
|
-
...tokens,
|
|
8290
|
-
org: resolveOrg(argv, loaded.resolved)
|
|
8291
|
-
},
|
|
8411
|
+
context,
|
|
8292
8412
|
projectId: argv.projectId,
|
|
8293
8413
|
branch,
|
|
8294
8414
|
...argv.target ? { target: argv.target } : {}
|