@inixiative/config 0.1.0 → 0.2.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
@@ -12,6 +12,7 @@ Shared toolchain for the inixiative ecosystem: tsconfig/biome/tsup presets, a ve
12
12
  | `@inixiative/config/biome/base.json` | formatter + linter core (single quotes, lineWidth 100, recommended + strictness block) |
13
13
  | `@inixiative/config/biome/react.json` | overlay: hook dependency linting |
14
14
  | `@inixiative/config/tsup` | `node(options)` / `react(options)` build presets |
15
+ | `@inixiative/config/lefthook/base.yml` | pre-commit hooks: typecheck + biome on staged files |
15
16
 
16
17
  Adoption is three stub files plus the devDependency:
17
18
 
@@ -55,7 +56,7 @@ bunx @inixiative/config scan [root]
55
56
  bunx @inixiative/config train [root]
56
57
  ```
57
58
 
58
- `check` is read-only and exits non-zero on drift — run it in CI after a frozen-lockfile install. It verifies toolchain pins, `"latest"` ranges, `packageManager`/`.bun-version`, legacy `bun.lockb`, a committed (tracked, un-ignored) `bun.lock`, required scripts (`check`/`typecheck`/`lint`/`test`), stub `extends`, presence of this package, and for every ecosystem dependency that the declared range admits the blessed version and the lockfile actually resolves to it (the stale-lockfile class).
59
+ `check` is read-only and exits non-zero on drift — run it in CI after a frozen-lockfile install. It verifies toolchain pins, `"latest"` ranges, `packageManager`/`.bun-version`, legacy `bun.lockb`, a committed (tracked, un-ignored) `bun.lock`, required scripts (`check`/`typecheck`/`lint`/`test`), lefthook (dep + `lefthook.yml` extending the shared hooks + `prepare` script; git repos only), stub `extends`, presence of this package, and for every ecosystem dependency that the declared range admits the blessed version and the lockfile actually resolves to it (the stale-lockfile class).
59
60
 
60
61
  `sync` applies every fix, re-locks via `bun install`, and runs `bun update` on stale ecosystem entries. It refuses to run on a dirty working tree without `--force`. The react preset is inferred from a `react` dependency; override with `--preset`. Missing scripts are filled with defaults; existing script bodies are never touched.
61
62
 
package/dist/cli.js CHANGED
@@ -69,7 +69,7 @@ var tryParseJsonc = (text) => {
69
69
  }
70
70
  };
71
71
  var parseRange = (range) => {
72
- const match = range.match(/^(\^|~|>=)?\s*(\d+)\.(\d+)\.(\d+)(?:-[\w.]+)?$/);
72
+ const match = range.match(/^(\^|~|>=)?\s*(\d+)\.(\d+)\.(\d+)$/);
73
73
  if (!match) return null;
74
74
  return {
75
75
  op: match[1] ?? "=",
@@ -90,7 +90,9 @@ var admits = (range, version) => {
90
90
  if (parts.some((p) => p === true)) return true;
91
91
  return parts.includes(null) ? null : false;
92
92
  }
93
- const parsed = parseRange(range.trim());
93
+ const target = range.trim();
94
+ if (version.includes("-")) return target === version ? true : null;
95
+ const parsed = parseRange(target);
94
96
  if (!parsed) return null;
95
97
  const cmp = compare(version, parsed.version);
96
98
  if (parsed.op === "=") return cmp === 0;
@@ -98,12 +100,22 @@ var admits = (range, version) => {
98
100
  if (cmp < 0) return false;
99
101
  const [major, minor] = parsed.version.split(".").map(Number);
100
102
  const [vMajor, vMinor] = version.split(".").map(Number);
101
- if (parsed.op === "^") return major === 0 ? vMajor === 0 && vMinor === minor : vMajor === major;
103
+ if (parsed.op === "^") {
104
+ if (major > 0) return vMajor === major;
105
+ if (minor > 0) return vMajor === 0 && vMinor === minor;
106
+ return cmp === 0;
107
+ }
102
108
  return vMajor === major && vMinor === minor;
103
109
  };
104
110
  var lockedVersion = (lockText, name) => {
105
- const match = lockText.match(new RegExp(`"${name.replace(/\//g, "\\/")}@(\\d[^"]*)"`));
106
- return match ? match[1] : null;
111
+ const lock = tryParseJsonc(lockText);
112
+ const packages = lock?.packages;
113
+ if (!packages || typeof packages !== "object") return null;
114
+ const entry = packages[name];
115
+ const spec = Array.isArray(entry) ? entry[0] : null;
116
+ if (typeof spec !== "string" || !spec.startsWith(`${name}@`)) return null;
117
+ const version = spec.slice(name.length + 1);
118
+ return /^\d/.test(version) ? version : null;
107
119
  };
