@lzear/repo-lint 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
@@ -3,7 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/@lzear/repo-lint)](https://www.npmjs.com/package/@lzear/repo-lint)
4
4
  [![license](https://img.shields.io/npm/l/@lzear/repo-lint)](../../LICENSE)
5
5
 
6
- Checks lzear repos against forge standards. Used internally by `forge check`.
6
+ Checks lzear repos against forge standards and keeps them fresh. Used internally by `forge check` and `forge update`.
7
7
 
8
8
  ## Install
9
9
 
@@ -58,24 +58,41 @@ interface RepoReport {
58
58
  }
59
59
  ```
60
60
 
61
+ ### `runUpdate(options?)`
62
+
63
+ Powers `forge update`. Detects the package manager (npm, yarn, pnpm, bun) and updates dependency ranges (ncu, honoring `.ncurc{,.json,.js,.cjs,.mjs}`), the `packageManager` field, `.nvmrc`/`.node-version` (latest Node LTS), and `.bun-version`, then installs & refreshes the lockfile.
64
+
65
+ ```ts
66
+ import { detectPackageManager, runUpdate } from '@lzear/repo-lint'
67
+
68
+ const report = await runUpdate({ dry: true })
69
+ // { dir, packageManager: { name: 'yarn', version: '4.17.1', … }, results: [...] }
70
+ ```
71
+
72
+ | Option | Type | Default | Description |
73
+ |-----------|-----------|-----------------|------------------------------------|
74
+ | `dir` | `string` | `process.cwd()` | Repo to update |
75
+ | `dry` | `boolean` | `false` | Report changes without writing |
76
+ | `install` | `boolean` | `true` | Run install/lockfile refresh after |
77
+
61
78
  ## Checks performed
62
79
 
63
- | Check | Description |
64
- |--------------------------------|-------------------------------------------------------|
65
- | `readme-exists` | `README.md` exists |
66
- | `readme-npm-badge` | README has an npm badge |
67
- | `readme-codacy-grade-badge` | README has Codacy grade badge |
68
- | `readme-codacy-coverage-badge` | README has Codacy coverage badge |
69
- | `codacy-config` | `.codacy.yml` exists |
70
- | `license` | `LICENSE` exists |
71
- | `ci-workflow` | `.github/workflows/ci.yml` exists |
72
- | `renovate` | `renovate.json` exists |
73
- | `pkg-publint` | All published packages pass `publint` |
74
- | `pkg-attw` | All published packages pass `attw` (ESM-only profile) |
75
- | `pkg-knip` | No unused exports or dependencies (`knip`) |
76
- | `deps-fresh` | All dependencies up to date (`ncu`) |
77
- | `secret-npm-token` | GitHub secret `NPM_TOKEN` is set |
78
- | `secret-codacy-token` | GitHub secret `CODACY_PROJECT_TOKEN` is set |
80
+ | Check | Description |
81
+ |-----------------------|-----------------------------------------------------------|
82
+ | `readme-exists` | `README.md` exists |
83
+ | `readme-npm-badge` | README has an npm badge |
84
+ | `codacy` | Codacy grade & coverage badges in README, `.codacy.yml` |
85
+ | `license` | `LICENSE` exists |
86
+ | `ci-workflow` | a workflow calls the forge reusable CI workflow |
87
+ | `renovate` | `renovate.json` exists |
88
+ | `pkg-publint` | All published packages pass `publint` |
89
+ | `pkg-attw` | All published packages pass `attw` (ESM-only profile) |
90
+ | `pkg-knip` | No unused exports or dependencies (`knip`) |
91
+ | `monorepo-lint` | Workspace consistency (`sherif`, monorepos only) |
92
+ | `deps-audit` | No known vulnerabilities (PM-native `audit`, prod, high+) |
93
+ | `deps-deprecated` | No direct dependency resolves to a deprecated version |
94
+ | `deps-fresh` | All dependencies up to date (`ncu`) |
95
+ | `secret-codacy-token` | GitHub secret `CODACY_PROJECT_TOKEN` is set |
79
96
 
80
97
  ## Part of forge
81
98
 
package/dist/bin.js CHANGED
@@ -7,150 +7,265 @@ import { parseArgs } from "util";
7
7
 
8
8
  // src/index.ts
9
9
  import { execSync } from "child_process";
10
- import { existsSync as existsSync2 } from "fs";
10
+ import { existsSync as existsSync3 } from "fs";
11
11
  import { tmpdir } from "os";
12
- import path2 from "path";
12
+ import path3 from "path";
13
13
 
14
14
  // src/checks.ts
15
+ import { spawnSync as spawnSync2 } from "child_process";
16
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
17
+ import path2 from "path";
18
+ import { fileURLToPath } from "url";
19
+ import { run as ncuRun2 } from "npm-check-updates";
20
+ import { publint } from "publint";
21
+ import { maxSatisfying, satisfies } from "semver";
22
+
23
+ // src/update.ts
15
24
  import { spawnSync } from "child_process";
16
- import { existsSync, readdirSync, readFileSync } from "fs";
25
+ import { existsSync, readFileSync, writeFileSync } from "fs";
17
26
  import path from "path";
18
- import { fileURLToPath } from "url";
27
+ import { pathToFileURL } from "url";
19
28
  import { run as ncuRun } from "npm-check-updates";
20
- import { publint } from "publint";
21
- var _dirname = path.dirname(fileURLToPath(import.meta.url));
22
- var readPkg = (dir) => {
23
- const f = path.join(dir, "package.json");
24
- if (!existsSync(f)) return null;
29
+ var LOCKFILES = [
30
+ ["bun.lock", "bun"],
31
+ ["bun.lockb", "bun"],
32
+ ["pnpm-lock.yaml", "pnpm"],
33
+ ["yarn.lock", "yarn"],
34
+ ["package-lock.json", "npm"]
35
+ ];
36
+ var PM_FIELD_RE = /^(npm|yarn|pnpm|bun)@([^+\s]+)/;
37
+ var detectPackageManager = (dir) => {
38
+ const packageFile = path.join(dir, "package.json");
39
+ if (existsSync(packageFile))
40
+ try {
41
+ const package_ = JSON.parse(readFileSync(packageFile, "utf8"));
42
+ if (typeof package_.packageManager === "string") {
43
+ const match = PM_FIELD_RE.exec(package_.packageManager);
44
+ if (match?.[1] && match[2])
45
+ return {
46
+ name: match[1],
47
+ version: match[2],
48
+ source: "packageManager"
49
+ };
50
+ }
51
+ } catch {
52
+ }
53
+ for (const [file, name] of LOCKFILES)
54
+ if (existsSync(path.join(dir, file))) return { name, source: "lockfile" };
55
+ return { name: "npm", source: "default" };
56
+ };
57
+
58
+ // src/checks.ts
59
+ var _dirname = path2.dirname(fileURLToPath(import.meta.url));
60
+ var readPackage = (dir) => {
61
+ const f = path2.join(dir, "package.json");
62
+ if (!existsSync2(f)) return null;
25
63
  try {
26
- return JSON.parse(readFileSync(f, "utf8"));
64
+ return JSON.parse(readFileSync2(f, "utf8"));
27
65
  } catch {
28
66
  return null;
29
67
  }
30
68
  };
31
- var getWorkspacePatterns = (pkg) => {
32
- const ws = pkg.workspaces;
69
+ var getWorkspacePatterns = (package_) => {
70
+ const ws = package_.workspaces;
33
71
  if (Array.isArray(ws)) return ws;
34
- const wsPkg = ws;
35
- if (Array.isArray(wsPkg?.packages)) return wsPkg.packages;
72
+ const wsPackage = ws;
73
+ if (Array.isArray(wsPackage?.packages)) return wsPackage.packages;
36
74
  return [];
37
75
  };
38
76
  var patternToBase = (rootDir, pattern) => {
39
77
  const parts = pattern.split("/");
40
78
  if (parts.length === 2 && parts[1] === "*")
41
- return path.join(rootDir, parts[0] ?? "");
79
+ return path2.join(rootDir, parts[0] ?? "");
42
80
  if (parts.length === 1 && parts[0] === "*") return rootDir;
43
81
  return null;
44
82
  };
45
- var getWorkspaceDirs = (rootDir) => {
46
- const pkg = readPkg(rootDir);
47
- if (!pkg) return [];
48
- const dirs = [];
49
- for (const pattern of getWorkspacePatterns(pkg)) {
83
+ var getWorkspaceDirectories = (rootDir) => {
84
+ const package_ = readPackage(rootDir);
85
+ if (!package_) return [];
86
+ const directories = [];
87
+ for (const pattern of getWorkspacePatterns(package_)) {
50
88
  const base = patternToBase(rootDir, pattern);
51
- if (!base || !existsSync(base)) continue;
52
- for (const entry of readdirSync(base, { withFileTypes: true }))
53
- if (entry.isDirectory()) dirs.push(path.join(base, entry.name));
89
+ if (!base || !existsSync2(base)) continue;
90
+ const entries = readdirSync(base, { withFileTypes: true });
91
+ for (const entry of entries)
92
+ if (entry.isDirectory()) directories.push(path2.join(base, entry.name));
54
93
  }
55
- return dirs;
94
+ return directories;
56
95
  };
57
96
  var findBin = (name, startDir) => {
58
97
  let dir = startDir;
59
98
  while (true) {
60
- const bin = path.join(dir, "node_modules", ".bin", name);
61
- if (existsSync(bin)) return bin;
62
- const parent = path.join(dir, "..");
99
+ const bin = path2.join(dir, "node_modules", ".bin", name);
100
+ if (existsSync2(bin)) return bin;
101
+ const parent = path2.join(dir, "..");
63
102
  if (parent === dir) return null;
64
103
  dir = parent;
65
104
  }
66
105
  };
67
- var eachPublishedPkg = async (dir, fn) => {
68
- const pkg = readPkg(dir);
69
- if (!pkg) return { pass: true };
70
- if (pkg.private === true) {
71
- const wsDirs = getWorkspaceDirs(dir);
72
- if (wsDirs.length === 0) return { pass: true };
106
+ var eachPublishedPackage = async (dir, function_) => {
107
+ const package_ = readPackage(dir);
108
+ if (!package_) return { pass: true };
109
+ if (package_.private === true) {
110
+ const wsDirectories = getWorkspaceDirectories(dir);
111
+ if (wsDirectories.length === 0) return { pass: true };
73
112
  const results = await Promise.all(
74
- wsDirs.map((d) => eachPublishedPkg(d, fn))
113
+ wsDirectories.map((d) => eachPublishedPackage(d, function_))
75
114
  );
76
115
  const failures = results.filter((r) => !r.pass);
77
116
  if (failures.length === 0) return { pass: true };
78
117
  const detail = failures.flatMap((r) => r.detail ? [r.detail] : []).join("\n");
79
118
  return { pass: false, ...detail && { detail } };
80
119
  }
81
- return fn(dir);
120
+ return function_(dir);
82
121
  };
83
122
  var hasPublishedPkg = (dir) => {
84
- const pkg = readPkg(dir);
85
- if (!pkg) return false;
86
- if (pkg.private !== true) return true;
87
- return getWorkspaceDirs(dir).some((d) => hasPublishedPkg(d));
123
+ const package_ = readPackage(dir);
124
+ if (!package_) return false;
125
+ if (package_.private !== true) return true;
126
+ return getWorkspaceDirectories(dir).some((d) => hasPublishedPkg(d));
88
127
  };
89
128
  var readmeIncludes = (dir, needle) => {
90
- const f = path.join(dir, "README.md");
91
- return existsSync(f) && readFileSync(f, "utf8").includes(needle);
129
+ const f = path2.join(dir, "README.md");
130
+ return existsSync2(f) && readFileSync2(f, "utf8").includes(needle);
131
+ };
132
+ var FORGE_WORKFLOW_RE = /uses:\s*lzear\/forge\/\.github\/workflows\/ci\.yml@/;
133
+ var isForgeRepo = (dir) => getWorkspaceDirectories(dir).some(
134
+ (d) => readPackage(d)?.name === "@lzear/forge"
135
+ );
136
+ var callsForgeWorkflow = (dir) => {
137
+ const wfDir = path2.join(dir, ".github", "workflows");
138
+ if (!existsSync2(wfDir)) return false;
139
+ return readdirSync(wfDir).filter((f) => /\.ya?ml$/.test(f)).some(
140
+ (f) => FORGE_WORKFLOW_RE.test(readFileSync2(path2.join(wfDir, f), "utf8"))
141
+ );
142
+ };
143
+ var auditCommand = (pm) => {
144
+ switch (pm.name) {
145
+ case "npm":
146
+ return ["npm", "audit", "--omit", "dev", "--audit-level", "high"];
147
+ case "pnpm":
148
+ return ["pnpm", "audit", "--prod", "--audit-level", "high"];
149
+ case "bun":
150
+ return ["bun", "audit"];
151
+ case "yarn":
152
+ return pm.version?.startsWith("1.") ? ["yarn", "audit", "--level", "high"] : [
153
+ "yarn",
154
+ "npm",
155
+ "audit",
156
+ "--environment",
157
+ "production",
158
+ "--severity",
159
+ "high"
160
+ ];
161
+ }
162
+ };
163
+ var DEP_FIELDS = [
164
+ "dependencies",
165
+ "devDependencies",
166
+ "optionalDependencies",
167
+ "peerDependencies"
168
+ ];
169
+ var NON_REGISTRY_RANGE_RE = /^(?:workspace:|file:|link:|portal:|catalog:|git|https?:)/;
170
+ var collectEntry = (dependencies, name, range) => {
171
+ if (typeof range !== "string" || NON_REGISTRY_RANGE_RE.test(range)) return;
172
+ const alias = /^npm:(.+)@([^@]+)$/.exec(range);
173
+ const target = alias?.[1] ?? name;
174
+ const targetRange = alias?.[2] ?? range;
175
+ if (!dependencies.has(target)) dependencies.set(target, targetRange);
176
+ };
177
+ var collectFromPackage = (dependencies, packageDir) => {
178
+ const package_ = readPackage(packageDir);
179
+ for (const field of DEP_FIELDS) {
180
+ const section = package_?.[field];
181
+ if (typeof section !== "object" || section === null) continue;
182
+ for (const [name, range] of Object.entries(section))
183
+ collectEntry(dependencies, name, range);
184
+ }
185
+ };
186
+ var collectDependencies = (dir) => {
187
+ const dependencies = /* @__PURE__ */ new Map();
188
+ for (const packageDir of [dir, ...getWorkspaceDirectories(dir)])
189
+ collectFromPackage(dependencies, packageDir);
190
+ return dependencies;
191
+ };
192
+ var findDeprecated = async (name, range) => {
193
+ const response = await fetch(
194
+ `https://registry.npmjs.org/${name.replace("/", "%2F")}`,
195
+ {
196
+ headers: { accept: "application/vnd.npm.install-v1+json" },
197
+ signal: AbortSignal.timeout(1e4)
198
+ }
199
+ );
200
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
201
+ const data = await response.json();
202
+ const latest = data["dist-tags"]?.latest;
203
+ const resolved = latest && satisfies(latest, range) ? latest : maxSatisfying(Object.keys(data.versions), range);
204
+ if (!resolved) return null;
205
+ const deprecated = data.versions[resolved]?.deprecated;
206
+ return deprecated ? `${name}@${resolved} \u2014 ${deprecated.slice(0, 120)}` : null;
92
207
  };
93
208
  var LOCAL_CHECKS = [
94
209
  {
95
210
  id: "readme-exists",
96
- desc: "README.md exists",
97
- type: "local",
98
- check: (dir) => existsSync(path.join(dir, "README.md"))
99
- },
100
- {
101
- id: "readme-codacy-grade-badge",
102
- desc: "README has Codacy grade badge",
211
+ desc: "README",
103
212
  type: "local",
104
- publishedOnly: true,
105
- check: (dir) => readmeIncludes(dir, "project/badge/Grade/")
106
- },
107
- {
108
- id: "readme-codacy-coverage-badge",
109
- desc: "README has Codacy coverage badge",
110
- type: "local",
111
- publishedOnly: true,
112
- check: (dir) => readmeIncludes(dir, "project/badge/Coverage/")
213
+ check: (dir) => existsSync2(path2.join(dir, "README.md"))
113
214
  },
114
215
  {
115
216
  id: "readme-npm-badge",
116
- desc: "README has npm badge",
217
+ desc: "npm badge",
117
218
  type: "local",
118
219
  publishedOnly: true,
119
220
  check: (dir) => readmeIncludes(dir, "shields.io/npm/v/")
120
221
  },
121
222
  {
122
- id: "codacy-config",
123
- desc: ".codacy.yml exists",
223
+ id: "codacy",
224
+ desc: "Codacy",
124
225
  type: "local",
125
226
  publishedOnly: true,
126
- check: (dir) => existsSync(path.join(dir, ".codacy.yml"))
227
+ check: (dir) => {
228
+ const missing = [
229
+ readmeIncludes(dir, "project/badge/Grade/") ? null : "grade badge missing in README",
230
+ readmeIncludes(dir, "project/badge/Coverage/") ? null : "coverage badge missing in README",
231
+ existsSync2(path2.join(dir, ".codacy.yml")) ? null : ".codacy.yml missing"
232
+ ].filter((m) => m !== null);
233
+ if (missing.length === 0) return true;
234
+ return { pass: false, detail: missing.join("\n") };
235
+ }
127
236
  },
128
237
  {
129
238
  id: "license",
130
- desc: "LICENSE exists",
239
+ desc: "LICENSE",
131
240
  type: "local",
132
241
  publishedOnly: true,
133
- check: (dir) => existsSync(path.join(dir, "LICENSE"))
242
+ check: (dir) => existsSync2(path2.join(dir, "LICENSE"))
134
243
  },
135
244
  {
136
245
  id: "ci-workflow",
137
- desc: "CI workflow exists",
246
+ desc: "CI calls forge workflow",
138
247
  type: "local",
139
248
  publishedOnly: true,
140
- check: (dir) => existsSync(path.join(dir, ".github/workflows/ci.yml"))
249
+ check: (dir) => {
250
+ if (callsForgeWorkflow(dir) || isForgeRepo(dir)) return true;
251
+ return {
252
+ pass: false,
253
+ detail: "no workflow calls the forge reusable CI \u2014 replace the local ci.yml copy with:\n jobs:\n ci:\n uses: lzear/forge/.github/workflows/ci.yml@<sha> # vX.Y.Z\n(pin to a commit SHA; renovate keeps it fresh)"
254
+ };
255
+ }
141
256
  },
142
257
  {
143
258
  id: "renovate",
144
- desc: "renovate.json exists",
259
+ desc: "renovate.json",
145
260
  type: "local",
146
- check: (dir) => existsSync(path.join(dir, "renovate.json"))
261
+ check: (dir) => existsSync2(path2.join(dir, "renovate.json"))
147
262
  },
148
263
  {
149
264
  id: "pkg-publint",
150
- desc: "publint (all published packages)",
265
+ desc: "publint",
151
266
  type: "local",
152
267
  publishedOnly: true,
153
- check: (dir) => eachPublishedPkg(dir, async (pkgDir) => {
268
+ check: (dir) => eachPublishedPackage(dir, async (pkgDir) => {
154
269
  const { messages } = await publint({ pkgDir });
155
270
  const failures = messages.filter(
156
271
  (m) => m.type === "error" || m.type === "warning"
@@ -164,13 +279,13 @@ var LOCAL_CHECKS = [
164
279
  },
165
280
  {
166
281
  id: "pkg-attw",
167
- desc: "attw (all published packages)",
282
+ desc: "attw",
168
283
  type: "local",
169
284
  check: (dir) => {
170
285
  const bin = findBin("attw", _dirname);
171
286
  if (!bin) return { pass: false, detail: "attw not available" };
172
- return eachPublishedPkg(dir, (pkgDir) => {
173
- const r = spawnSync(
287
+ return eachPublishedPackage(dir, (pkgDir) => {
288
+ const r = spawnSync2(
174
289
  process.execPath,
175
290
  [bin, "--pack", "--profile", "esm-only"],
176
291
  {
@@ -186,12 +301,12 @@ var LOCAL_CHECKS = [
186
301
  },
187
302
  {
188
303
  id: "pkg-knip",
189
- desc: "knip (no unused exports/deps)",
304
+ desc: "knip",
190
305
  type: "local",
191
306
  check: (dir) => {
192
307
  const bin = findBin("knip", _dirname);
193
308
  if (!bin) return { pass: false, detail: "knip not available" };
194
- const r = spawnSync(process.execPath, [bin], {
309
+ const r = spawnSync2(process.execPath, [bin], {
195
310
  cwd: dir,
196
311
  encoding: "utf8",
197
312
  stdio: ["ignore", "pipe", "pipe"],
@@ -204,17 +319,80 @@ var LOCAL_CHECKS = [
204
319
  Run: npx knip` };
205
320
  }
206
321
  },
322
+ {
323
+ id: "monorepo-lint",
324
+ desc: "sherif (workspaces)",
325
+ type: "local",
326
+ check: (dir) => {
327
+ const package_ = readPackage(dir);
328
+ if (!package_ || getWorkspacePatterns(package_).length === 0)
329
+ return { pass: true, detail: "not a monorepo" };
330
+ const bin = findBin("sherif", _dirname);
331
+ if (!bin) return { pass: false, detail: "sherif not available" };
332
+ const r = spawnSync2(process.execPath, [bin], {
333
+ cwd: dir,
334
+ encoding: "utf8",
335
+ stdio: ["ignore", "pipe", "pipe"],
336
+ env: { ...process.env, NO_COLOR: "1" }
337
+ });
338
+ if (r.status === 0) return true;
339
+ const detail = (r.stdout + r.stderr).trim().split("\n").slice(0, 20).join("\n");
340
+ return { pass: false, detail };
341
+ }
342
+ },
343
+ {
344
+ id: "deps-audit",
345
+ desc: "audit",
346
+ type: "local",
347
+ check: (dir) => {
348
+ const pm = detectPackageManager(dir);
349
+ const [command, ...arguments_] = auditCommand(pm);
350
+ const r = spawnSync2(command, arguments_, {
351
+ cwd: dir,
352
+ encoding: "utf8",
353
+ stdio: ["ignore", "pipe", "pipe"]
354
+ });
355
+ if (r.error) return { pass: false, detail: String(r.error) };
356
+ if (r.status === 0) return true;
357
+ const detail = (r.stdout + r.stderr).trim().split("\n").slice(0, 20).join("\n");
358
+ return { pass: false, detail: detail || `audit exited ${r.status}` };
359
+ }
360
+ },
361
+ {
362
+ id: "deps-deprecated",
363
+ desc: "no deprecated deps",
364
+ type: "local",
365
+ check: async (dir) => {
366
+ const dependencies = collectDependencies(dir);
367
+ const failures = [];
368
+ await Promise.all(
369
+ [...dependencies].map(async ([name, range]) => {
370
+ try {
371
+ const deprecated = await findDeprecated(name, range);
372
+ if (deprecated) failures.push(deprecated);
373
+ } catch (error) {
374
+ failures.push(`${name} \u2014 check failed: ${String(error)}`);
375
+ }
376
+ })
377
+ );
378
+ if (failures.length === 0) return true;
379
+ return {
380
+ pass: false,
381
+ detail: failures.toSorted((a, b) => a.localeCompare(b)).join("\n")
382
+ };
383
+ }
384
+ },
207
385
  {
208
386
  id: "deps-fresh",
209
- desc: "dependencies up to date (ncu)",
387
+ desc: "deps up to date",
210
388
  type: "local",
211
389
  check: async (dir) => {
212
- const pkg = readPkg(dir);
213
- const hasWorkspaces = Array.isArray(pkg?.workspaces) && pkg.workspaces.length > 0;
390
+ const package_ = readPackage(dir);
391
+ const hasWorkspaces = Array.isArray(package_?.workspaces) && package_.workspaces.length > 0;
214
392
  try {
215
- const result = await ncuRun({
216
- packageFile: path.join(dir, "package.json"),
217
- ...hasWorkspaces ? { workspaces: true } : {},
393
+ const result = await ncuRun2({
394
+ packageFile: path2.join(dir, "package.json"),
395
+ ...hasWorkspaces && { workspaces: true },
218
396
  silent: true
219
397
  });
220
398
  const entries = hasWorkspaces ? Object.values(
@@ -222,10 +400,9 @@ Run: npx knip` };
222
400
  ).flatMap((x) => Object.entries(x)) : Object.entries(result);
223
401
  if (entries.length === 0) return true;
224
402
  const lines = entries.map(([k, v]) => `${k} \u2192 ${v}`).join("\n");
225
- const cmd = hasWorkspaces ? "yarn dlx npm-check-updates --dep dev,optional,peer,prod,packageManager -u --workspaces && yarn install" : "yarn dlx npm-check-updates --dep dev,optional,peer,prod,packageManager -u && yarn install";
226
403
  return { pass: false, detail: `${lines}
227
404
 
228
- Run: ${cmd}` };
405
+ Run: forge update` };
229
406
  } catch (error) {
230
407
  return { pass: false, detail: String(error) };
231
408
  }
@@ -234,7 +411,7 @@ Run: ${cmd}` };
234
411
  ];
235
412
  var listSecrets = (repo) => {
236
413
  try {
237
- const result = spawnSync(
414
+ const result = spawnSync2(
238
415
  "gh",
239
416
  ["secret", "list", "--repo", repo, "--json", "name"],
240
417
  {
@@ -251,7 +428,7 @@ var listSecrets = (repo) => {
251
428
  var REMOTE_CHECKS = [
252
429
  {
253
430
  id: "secret-codacy-token",
254
- desc: "Secret CODACY_PROJECT_TOKEN set",
431
+ desc: "Codacy secret",
255
432
  type: "remote",
256
433
  publishedOnly: true,
257
434
  check: (repo) => {
@@ -272,19 +449,21 @@ var checkLocal = async (options = {}) => {
272
449
  stdio: ["ignore", "pipe", "ignore"]
273
450
  }).trim();
274
451
  const match = /github\.com[:/](.+?)(?:\.git)?$/.exec(remote);
275
- return match?.[1] ?? path2.basename(dir);
452
+ return match?.[1] ?? path3.basename(dir);
276
453
  } catch {
277
454
  return dir;
278
455
  }
279
456
  })();
280
- const published = hasPublishedPkg(dir);
457
+ const isPublished = hasPublishedPkg(dir);
281
458
  const localResults = await Promise.all(
282
- LOCAL_CHECKS.filter((c) => !c.publishedOnly || published).map(async (c) => {
283
- const raw = await c.check(dir);
284
- return typeof raw === "boolean" ? { id: c.id, desc: c.desc, pass: raw } : { id: c.id, desc: c.desc, ...raw };
285
- })
459
+ LOCAL_CHECKS.filter((c) => !c.publishedOnly || isPublished).map(
460
+ async (c) => {
461
+ const raw = await c.check(dir);
462
+ return typeof raw === "boolean" ? { id: c.id, desc: c.desc, pass: raw } : { id: c.id, desc: c.desc, ...raw };
463
+ }
464
+ )
286
465
  );
287
- const remoteResults = skipRemote ? [] : REMOTE_CHECKS.filter((c) => !c.publishedOnly || published).map((c) => ({
466
+ const remoteResults = skipRemote ? [] : REMOTE_CHECKS.filter((c) => !c.publishedOnly || isPublished).map((c) => ({
288
467
  id: c.id,
289
468
  desc: c.desc,
290
469
  pass: c.check(repo)
@@ -294,11 +473,11 @@ var checkLocal = async (options = {}) => {
294
473
  var checkRepo = async (repo, options = {}) => {
295
474
  const {
296
475
  token,
297
- baseDir = path2.join(tmpdir(), "repo-lint"),
476
+ baseDir = path3.join(tmpdir(), "repo-lint"),
298
477
  skipRemote = false
299
478
  } = options;
300
- const dir = path2.join(baseDir, repo.replace("/", "__"));
301
- if (existsSync2(dir))
479
+ const dir = path3.join(baseDir, repo.replace("/", "__"));
480
+ if (existsSync3(dir))
302
481
  execSync(`git -C ${dir} pull --quiet`, { stdio: "ignore" });
303
482
  else {
304
483
  const url = token ? `https://${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
@@ -329,24 +508,24 @@ var { values } = parseArgs({
329
508
  }
330
509
  });
331
510
  var printReport = (result) => {
332
- let anyFail = false;
511
+ let isAnyFail = false;
333
512
  const pass = result.results.filter((r) => r.pass).length;
334
513
  const total = result.results.length;
335
514
  for (const r of result.results) {
336
515
  console.log(` ${r.pass ? "\u2713" : "\u2717"} ${r.desc}`);
337
- if (!r.pass) anyFail = true;
516
+ if (!r.pass) isAnyFail = true;
338
517
  }
339
518
  console.log(`
340
519
  ${pass}/${total} checks passed`);
341
- return anyFail;
520
+ return isAnyFail;
342
521
  };
343
522
  var main = async () => {
344
- let anyFail = false;
523
+ let isAnyFail = false;
345
524
  if (values.local) {
346
525
  console.log(`
347
526
  \u2500\u2500 ${process.cwd()}`);
348
527
  const result = await checkLocal({ skipRemote: values["skip-remote"] });
349
- anyFail = printReport(result);
528
+ isAnyFail = printReport(result);
350
529
  } else {
351
530
  if (!values.repos) {
352
531
  console.error(
@@ -355,7 +534,8 @@ var main = async () => {
355
534
  process.exit(1);
356
535
  }
357
536
  mkdirSync(values.dir, { recursive: true });
358
- for (const repo of values.repos.split(",").map((r) => r.trim())) {
537
+ const repos = values.repos.split(",").map((r) => r.trim());
538
+ for (const repo of repos) {
359
539
  console.log(`
360
540
  \u2500\u2500 ${repo}`);
361
541
  try {
@@ -364,13 +544,13 @@ var main = async () => {
364
544
  baseDir: values.dir,
365
545
  skipRemote: values["skip-remote"]
366
546
  });
367
- if (printReport(result)) anyFail = true;
547
+ if (printReport(result)) isAnyFail = true;
368
548
  } catch {
369
549
  console.log(" \u2717 could not clone repo");
370
- anyFail = true;
550
+ isAnyFail = true;
371
551
  }
372
552
  }
373
553
  }
374
- process.exit(anyFail ? 1 : 0);
554
+ process.exit(isAnyFail ? 1 : 0);
375
555
  };
376
- void main();
556
+ await main();