@lzear/forge 4.2.2 → 4.4.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/README.md CHANGED
@@ -19,6 +19,7 @@ Requires Node ≥ 20.
19
19
 
20
20
  ```sh
21
21
  yarn forge check # audit this repo against forge standards
22
+ yarn forge update # update deps, packageManager & version files
22
23
  yarn forge setup # check and set required GitHub secrets
23
24
  yarn forge sync # pull shared files from forge into this repo
24
25
  ```
@@ -35,6 +36,24 @@ forge check --json # machine-readable output
35
36
  forge check --skip-remote # skip GitHub secret checks
36
37
  ```
37
38
 
39
+ ### `forge update`
40
+
41
+ One-stop repo freshening. Detects the package manager (npm, yarn, pnpm, or bun — from the `packageManager` field or the lockfile) and updates everything that needs regular bumping:
42
+
43
+ - dependency ranges in all workspaces (via `npm-check-updates`; honors `.ncurc{,.json,.js,.cjs,.mjs}` — e.g. `reject` to pin packages)
44
+ - the `packageManager` field (yarn ≥ 2 resolved from `@yarnpkg/cli-dist`)
45
+ - `.nvmrc` / `.node-version` → latest Node LTS
46
+ - `.bun-version` → latest Bun
47
+ - LICENSE copyright year (extends `2023` → `2023-2026`)
48
+ - then installs, refreshes the lockfile, and dedupes with the detected package manager (`yarn up -R '*' && yarn dedupe`, `pnpm update -r && pnpm dedupe`, `npm update && npm dedupe`, or `bun update`)
49
+
50
+ ```sh
51
+ forge update # update everything, then install
52
+ forge update --dry # preview changes without writing
53
+ forge update --no-install # write updates but skip install
54
+ forge update --json # machine-readable output
55
+ ```
56
+
38
57
  ### `forge setup`
39
58
 
40
59
  Checks and sets the required GitHub secrets (`NPM_TOKEN`, `CODACY_PROJECT_TOKEN`) for a repo.
@@ -50,7 +69,7 @@ forge setup --dry # check only, do not prompt
50
69
  Fetches shared template files from the forge `main` branch and writes them locally.
51
70
 
52
71
  ```sh
53
- forge sync # write .editorconfig, .codacy.yml, lefthook.yml
72
+ forge sync # write .editorconfig, .codacy.yml, lefthook.yml, .github/zizmor.yml
54
73
  forge sync --dry # preview without writing
55
74
  ```
56
75
 
package/dist/bin.js CHANGED
@@ -9,36 +9,40 @@ import path from "path";
9
9
  import * as clack from "@clack/prompts";
10
10
  import { Command } from "commander";
11
11
  import pc from "picocolors";
12
- import { checkLocal, checkRepo } from "@lzear/repo-lint";
12
+ import {
13
+ checkLocal,
14
+ checkRepo,
15
+ runUpdate
16
+ } from "@lzear/repo-lint";
13
17
  var require2 = createRequire(import.meta.url);
14
18
  var { version } = require2("../package.json");
15
19
  var isTTY = process.stdout.isTTY;
16
20
  var isDebug = process.env.DEBUG?.includes("forge") ?? false;
17
- var debug = (...args) => {
18
- if (isDebug) console.error(pc.dim(`[debug] ${args.join(" ")}`));
21
+ var debug = (...arguments_) => {
22
+ if (isDebug) console.error(pc.dim(`[debug] ${arguments_.join(" ")}`));
19
23
  };
20
24
  var log2 = {
21
- intro: (msg) => {
22
- isTTY ? clack.intro(msg) : console.log(msg);
25
+ intro: (message) => {
26
+ isTTY ? clack.intro(message) : console.log(message);
23
27
  },
24
- outro: (msg) => {
25
- isTTY ? clack.outro(msg) : console.log(msg);
28
+ outro: (message) => {
29
+ isTTY ? clack.outro(message) : console.log(message);
26
30
  },
27
- success: (msg) => {
28
- isTTY ? clack.log.success(msg) : console.log(`\u2713 ${msg}`);
31
+ success: (message) => {
32
+ isTTY ? clack.log.success(message) : console.log(`\u2713 ${message}`);
29
33
  },
30
- error: (msg) => {
31
- isTTY ? clack.log.error(msg) : console.error(`\u2717 ${msg}`);
34
+ error: (message) => {
35
+ isTTY ? clack.log.error(message) : console.error(`\u2717 ${message}`);
32
36
  },
33
- warn: (msg) => {
34
- isTTY ? clack.log.warn(msg) : console.warn(`! ${msg}`);
37
+ warn: (message) => {
38
+ isTTY ? clack.log.warn(message) : console.warn(`! ${message}`);
35
39
  },
36
40
  spinner: () => isTTY ? clack.spinner() : {
37
- start: (msg) => {
38
- debug(msg);
41
+ start: (message) => {
42
+ debug(message);
39
43
  },
40
- stop: (msg) => {
41
- debug(msg);
44
+ stop: (message) => {
45
+ debug(message);
42
46
  }
43
47
  }
44
48
  };
@@ -46,16 +50,16 @@ var program = new Command();
46
50
  program.name("forge").description("lzear repo tooling").version(version, "-v, --version").helpOption("-h, --help", "show help");
47
51
  var DEFAULT_DIR = path.join(homedir(), ".cache", "forge");
48
52
  var printResults = (report, json) => {
49
- const anyFail = report.results.some((r) => !r.pass);
53
+ const isAnyFail = report.results.some((r) => !r.pass);
50
54
  if (json) {
51
55
  console.log(
52
56
  JSON.stringify(
53
- { repo: report.repo, results: report.results, pass: !anyFail },
57
+ { repo: report.repo, results: report.results, pass: !isAnyFail },
54
58
  null,
55
59
  2
56
60
  )
57
61
  );
58
- return anyFail;
62
+ return isAnyFail;
59
63
  }
60
64
  const pass = report.results.filter((r) => r.pass).length;
61
65
  const total = report.results.length;
@@ -73,45 +77,45 @@ var printResults = (report, json) => {
73
77
  process.stdout.write(`
