@inixiative/config 0.1.0 → 0.3.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
@@ -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)) {
277
330
  findings.push({
278
331
  level: "error",
279
- message: "bun.lock is gitignored \u2192 stop ignoring it and commit the lockfile"
332
+ message: `lefthook missing \u2192 devDependency ${lefthookPin}`,
333
+ fix: () => {
334
+ pkg.devDependencies ??= {};
335
+ pkg.devDependencies.lefthook = lefthookPin;
336
+ touch();
337
+ }
280
338
  });
281
- } else if (spawnSync("git", ["ls-files", "--error-unmatch", "bun.lock"], { cwd: dir2 }).status !== 0) {
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)) {
343
+ findings.push({
344
+ level: "error",
345
+ message: "lefthook.yml missing \u2192 stub extending @inixiative/config/lefthook/base.yml",
346
+ fix: () => writeFileSync(lefthookPath, lefthookStub)
347
+ });
348
+ } else if (!readFileSync(lefthookPath, "utf8").includes("lefthook/base.yml")) {
349
+ findings.push({
350
+ level: "error",
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) {
282
356
  findings.push({
283
357
  level: "error",
284
- message: "bun.lock untracked \u2192 commit the lockfile"
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
  }
@@ -492,7 +576,8 @@ if (command === "train") {
492
576
  bump.fix?.();
493
577
  }
494
578
  inspection.flush();
495
- let touched = bumps.length > 0;
579
+ const bumped = bumps.length > 0;
580
+ const staleLock = inspection.findings.some((finding) => finding.kind === "stale-lock");
496
581
  const remote = spawnSync2("npm", ["view", `${repo.name}@latest`, "version"], {
497
582
  encoding: "utf8"
498
583
  });
@@ -504,7 +589,7 @@ if (command === "train") {
504
589
  skipped.push(repo.name);
505
590
  continue;
506
591
  }
507
- if (touched || ahead) {
592
+ if (bumped || ahead || staleLock) {
508
593
  const install = spawnSync2("bun", ["install"], { cwd: repo.dir, stdio: "inherit" });
509
594
  if (install.status !== 0) {
510
595
  console.error("\u2717 bun install failed \u2014 aborting train");
@@ -513,7 +598,6 @@ if (command === "train") {
513
598
  const stale = inspect(repo.dir, manifest).findings.filter((finding) => finding.kind === "stale-lock" && finding.name).map((finding) => finding.name);
514
599
  if (stale.length > 0) {
515
600
  spawnSync2("bun", ["update", ...new Set(stale)], { cwd: repo.dir, stdio: "inherit" });
516
- touched = true;
517
601
  }
518
602
  const check = spawnSync2("bun", ["run", "check"], { cwd: repo.dir, stdio: "inherit" });
519
603
  if (check.status !== 0) {
@@ -521,7 +605,11 @@ if (command === "train") {
521
605
  process.exit(1);
522
606
  }
523
607
  }
524
- if (touched) {
608
+ const mutated = spawnSync2("git", ["status", "--porcelain", "package.json", "bun.lock"], {
609
+ cwd: repo.dir,
610
+ encoding: "utf8"
611
+ });
612
+ if (mutated.status === 0 && mutated.stdout.trim().length > 0) {
525
613
  spawnSync2("git", ["add", "package.json", "bun.lock"], { cwd: repo.dir });
526
614
  const commit = spawnSync2(
527
615
  "git",
@@ -584,4 +672,8 @@ if (!flags.has("--no-install")) {
584
672
  }
585
673
  }
586
674
  var final = inspect(dir, manifest, presetFlag);
587
- process.exit(report(final.findings) > 0 ? 1 : 0);
675
+ var remaining = final.findings.filter((finding) => finding.fix);
676
+ for (const finding of remaining) finding.fix?.();
677
+ final.flush();
678
+ var settled = remaining.length > 0 ? inspect(dir, manifest, presetFlag) : final;
679
+ process.exit(report(settled.findings) > 0 ? 1 : 0);
package/dist/tsup.js CHANGED
@@ -8,12 +8,13 @@ var base = {
8
8
  sourcemap: true,
9
9
  clean: true
10
10
  };
11
- var node = (options = {}) => defineConfig({ ...base, ...options });
11
+ var withDts = (options) => options.dts === true ? { ...options, dts: base.dts } : options;
12
+ var node = (options = {}) => defineConfig({ ...base, ...withDts(options) });
12
13
  var react = (options = {}) => {
13
14
  const external = Array.isArray(options.external) ? options.external : [];
14
15
  return defineConfig({
15
16
  ...base,
16
- ...options,
17
+ ...withDts(options),
17
18
  external: [.../* @__PURE__ */ new Set(["react", "react-dom", "react/jsx-runtime", ...external])]
18
19
  });
19
20
  };
@@ -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.3.0",
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,15 +4,20 @@
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
- "required": ["@biomejs/biome", "typescript", "@types/bun"],
10
+ "required": [
11
+ "@biomejs/biome",
12
+ "typescript",
13
+ "@types/bun"
14
+ ],
10
15
  "ecosystem": {
11
- "@inixiative/json-rules": "2.12.1",
12
- "@inixiative/permissions": "0.3.1",
13
- "@inixiative/transitions": "0.0.3",
14
- "@inixiative/rules-builder": "0.13.0",
16
+ "@inixiative/json-rules": "2.13.0",
17
+ "@inixiative/permissions": "0.3.2",
18
+ "@inixiative/transitions": "0.1.0",
19
+ "@inixiative/rules-builder": "0.14.0",
15
20
  "@inixiative/prisma-map": "0.1.0",
16
- "@inixiative/atlas": "0.1.0"
21
+ "@inixiative/atlas": "0.1.1"
17
22
  }
18
23
  }