108
120
  var topoOrder = (root, manifest2) => {
109
121
  const repos = discoverRepos(root, manifest2).map((repo) => ({
@@ -273,15 +285,87 @@ function inspect(dir2, manifest2, presetOverride) {
273
285
  level: "error",
274
286
  message: "bun.lock missing \u2192 run bun install and commit the lockfile"
275
287
  });
276
- } else if (spawnSync("git", ["check-ignore", "-q", "bun.lock"], { cwd: dir2 }).status === 0) {
288
+ } else {
289
+ const ignoreStatus = spawnSync("git", ["check-ignore", "-q", "bun.lock"], {
290
+ cwd: dir2
291
+ }).status;
292
+ if (ignoreStatus === 0) {
293
+ findings.push({
294
+ level: "error",
295
+ message: "bun.lock is gitignored \u2192 removing the ignore rule; commit the lockfile",
296
+ fix: () => {
297
+ const gitignorePath = join(dir2, ".gitignore");
298
+ if (!existsSync(gitignorePath)) return;
299
+ const kept = readFileSync(gitignorePath, "utf8").split("\n").filter((line) => !/^\s*bun\.lockb?\s*$/.test(line));
300
+ writeFileSync(gitignorePath, kept.join("\n"));
301
+ }
302
+ });
303
+ } else if (ignoreStatus === 1) {
304
+ const trackedStatus = spawnSync("git", ["ls-files", "--error-unmatch", "bun.lock"], {
305
+ cwd: dir2
306
+ }).status;
307
+ if (trackedStatus === 1) {
308
+ findings.push({
309
+ level: "error",
310
+ message: "bun.lock untracked \u2192 staging it; commit the lockfile",
311
+ fix: () => {
312
+ spawnSync("git", ["add", "bun.lock"], { cwd: dir2 });
313
+ }
314
+ });
315
+ } else if (trackedStatus !== 0) {
316
+ findings.push({
317
+ level: "warn",
318
+ message: "git ls-files failed \u2014 cannot verify the lockfile is tracked"
319
+ });
320
+ }
321
+ } else {
322
+ findings.push({
323
+ level: "warn",
324
+ message: "git check-ignore failed \u2014 cannot verify the lockfile is not ignored"
325
+ });
326
+ }
327
+ }
328
+ const lefthookPin = manifest2.toolchain.lefthook;
329
+ if (lefthookPin && !DEP_FIELDS.some((field) => pkg[field]?.lefthook)) {
330
+ findings.push({
331
+ level: "error",
332
+ message: `lefthook missing \u2192 devDependency ${lefthookPin}`,
333
+ fix: () => {
334
+ pkg.devDependencies ??= {};
335
+ pkg.devDependencies.lefthook = lefthookPin;
336
+ touch();
337
+ }
338
+ });
339
+ }
340
+ const lefthookPath = join(dir2, "lefthook.yml");
341
+ const lefthookStub = "extends:\n - node_modules/@inixiative/config/lefthook/base.yml\n";
342
+ if (!existsSync(lefthookPath)) {
277
343
  findings.push({
278
344
  level: "error",
279
- message: "bun.lock is gitignored \u2192 stop ignoring it and commit the lockfile"
345
+ message: "lefthook.yml missing \u2192 stub extending @inixiative/config/lefthook/base.yml",
346
+ fix: () => writeFileSync(lefthookPath, lefthookStub)
280
347
  });
281
- } else if (spawnSync("git", ["ls-files", "--error-unmatch", "bun.lock"], { cwd: dir2 }).status !== 0) {
348
+ } else if (!readFileSync(lefthookPath, "utf8").includes("lefthook/base.yml")) {
282
349
  findings.push({
283
350
  level: "error",
284
- message: "bun.lock untracked \u2192 commit the lockfile"
351
+ message: "lefthook.yml does not extend the shared hooks \u2192 replaced with extends stub (local hooks overwritten \u2014 review the diff)",
352
+ fix: () => writeFileSync(lefthookPath, lefthookStub)
353
+ });
354
+ }
355
+ if (!pkg.scripts?.prepare) {
356
+ findings.push({
357
+ level: "error",
358
+ message: 'scripts.prepare missing \u2192 "lefthook install"',
359
+ fix: () => {
360
+ pkg.scripts ??= {};
361
+ pkg.scripts.prepare = "lefthook install";
362
+ touch();
363
+ }
364
+ });
365
+ } else if (!pkg.scripts.prepare.includes("lefthook")) {
366
+ findings.push({
367
+ level: "warn",
368
+ message: "scripts.prepare does not run lefthook install \u2014 hooks will not auto-install"
285
369
  });
286
370
  }
287
371
  }
@@ -0,0 +1,8 @@
1
+ pre-commit:
2
+ parallel: true
3
+ commands:
4
+ typecheck:
5
+ run: bun run typecheck
6
+ lint:
7
+ run: bunx biome check --no-errors-on-unmatched --diagnostic-level=error {staged_files}
8
+ glob: "*.{ts,tsx}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inixiative/config",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Shared toolchain for the inixiative ecosystem: tsconfig/biome/tsup presets, a version manifest (BOM), and a sync/check CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,6 +19,7 @@
19
19
  "dist",
20
20
  "tsconfig",
21
21
  "biome",
22
+ "lefthook",
22
23
  "versions.json",
23
24
  "README.md"
24
25
  ],