74
78
  ${score} checks passed
75
79
  `);
76
- return anyFail;
80
+ return isAnyFail;
77
81
  };
78
- var runCheck = async (repos, opts) => {
79
- if (!opts.json) process.stdout.write(`
82
+ var runCheck = async (repos, options) => {
83
+ if (!options.json) process.stdout.write(`
80
84
  ${pc.bold("forge check")}
81
85
 
82
86
  `);
83
87
  if (!repos) {
84
88
  debug("checkLocal", process.cwd());
85
- const result = await checkLocal({ skipRemote: opts.skipRemote });
86
- process.exit(printResults(result, opts.json) ? 1 : 0);
89
+ const result = await checkLocal({ skipRemote: options.skipRemote });
90
+ process.exit(printResults(result, options.json) ? 1 : 0);
87
91
  }
88
92
  const repoList = repos.split(",").map((r) => r.trim());
89
- let anyFail = false;
93
+ let isAnyFail = false;
90
94
  for (const repo of repoList) {
91
- if (!opts.json)
95
+ if (!options.json)
92
96
  process.stdout.write(` ${pc.dim("checking " + repo + "\u2026")}
93
97
  `);
94
98
  debug("checkRepo", repo);
95
99
  try {
96
100
  const result = await checkRepo(repo, {
97
- baseDir: opts.dir,
98
- skipRemote: opts.skipRemote
101
+ baseDir: options.dir,
102
+ skipRemote: options.skipRemote
99
103
  });
100
- if (printResults(result, opts.json)) anyFail = true;
104
+ if (printResults(result, options.json)) isAnyFail = true;
101
105
  } catch (error) {
102
106
  debug(String(error));
103
107
  process.stderr.write(` ${pc.red("\u2717 " + repo + " \u2014 could not clone")}
104
108
  `);
105
- anyFail = true;
109
+ isAnyFail = true;
106
110
  }
107
111
  }
108
- process.exit(anyFail ? 1 : 0);
112
+ process.exit(isAnyFail ? 1 : 0);
109
113
  };
110
114
  program.command("check").description("audit repo(s) against forge standards").option(
111
115
  "--repos <repos>",
112
116
  "comma-separated list of repos (default: current repo)"
113
117
  ).option("--skip-remote", "skip secret checks", false).option("--dir <dir>", "cache dir for cloned repos", DEFAULT_DIR).option("--json", "output results as JSON", false).action(
114
- (opts) => runCheck(opts.repos, opts)
118
+ (options) => runCheck(options.repos, options)
115
119
  );
