@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 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
- }), async (argv) => {
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 } : {}