@lzear/repo-lint 4.3.0 → 4.4.1

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
@@ -83,7 +83,7 @@ const report = await runUpdate({ dry: true })
83
83
  | `readme-npm-badge` | README has an npm badge |
84
84
  | `codacy` | Codacy grade & coverage badges in README, `.codacy.yml` |
85
85
  | `license` | `LICENSE` exists |
86
- | `ci-workflow` | `.github/workflows/ci.yml` exists |
86
+ | `ci-workflow` | a workflow calls the forge reusable CI workflow |
87
87
  | `renovate` | `renovate.json` exists |
88
88
  | `pkg-publint` | All published packages pass `publint` |
89
89
  | `pkg-attw` | All published packages pass `attw` (ESM-only profile) |
package/dist/bin.js CHANGED
@@ -22,6 +22,7 @@ import { maxSatisfying, satisfies } from "semver";
22
22
 
23
23
  // src/update.ts
24
24
  import { spawnSync } from "child_process";
25
+ import { createHash } from "crypto";
25
26
  import { existsSync, readFileSync, writeFileSync } from "fs";
26
27
  import path from "path";
27
28
  import { pathToFileURL } from "url";
@@ -129,6 +130,17 @@ var readmeIncludes = (dir, needle) => {
129
130
  const f = path2.join(dir, "README.md");
130
131
  return existsSync2(f) && readFileSync2(f, "utf8").includes(needle);
131
132
  };
133
+ var FORGE_WORKFLOW_RE = /uses:\s*lzear\/forge\/\.github\/workflows\/ci\.yml@/;
134
+ var isForgeRepo = (dir) => getWorkspaceDirectories(dir).some(
135
+ (d) => readPackage(d)?.name === "@lzear/forge"
136
+ );
137
+ var callsForgeWorkflow = (dir) => {
138
+ const wfDir = path2.join(dir, ".github", "workflows");
139
+ if (!existsSync2(wfDir)) return false;
140
+ return readdirSync(wfDir).filter((f) => /\.ya?ml$/.test(f)).some(
141
+ (f) => FORGE_WORKFLOW_RE.test(readFileSync2(path2.join(wfDir, f), "utf8"))
142
+ );
143
+ };
132
144
  var auditCommand = (pm) => {
133
145
  switch (pm.name) {
134
146
  case "npm":
@@ -232,10 +244,16 @@ var LOCAL_CHECKS = [
232
244
  },
233
245
  {
234
246
  id: "ci-workflow",
235
- desc: "CI workflow",
247
+ desc: "CI calls forge workflow",
236
248
  type: "local",
237
249
  publishedOnly: true,
238
- check: (dir) => existsSync2(path2.join(dir, ".github/workflows/ci.yml"))
250
+ check: (dir) => {
251
+ if (callsForgeWorkflow(dir) || isForgeRepo(dir)) return true;
252
+ return {
253
+ pass: false,
254
+ 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)"
255
+ };
256
+ }
239
257
  },
240
258
  {
241
259
  id: "renovate",
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ import { maxSatisfying, satisfies } from "semver";
15
15
 
16
16
  // src/update.ts
17
17
  import { spawnSync } from "child_process";
18
+ import { createHash } from "crypto";
18
19
  import { existsSync, readFileSync, writeFileSync } from "fs";
19
20
  import path from "path";
20
21
  import { pathToFileURL } from "url";
@@ -297,7 +298,8 @@ var installCommands = (pm) => {
297
298
  ["yarn", "upgrade"]
298
299
  ] : [
299
300
  ["yarn", "install"],
300
- ["yarn", "up", "--recursive", "*"],
301
+ // '*' alone skips scoped packages (@foo/bar) — needs '@*/*' too
302
+ ["yarn", "up", "--recursive", "*", "@*/*"],
301
303
  ["yarn", "dedupe"]
302
304
  ];
303
305
  case "npm":
@@ -308,11 +310,13 @@ var installCommands = (pm) => {
308
310
  ];
309
311
  }
310
312
  };
