@theholocron/cli 5.0.0-alpha.149 → 5.0.0-alpha.151

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
@@ -18,12 +18,12 @@ import { createLogger, isCI, parseLogLevel, resolveAxiomFromEnv } from "@theholo
18
18
  import chalk from "chalk";
19
19
  import ora from "ora";
20
20
  import { execFileSync, spawn, spawnSync } from "node:child_process";
21
+ import { findRootSync, walkFilesSync } from "@theholocron/fs-utils";
21
22
  import { stringifyJson } from "@theholocron/json-utils";
23
+ import { listWorkspacePackagesSync, readPackageJson, readPackageJsonSync } from "@theholocron/pkg-utils";
22
24
  import { readGitRemote, updateGitignore } from "@theholocron/git-utils";
23
- import { readPackageJson } from "@theholocron/pkg-utils";
24
25
  import { access, copyFile, mkdir, readFile, readdir, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
25
26
  import { createHash, randomUUID } from "node:crypto";
26
- import { findRootSync } from "@theholocron/fs-utils";
27
27
  import { generateReadme } from "@theholocron/components-doc/markdown";
28
28
  import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
29
29
  import { createGitHubClient } from "@theholocron/github-client";
@@ -1571,23 +1571,12 @@ async function runDeploy(input) {
1571
1571
  };
1572
1572
  }
1573
1573
  }
1574
- const SKIP_DIRS$2 = /* @__PURE__ */ new Set([
1574
+ const SKIP_DIRS$2 = [
1575
1575
  ".git",
1576
1576
  "node_modules",
1577
1577
  ".turbo"
1578
- ]);
1579
- /** Recursively lists absolute file paths under `dir`, skipping VCS/tooling noise. */
1580
- function defaultWalkFiles$2(dir) {
1581
- const results = [];
1582
- for (const entry of readdirSync(dir)) {
1583
- if (SKIP_DIRS$2.has(entry)) continue;
1584
- const full = join(dir, entry);
1585
- const stat = statSync(full);
1586
- if (stat.isDirectory()) results.push(...defaultWalkFiles$2(full));
1587
- else if (stat.isFile()) results.push(full);
1588
- }
1589
- return results;
1590
- }
1578
+ ];
1579
+ const defaultWalkFiles$2 = (dir) => walkFilesSync(dir, { skip: SKIP_DIRS$2 });
1591
1580
  /** `holocron deploy --files <dir>` — the non-git counterpart to `runDeploy`. */