@@ -29,12 +30,14 @@
29
30
  "./tsconfig/react.json": "./tsconfig/react.json",
30
31
  "./biome/base.json": "./biome/base.json",
31
32
  "./biome/react.json": "./biome/react.json",
33
+ "./lefthook/base.yml": "./lefthook/base.yml",
32
34
  "./tsup": {
33
35
  "types": "./dist/tsup.d.ts",
34
36
  "import": "./dist/tsup.js"
35
37
  }
36
38
  },
37
39
  "scripts": {
40
+ "prepare": "lefthook install",
38
41
  "build": "tsup",
39
42
  "test": "bun test",
40
43
  "typecheck": "tsc --noEmit",
@@ -47,6 +50,7 @@
47
50
  "devDependencies": {
48
51
  "@biomejs/biome": "2.5.2",
49
52
  "@types/bun": "1.3.14",
53
+ "lefthook": "2.1.9",
50
54
  "tsup": "8.5.1",
51
55
  "typescript": "6.0.3"
52
56
  },
package/versions.json CHANGED
@@ -4,7 +4,8 @@
4
4
  "@biomejs/biome": "2.5.2",
5
5
  "typescript": "6.0.3",
6
6
  "tsup": "8.5.1",
7
- "@types/bun": "1.3.14"
7
+ "@types/bun": "1.3.14",
8
+ "lefthook": "2.1.9"
8
9
  },
9
10
  "required": ["@biomejs/biome", "typescript", "@types/bun"],
10
11
  "ecosystem": {