313
+ var lockfileHash = (dir) => LOCKFILES.map(([f]) => path.join(dir, f)).filter((f) => existsSync(f)).map((f) => createHash("sha256").update(readFileSync(f)).digest("hex")).join("|");
311
314
  var stepInstall = (dir, pm) => {
312
315
  const base = {
313
316
  id: "install",
314
317
  desc: `install & refresh lockfile (${pm.name})`
315
318
  };
319
+ const before = lockfileHash(dir);
316
320
  const lines = [];
317
321
  for (const [command, ...arguments_] of installCommands(pm)) {
318
322
  lines.push(`$ ${[command, ...arguments_].join(" ")}`);
@@ -324,12 +328,18 @@ var stepInstall = (dir, pm) => {
324
328
  return {
325
329
  ...base,
326
330
  pass: false,
327
- changed: true,
331
+ changed: lockfileHash(dir) !== before,
328
332
  detail: `${lines.join("\n")}
329
333
  exited with ${result.status ?? "signal"}`
330
334
  };
331
335
  }
332
- return { ...base, pass: true, changed: true, detail: lines.join("\n") };
336
+ const changed = lockfileHash(dir) !== before;
337
+ return {
338
+ ...base,
339
+ pass: true,
340
+ changed,
341
+ detail: changed ? lines.join("\n") : "lockfile unchanged"
342
+ };
333
343
  };
334
344
  var runUpdate = async (options = {}) => {
335
345
  const {
@@ -423,6 +433,17 @@ var readmeIncludes = (dir, needle) => {
423
433
  const f = path2.join(dir, "README.md");
424
434
  return existsSync2(f) && readFileSync2(f, "utf8").includes(needle);
425
435
  };
436
+ var FORGE_WORKFLOW_RE = /uses:\s*lzear\/forge\/\.github\/workflows\/ci\.yml@/;
437
+ var isForgeRepo = (dir) => getWorkspaceDirectories(dir).some(
438
+ (d) => readPackage(d)?.name === "@lzear/forge"
439
+ );
440
+ var callsForgeWorkflow = (dir) => {
441
+ const wfDir = path2.join(dir, ".github", "workflows");
442
+ if (!existsSync2(wfDir)) return false;
443
+ return readdirSync(wfDir).filter((f) => /\.ya?ml$/.test(f)).some(
444
+ (f) => FORGE_WORKFLOW_RE.test(readFileSync2(path2.join(wfDir, f), "utf8"))
445
+ );
446
+ };
426
447
  var auditCommand = (pm) => {
427
448
  switch (pm.name) {
428
449
  case "npm":
@@ -526,10 +547,16 @@ var LOCAL_CHECKS = [
526
547
  },
527
548
  {
528
549
  id: "ci-workflow",
529
- desc: "CI workflow",
550
+ desc: "CI calls forge workflow",
530
551
  type: "local",
531
552
  publishedOnly: true,
532
- check: (dir) => existsSync2(path2.join(dir, ".github/workflows/ci.yml"))
553
+ check: (dir) => {
554
+ if (callsForgeWorkflow(dir) || isForgeRepo(dir)) return true;
555
+ return {
556
+ pass: false,
557
+ 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)"
558
+ };
559
+ }
533
560
  },
534
561
  {
535
562
  id: "renovate",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lzear/repo-lint",
3
- "version": "4.3.0",
3
+ "version": "4.4.1",
4
4
  "description": "Checks lzear repos against forge standards",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "sherif": "^1"
52
52
  },
53
53
  "devDependencies": {
54
- "@lzear/configs": "4.3.0",
54
+ "@lzear/configs": "4.4.1",
55
55
  "@types/node": "^26",
56
56
  "@types/semver": "^7",
57
57
  "@vitest/coverage-v8": "^4",