1592
1581
  async function runDeployFromFiles(input) {
1593
1582
  const print = input.print ?? ((line) => console.log(line));
@@ -2113,23 +2102,13 @@ function generateHolocronConfig(opts) {
2113
2102
  lines.push(``);
2114
2103
  return lines.join("\n");
2115
2104
  }
2116
- const SKIP_DIRS$1 = /* @__PURE__ */ new Set([
2105
+ const SKIP_DIRS$1 = [
2117
2106
  ".git",
2118
2107
  "node_modules",
2119
2108
  "dist",
2120
2109
  ".turbo"
2121
- ]);
2122
- function defaultWalkFiles$1(dir) {
2123
- const results = [];
2124
- for (const entry of readdirSync(dir)) {
2125
- if (SKIP_DIRS$1.has(entry)) continue;
2126
- const full = path.join(dir, entry);
2127
- const stat = statSync(full);
2128
- if (stat.isDirectory()) results.push(...defaultWalkFiles$1(full));
2129
- else if (stat.isFile()) results.push(full);
2130
- }
2131
- return results;
2132
- }
2110
+ ];
2111
+ const defaultWalkFiles$1 = (dir) => walkFilesSync(dir, { skip: SKIP_DIRS$1 });
2133
2112
  function isBinary(content) {
2134
2113
  for (let i = 0; i < Math.min(content.length, 8e3); i++) if (content.charCodeAt(i) === 0) return true;
2135
2114
  return false;
@@ -2429,39 +2408,37 @@ async function runNpmBumpVersions(input) {
2429
2408
  bumped.push(label);
2430
2409
  }
2431
2410
  bumpFile(join(cwd, "package.json"), "root");
2432
- function bumpWorkspaceDir(dirName) {
2433
- const dirPath = join(cwd, dirName);
2434
- let entries;
2435
- try {
2436
- entries = listDir(dirPath);
2437
- } catch {
2438
- return;
2439
- }
2440
- for (const entry of entries) {
2441
- const pkgDir = join(dirPath, entry);
2442
- try {
2443
- if (!isDir(pkgDir)) continue;
2444
- } catch {
2445
- continue;
2446
- }
2447
- const pkgFile = join(pkgDir, "package.json");
2448
- let pkg;
2411
+ const listed = /* @__PURE__ */ new Set();
2412
+ const workspace = listWorkspacePackagesSync(cwd, {
2413
+ readFile,
2414
+ listDirs: (dir) => {
2415
+ let names;
2449
2416
  try {
2450
- pkg = JSON.parse(readFile(pkgFile));
2417
+ names = listDir(dir);
2451
2418
  } catch {
2452
- print(` ! skipping ${dirName}/${entry}: no package.json or malformed JSON`);
2453
- continue;
2454
- }
2455
- if (pkg.private) {
2456
- print(` · skipping private package ${dirName}/${entry}`);
2457
- skipped.push(`${dirName}/${entry}`);
2458
- continue;
2419
+ throw Object.assign(/* @__PURE__ */ new Error(`ENOENT: ${dir}`), { code: "ENOENT" });
2459
2420
  }
2460
- bumpFile(pkgFile, `${dirName}/${entry}`);
2421
+ return names.filter((name) => {
2422
+ try {
2423
+ if (!isDir(join(dir, name))) return false;
2424
+ } catch {
2425
+ return false;
2426
+ }
2427
+ listed.add(join(dir, name));
2428
+ return true;
2429
+ });
2461
2430
  }
2431
+ });
2432
+ const found = new Set(workspace.map((entry) => entry.dir));
2433
+ for (const dir of listed) if (!found.has(dir)) print(` ! skipping ${relative(resolve(cwd), dir)}: no package.json or malformed JSON`);
2434
+ for (const { dir, relativeDir, manifest } of workspace) {
2435
+ if (manifest.private) {
2436
+ print(` · skipping private package ${relativeDir}`);
2437
+ skipped.push(relativeDir);
2438
+ continue;
2439
+ }
2440
+ bumpFile(join(dir, "package.json"), relativeDir);
2462
2441
  }
2463
- bumpWorkspaceDir("packages");
2464
- bumpWorkspaceDir("plugins");
2465
2442
  const status = dryRun ? "dry-run" : "ok";
2466
2443
  logger.info({
2467
2444
  version,
@@ -4094,71 +4071,40 @@ function workspaceFilterArgs(cwd) {
4094
4071
  if (hasPluginsDir(cwd)) filters.push("--filter=./plugins/*");
4095
4072
  return filters.length > 0 ? ["-r", ...filters] : [];
4096
4073
  }
4097
- /** Read a `package.json`'s `name`, when it's public (`!private && name`). */
4098
- function publicPackageName(pkgPath) {
4099
- if (!existsSync(pkgPath)) return void 0;
4100
- try {
4101
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
4102
- return !pkg.private && pkg.name ? pkg.name : void 0;
4103
- } catch {
4104
- return;
4105
- }
4106
- }
4107
- /** Read a `package.json`'s `name` + `version`, when it's public (`!private && name`). */
4108
- function publicPackageEntry(pkgPath, dir) {
4109
- if (!existsSync(pkgPath)) return void 0;
4110
- try {
4111
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
4112
- return !pkg.private && pkg.name && pkg.version ? {
4113
- name: pkg.name,
4114
- version: pkg.version,
4115
- dir
4116
- } : void 0;
4117
- } catch {
4118
- return;
4119
- }
4120
- }
4121
4074
  /**
4122
- * Same repo-layout detection as {@link discoverPublicPackages}, but carrying
4123
- * version + a `pnpm --filter`-ready path — {@link runSteadyPublish}'s
4124
- * `skipAlreadyPublished` mode needs both to check `npm view <pkg>@<version>`
4125
- * and publish one workspace at a time.
4075
+ * Monorepo (`packages/` and/or `plugins/` present): every workspace under
4076
+ * either. Single package (neither dir present): the root `package.json`.
4077
+ * Callers keep the public ones.
4126
4078
  */
4127
- function discoverPublicPackageEntries(cwd) {
4079
+ function discoverManifests(cwd) {
4128
4080
  if (!hasPackagesDir(cwd) && !hasPluginsDir(cwd)) {
4129
- const entry = publicPackageEntry(join(cwd, "package.json"), ".");
4130
- return entry ? [entry] : [];
4131
- }
4132
- return [...entriesInWorkspaceDir(cwd, "packages"), ...entriesInWorkspaceDir(cwd, "plugins")];
4133
- }
4134
- function entriesInWorkspaceDir(cwd, dirName) {
4135
- const dirPath = join(cwd, dirName);
4136
- if (!existsSync(dirPath)) return [];
4137
- return readdirSync(dirPath, { withFileTypes: true }).filter((e) => e.isDirectory()).flatMap((e) => {
4138
- const entry = publicPackageEntry(join(dirPath, e.name, "package.json"), `./${dirName}/${e.name}`);
4139
- return entry ? [entry] : [];
4140
- });
4081
+ const manifest = readPackageJsonSync(cwd);
4082
+ return manifest ? [{
4083
+ manifest,
4084
+ dir: "."
4085
+ }] : [];
4086
+ }
4087
+ return listWorkspacePackagesSync(cwd).map(({ manifest, relativeDir }) => ({
4088
+ manifest,
4089
+ dir: `./${relativeDir}`
4090
+ }));
4141
4091
  }
4142
4092
  /**
4143
- * Monorepo (`packages/` and/or `plugins/` present): every public workspace
4144
- * under either. Single package (neither dir present): the root
4145
- * `package.json`, if it's public — most `node-template` scaffolds are one
4146
- * package at the repo root.
4093
+ * Every public package (`!private && name`) with its version and a
4094
+ * `pnpm --filter`-ready path — {@link runSteadyPublish}'s `skipAlreadyPublished`
4095
+ * mode needs both to check `npm view <pkg>@<version>` and publish one
4096
+ * workspace at a time.
4147
4097
  */
4148
- function discoverPublicPackages(cwd) {
4149
- if (!hasPackagesDir(cwd) && !hasPluginsDir(cwd)) {
4150
- const name = publicPackageName(join(cwd, "package.json"));
4151
- return name ? [name] : [];
4152
- }
4153
- return [...namesInWorkspaceDir(cwd, "packages"), ...namesInWorkspaceDir(cwd, "plugins")];
4098
+ function discoverPublicPackageEntries(cwd) {
4099
+ return discoverManifests(cwd).flatMap(({ manifest, dir }) => !manifest.private && manifest.name && manifest.version ? [{
4100
+ name: manifest.name,
4101
+ version: manifest.version,
4102
+ dir
4103
+ }] : []);
4154
4104
  }
4155
- function namesInWorkspaceDir(cwd, dirName) {
4156
- const dirPath = join(cwd, dirName);
4157
- if (!existsSync(dirPath)) return [];
4158
- return readdirSync(dirPath, { withFileTypes: true }).filter((e) => e.isDirectory()).flatMap((e) => {
4159
- const name = publicPackageName(join(dirPath, e.name, "package.json"));
4160
- return name ? [name] : [];
4161
- });
4105
+ /** The names of {@link discoverPublicPackageEntries}' packages, without requiring a version. */
4106
+ function discoverPublicPackages(cwd) {
4107
+ return discoverManifests(cwd).flatMap(({ manifest }) => !manifest.private && manifest.name ? [manifest.name] : []);
4162
4108
  }
4163
4109
  async function resolveRepoName(cwd, exec) {
4164
4110
  return (await readGitRemote(cwd, { exec: async (args, options) => {
@@ -8062,7 +8008,7 @@ async function runUpgradeDeps(input = {}) {
8062
8008
  }
8063
8009
  //#endregion
8064
8010
  //#region src/commands/upgrade-node.ts
8065
- const SKIP_DIRS = /* @__PURE__ */ new Set([
8011
+ const SKIP_DIRS = [
8066
8012
  "node_modules",
8067
8013
  ".git",
8068
8014
  "dist",
@@ -8071,7 +8017,7 @@ const SKIP_DIRS = /* @__PURE__ */ new Set([
8071
8017
  ".turbo",
8072
8018
  ".next",
8073
8019
  "out"
8074
- ]);
8020
+ ];
8075
8021
  function patchPackageJson(content, from, to) {
8076
8022
  let pkg;
8077
8023
  try {
@@ -8150,27 +8096,7 @@ function detectFrom(cwd, _readFile) {
8150
8096
  } catch {}
8151
8097
  return null;
8152
8098
  }
8153
- function defaultWalkFiles(dir) {
8154
- const results = [];
8155
- function walk(current) {
8156
- let entries;
8157
- try {
8158
- entries = readdirSync(current);
8159
- } catch {
8160
- return;
8161
- }
8162
- for (const entry of entries) {
8163
- if (SKIP_DIRS.has(entry)) continue;
8164
- const abs = join(current, entry);
8165
- try {
8166
- if (statSync(abs).isDirectory()) walk(abs);
8167
- else results.push(abs);
8168
- } catch {}
8169
- }
8170
- }
8171
- walk(dir);
8172
- return results;
8173
- }
8099
+ const defaultWalkFiles = (dir) => walkFilesSync(dir, { skip: SKIP_DIRS });
8174
8100
  async function runUpgradeNode(input) {
8175
8101
  const print = input.print ?? ((line) => console.log(line));
8176
8102
  const logger = input.logger ?? getLogger();
@@ -8179,6 +8105,21 @@ async function runUpgradeNode(input) {
8179
8105
  const _readFile = input.readFile ?? ((p) => readFileSync(p, "utf8"));
8180
8106
  const _writeFile = input.writeFile ?? ((p, c) => writeFileSync(p, c));
8181
8107
  const _walkFiles = input.walkFiles ?? defaultWalkFiles;
8108
+ let walked;
8109
+ try {
8110
+ walked = _walkFiles(cwd);
8111
+ } catch (error) {
8112
+ const message = `could not read ${cwd}: ${errorMessage(error)}`;
8113
+ logger.warn({
8114
+ to,
8115
+ reason: message
8116
+ }, "upgrade node: done");
8117
+ return {
8118
+ status: "fail",
8119
+ updated: [],
8120
+ message
8121
+ };
8122
+ }
8182
8123
  const from = input.from ?? detectFrom(cwd, _readFile);
8183
8124
  if (from === null) {
8184
8125
  const message = "could not detect current Node version — pass --from <major>";
@@ -8212,7 +8153,8 @@ async function runUpgradeNode(input) {
8212
8153
  }
8213
8154
  print(`Upgrading Node.js ${from} → ${to}${dryRun ? " (dry-run)" : ""}…`);
8214
8155
  const updated = [];
8215
- const scanned = [..._walkFiles(cwd), ...extra.map((p) => join(cwd, p))];
8156
+ const root = resolve(cwd);
8157
+ const scanned = [...walked, ...extra.map((p) => join(cwd, p))];
8216
8158
  for (const abs of scanned) {
8217
8159
  const name = basename(abs);
8218
8160
  const pattern = PATTERNS.find((p) => p.matches(name));
@@ -8225,7 +8167,8 @@ async function runUpgradeNode(input) {
8225
8167
  }
8226
8168
  const patched = pattern.patch(content, from, to);
8227
8169
  if (patched === null) continue;
8228
- const rel = abs.startsWith(cwd + "/") ? abs.slice(cwd.length + 1) : abs;
8170
+ const absolute = resolve(abs);
8171
+ const rel = absolute.startsWith(root + sep) ? absolute.slice(root.length + 1) : abs;
8229
8172
  if (!dryRun) _writeFile(abs, patched);
8230
8173
  print(` ${dryRun ? "~" : "✓"} ${rel}`);
8231
8174
  logger.debug({