116
120
  var promptAndSet = async (s, repo) => {
117
121
  if (!isTTY) {
@@ -150,13 +154,13 @@ var detectRepo = () => {
150
154
  const match = /github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?$/.exec(url);
151
155
  return match?.[1];
152
156
  };
153
- program.command("setup").description("check and set required GitHub secrets for a repo").option("--repo <repo>", "repo to set up (e.g. lzear/my-repo)").option("--dry", "check only, do not prompt for values", false).option("--json", "output results as JSON", false).action(async (opts) => {
154
- const repo = opts.repo ?? detectRepo();
157
+ program.command("setup").description("check and set required GitHub secrets for a repo").option("--repo <repo>", "repo to set up (e.g. lzear/my-repo)").option("--dry", "check only, do not prompt for values", false).option("--json", "output results as JSON", false).action(async (options) => {
158
+ const repo = options.repo ?? detectRepo();
155
159
  if (!repo) {
156
160
  log2.error("Could not detect repo. Pass --repo <owner/repo>.");
157
161
  process.exit(1);
158
162
  }
159
- const { dry, json } = opts;
163
+ const { dry, json } = options;
160
164
  if (!json) log2.intro(pc.bold(`forge setup \xB7 ${pc.cyan(repo)}`));
161
165
  if (spawnSync("gh", ["--version"], { stdio: "ignore" }).status !== 0) {
162
166
  log2.error("gh CLI not found \u2014 install at https://cli.github.com");
@@ -208,35 +212,80 @@ program.command("setup").description("check and set required GitHub secrets for
208
212
  for (const s of missing) await promptAndSet(s, repo);
209
213
  log2.outro(pc.green("Done."));
210
214
  });
215
+ var formatPackageManager = (pm) => {
216
+ const version2 = pm.version ? `@${pm.version}` : "";
217
+ return `package manager: ${pm.name}${version2} (${pm.source})`;
218
+ };
219
+ var updateMark = (r) => {
220
+ if (!r.pass) return pc.red("\u2717");
221
+ return r.changed ? pc.yellow("\u2191") : pc.green("\u2713");
222
+ };
223
+ var printUpdateReport = (report) => {
224
+ process.stdout.write(
225
+ ` ${pc.dim(formatPackageManager(report.packageManager))}
226
+
227
+ `
228
+ );
229
+ for (const r of report.results) {
230
+ process.stdout.write(` ${updateMark(r)} ${r.desc}
231
+ `);
232
+ if (r.detail && (r.changed || !r.pass))
233
+ for (const line of r.detail.split("\n"))
234
+ process.stdout.write(` ${pc.dim(line)}
235
+ `);
236
+ }
237
+ process.stdout.write("\n");
238
+ };
239
+ program.command("update").description("update dependencies, packageManager, and runtime version files").option("--dry", "preview changes, do not write", false).option("--no-install", "skip install & lockfile refresh").option("--json", "output results as JSON", false).action(
240
+ async (options) => {
241
+ const { dry: isDry, install: isInstall, json: isJson } = options;
242
+ if (!isJson)
243
+ process.stdout.write(
244
+ `
245
+ ${pc.bold("forge update")}${isDry ? pc.dim(" (dry run)") : ""}
246
+
247
+ `
248
+ );
249
+ debug("runUpdate", process.cwd());
250
+ const report = await runUpdate({ dry: isDry, install: isInstall });
251
+ const isAnyFail = report.results.some((r) => !r.pass);
252
+ if (isJson)
253
+ console.log(JSON.stringify({ ...report, pass: !isAnyFail }, null, 2));
254
+ else printUpdateReport(report);
255
+ process.exit(isAnyFail ? 1 : 0);
256
+ }
257
+ );
211
258
  var SYNC_FILES = [
212
259
  { src: "template/.editorconfig", dest: ".editorconfig" },
213
260
  { src: "template/.codacy.yml", dest: ".codacy.yml" },
214
- { src: "template/lefthook.yml", dest: "lefthook.yml" }
261
+ { src: "template/lefthook.yml", dest: "lefthook.yml" },
262
+ // shared as-is: allows @main refs to forge, hash-pins everything else
263
+ { src: ".github/zizmor.yml", dest: ".github/zizmor.yml" }
215
264
  ];
216
265
  var RAW_BASE = "https://raw.githubusercontent.com/lzear/forge/main";
217
- program.command("sync").description("sync template files from forge into this repo").option("--dry", "print what would change, do not write", false).action(async (opts) => {
266
+ program.command("sync").description("sync template files from forge into this repo").option("--dry", "print what would change, do not write", false).action(async (options) => {
218
267
  log2.intro(pc.bold("forge sync"));
219
- let anyFail = false;
268
+ let isAnyFail = false;
220
269
  for (const { src, dest } of SYNC_FILES) {
221
270
  const url = `${RAW_BASE}/${src}`;
222
- const destPath = path.join(process.cwd(), dest);
271
+ const destinationPath = path.join(process.cwd(), dest);
223
272
  try {
224
273
  const res = await fetch(url);
225
274
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
226
275
  const content = await res.text();
227
- if (opts.dry) log2.success(`${dest} ${pc.dim("(dry)")}`);
276
+ if (options.dry) log2.success(`${dest} ${pc.dim("(dry)")}`);
228
277
  else {
229
- await mkdir(path.dirname(destPath), { recursive: true });
230
- await writeFile(destPath, content, "utf8");
278
+ await mkdir(path.dirname(destinationPath), { recursive: true });
279
+ await writeFile(destinationPath, content, "utf8");
231
280
  log2.success(dest);
232
281
  }
233
282
  } catch (error) {
234
283
  log2.error(`${dest} \u2014 ${String(error)}`);
235
- anyFail = true;
284
+ isAnyFail = true;
236
285
  }
237
286
  }
238
- log2.outro(anyFail ? pc.red("Done with errors.") : pc.green("Done."));
239
- if (anyFail) process.exit(1);
287
+ log2.outro(isAnyFail ? pc.red("Done with errors.") : pc.green("Done."));
288
+ if (isAnyFail) process.exit(1);
240
289
  });
241
290
  var handleSignal = () => {
242
291
  if (isTTY) clack.cancel("Cancelled.");
@@ -1 +1 @@
1
- export { Check, CheckLocalOptions, CheckRepoOptions, CheckResult, LocalCheck, RemoteCheck, RepoReport, checkLocal, checkRepo } from '@lzear/repo-lint';
1
+ export { Check, CheckLocalOptions, CheckRepoOptions, CheckResult, LocalCheck, PackageManager, PackageManagerName, RemoteCheck, RepoReport, UpdateOptions, UpdateReport, UpdateResult, checkLocal, checkRepo, detectPackageManager, runUpdate } from '@lzear/repo-lint';
package/dist/repo-lint.js CHANGED
@@ -1,6 +1,13 @@
1
1
  // src/repo-lint.ts
2
- import { checkLocal, checkRepo } from "@lzear/repo-lint";
2
+ import {
3
+ checkLocal,
4
+ checkRepo,
5
+ detectPackageManager,
6
+ runUpdate
7
+ } from "@lzear/repo-lint";
3
8
  export {
4
9
  checkLocal,
5
- checkRepo
10
+ checkRepo,
11
+ detectPackageManager,
12
+ runUpdate
6
13
  };
package/package.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "name": "@lzear/forge",
3
- "version": "4.2.2",
3
+ "version": "4.4.0",
4
4
  "description": "Umbrella package for all lzear dev tooling",
5
+ "license": "MIT",
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "git+https://github.com/lzear/forge.git",
8
9
  "directory": "packages/forge"
9
10
  },
10
11
  "author": "lzear",
11
- "license": "MIT",
12
- "sideEffects": false,
13
12
  "type": "module",
14
13
  "exports": {
15
14
  "./commitlint": {
@@ -49,29 +48,43 @@
49
48
  }
50
49
  },
51
50
  "bin": "./dist/bin.js",
52
- "files": [
53
- "dist",
54
- "tsconfig"
55
- ],
51
+ "sideEffects": false,
52
+ "engines": {
53
+ "node": ">=24"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public",
57
+ "provenance": true
58
+ },
56
59
  "scripts": {
57
60
  "build": "tsup",
58
61
  "dev": "tsup --watch",
59
62
  "typecheck": "tsc --noEmit"
60
63
  },
64
+ "files": [
65
+ "dist",
66
+ "tsconfig"
67
+ ],
68
+ "keywords": [
69
+ "cli",
70
+ "dev-tools",
71
+ "eslint-config",
72
+ "monorepo",
73
+ "tooling"
74
+ ],
61
75
  "dependencies": {
62
76
  "@clack/prompts": "^1",
63
- "@lzear/configs": "4.2.2",
64
- "@lzear/eslint-config": "4.2.2",
65
- "@lzear/repo-lint": "4.2.2",
77
+ "@lzear/configs": "4.4.0",
78
+ "@lzear/eslint-config": "4.4.0",
79
+ "@lzear/repo-lint": "4.4.0",
66
80
  "commander": "^15",
67
81
  "picocolors": "^1"
68
82
  },
69
83
  "devDependencies": {
70
- "@types/node": "^25",
84
+ "@types/node": "^26",
71
85
  "@vitejs/plugin-react": "^6",
72
86
  "eslint": "^10",
73
87
  "tsup": "^8",
74
- "typescript": "^6",
75
88
  "vite": "^8",
76
89
  "vitest": "^4"
77
90
  },
@@ -98,12 +111,5 @@
98
111
  "vitest": {
99
112
  "optional": true
100
113
  }
101
- },
102
- "engines": {
103
- "node": ">=24"
104
- },
105
- "publishConfig": {
106
- "access": "public",
107
- "provenance": true
108
114
  }
109
115
  }