@ingram-tech/nk-dev 0.2.5 → 0.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 +18 -16
- package/bin/nk.js +18 -8
- package/guide.md +6 -4
- package/lib/doctor.js +269 -0
- package/lib/drift.js +36 -0
- package/lib/format.js +8 -114
- package/lib/oxlint-plugins/base-ui.js +121 -0
- package/lib/passthrough.js +23 -7
- package/oxlintrc.json +2 -0
- package/package.json +7 -8
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The nextkit **dev toolchain in one package**. Everything a site needs at
|
|
4
4
|
development time — and nothing that ships to production — lives here:
|
|
5
5
|
|
|
6
|
-
- the **`nk` CLI** (`nk dev` / `format` / `lint` / `knip` / `check` / `type-check` / `build`);
|
|
6
|
+
- the **`nk` CLI** (`nk dev` / `format` / `lint` / `knip` / `check` / `type-check` / `test` / `build`, plus `nk doctor`);
|
|
7
7
|
- the shared **oxlint + oxfmt**, **TypeScript**, and **Vitest** config;
|
|
8
8
|
- **knip** (unused dependency / export / file detection), bundled and run by `nk check`;
|
|
9
9
|
- the **oxfmt format-on-commit** git hook (`nextkit-format-staged`);
|
|
@@ -46,10 +46,6 @@ Everything is `extends`-based, so the house config is enforced by default but
|
|
|
46
46
|
overridable — layer your own rules on top, or replace a stub entirely (e.g. drop
|
|
47
47
|
in a `biome.json` instead of the oxlint/oxfmt stubs).
|
|
48
48
|
|
|
49
|
-
Already on the old split packages or `@ingram-tech/biome-config`? Run the
|
|
50
|
-
codemod — see
|
|
51
|
-
[`oxlint-migration.md`](https://github.com/ingram-technologies/nextkit/blob/main/docs/oxlint-migration.md).
|
|
52
|
-
|
|
53
49
|
## The `nk` command
|
|
54
50
|
|
|
55
51
|
Point your package.json scripts at it:
|
|
@@ -62,6 +58,7 @@ Point your package.json scripts at it:
|
|
|
62
58
|
"lint": "nk lint",
|
|
63
59
|
"check": "nk check",
|
|
64
60
|
"type-check": "nk type-check",
|
|
61
|
+
"test": "nk test",
|
|
65
62
|
"build": "nk build"
|
|
66
63
|
}
|
|
67
64
|
}
|
|
@@ -81,6 +78,9 @@ tsc), so versions stay under each site's control — nk just orchestrates.
|
|
|
81
78
|
|
|
82
79
|
- **`nk init`** — scaffold this project to use nextkit (see above). Idempotent:
|
|
83
80
|
skips files that already exist.
|
|
81
|
+
- **`nk doctor [--fix]`** — report drift from the canonical nk-dev toolchain
|
|
82
|
+
(superseded deps, config `extends`, package.json scripts, the agent-guide
|
|
83
|
+
import, a stale `.prettierignore`); `--fix` applies the auto-fixable findings.
|
|
84
84
|
- **`nk dev`** — start the Next dev server on the golden-path local database
|
|
85
85
|
(see [`db-package.md`](https://github.com/ingram-technologies/nextkit/blob/main/docs/db-package.md)):
|
|
86
86
|
- **PGlite** — if `@ingram-tech/nk-db`'s `nk-pglite-dev` bin resolves, hand off
|
|
@@ -88,13 +88,15 @@ tsc), so versions stay under each site's control — nk just orchestrates.
|
|
|
88
88
|
`DATABASE_URL`, then `next dev --turbopack`. No Docker, no daemon.
|
|
89
89
|
- **Plain** — otherwise just `next dev` (static/marketing sites with no DB).
|
|
90
90
|
- **`nk format` / `nk format --check`** — formats code (JS/TS/JSON/CSS) with
|
|
91
|
-
oxfmt
|
|
91
|
+
oxfmt. `--check` verifies without writing (CI). SQL isn't formatted — it's
|
|
92
|
+
generated (drizzle migrations, `pg_dump` baselines, pglite fixtures).
|
|
92
93
|
- **`nk lint`** — `oxlint`.
|
|
93
94
|
- **`nk knip`** — `knip` (unused dependencies / exports / files).
|
|
94
|
-
- **`nk check`** — `oxlint` + `oxfmt --check` +
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
- **`nk check`** — `oxlint` + `oxfmt --check` + `knip` (only when the repo has a
|
|
96
|
+
knip config) + the agent-guide import gate. The CI gate; runs every checker and
|
|
97
|
+
reports them all before failing.
|
|
97
98
|
- **`nk type-check`** — `next typegen && tsc --noEmit`.
|
|
99
|
+
- **`nk test [...]`** — `vitest run`, extra args passed through.
|
|
98
100
|
- **`nk build [...]`** — `next build`, extra args passed through.
|
|
99
101
|
|
|
100
102
|
## Exports
|
|
@@ -110,11 +112,11 @@ tsc), so versions stay under each site's control — nk just orchestrates.
|
|
|
110
112
|
"@ingram-tech/nk-dev/guide.md" // the AI agent guide
|
|
111
113
|
```
|
|
112
114
|
|
|
113
|
-
## Why
|
|
115
|
+
## Why no SQL formatting?
|
|
114
116
|
|
|
115
|
-
oxfmt is the formatter for code and
|
|
116
|
-
|
|
117
|
-
`
|
|
118
|
-
files
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
oxfmt is the formatter for code, and it can't format SQL — but nk-dev doesn't
|
|
118
|
+
format SQL at all. The SQL in our repos is ~entirely generated (drizzle
|
|
119
|
+
migrations, `pg_dump` baselines, pglite fixtures), so formatting it only churned
|
|
120
|
+
generated files and crashed on psql directives (`\restrict`) for no gain. nk-dev
|
|
121
|
+
therefore carries no `prettier` dependency; if a site has a leftover
|
|
122
|
+
`.prettierignore`, `nk doctor --fix` removes it.
|
package/bin/nk.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { dev } from "../lib/dev.js";
|
|
3
|
+
import { doctor } from "../lib/doctor.js";
|
|
3
4
|
import { format } from "../lib/format.js";
|
|
4
5
|
import { init } from "../lib/init.js";
|
|
5
6
|
import { knip } from "../lib/knip.js";
|
|
6
|
-
import { build, check, lint, typeCheck } from "../lib/passthrough.js";
|
|
7
|
+
import { build, check, lint, test, typeCheck } from "../lib/passthrough.js";
|
|
7
8
|
|
|
8
9
|
const USAGE = `nk — the nextkit CLI
|
|
9
10
|
|
|
@@ -13,18 +14,21 @@ Commands:
|
|
|
13
14
|
init Scaffold this project to use nextkit: writes the oxlint /
|
|
14
15
|
oxfmt / TypeScript / Vitest config, the format-on-commit
|
|
15
16
|
hook, and the agent-guide import. Skips files that exist.
|
|
17
|
+
doctor [--fix] Report drift from the canonical nk-dev toolchain (scripts,
|
|
18
|
+
superseded deps, config extends, guide import); --fix applies.
|
|
16
19
|
dev Start the Next dev server (Turbopack). Boots local PGlite
|
|
17
20
|
first when @ingram-tech/nk-db is installed (no Docker).
|
|
18
|
-
format [--check] Format code with oxfmt
|
|
19
|
-
verifies without writing (for CI).
|
|
21
|
+
format [--check] Format code with oxfmt. --check verifies without writing.
|
|
20
22
|
lint Lint with oxlint.
|
|
21
23
|
knip Find unused dependencies / exports / files with knip.
|
|
22
|
-
check The CI gate: lint + format verify +
|
|
23
|
-
|
|
24
|
+
check The CI gate: lint + format verify + knip (when configured)
|
|
25
|
+
+ the agent-guide import gate.
|
|
24
26
|
type-check next typegen && tsc --noEmit.
|
|
27
|
+
test [...] vitest run (extra args passed through).
|
|
25
28
|
build [...] next build (extra args passed through).
|
|
26
29
|
|
|
27
|
-
Code formats with oxfmt and lints with oxlint
|
|
30
|
+
Code formats with oxfmt and lints with oxlint. SQL is not formatted (it's
|
|
31
|
+
generated); nk-dev carries no Prettier.`;
|
|
28
32
|
|
|
29
33
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
30
34
|
|
|
@@ -32,11 +36,14 @@ switch (cmd) {
|
|
|
32
36
|
case "init":
|
|
33
37
|
init();
|
|
34
38
|
break;
|
|
39
|
+
case "doctor":
|
|
40
|
+
doctor(rest);
|
|
41
|
+
break;
|
|
35
42
|
case "dev":
|
|
36
43
|
dev(rest);
|
|
37
44
|
break;
|
|
38
45
|
case "format":
|
|
39
|
-
|
|
46
|
+
format({ check: rest.includes("--check") });
|
|
40
47
|
break;
|
|
41
48
|
case "lint":
|
|
42
49
|
lint();
|
|
@@ -45,11 +52,14 @@ switch (cmd) {
|
|
|
45
52
|
knip(rest);
|
|
46
53
|
break;
|
|
47
54
|
case "check":
|
|
48
|
-
|
|
55
|
+
check();
|
|
49
56
|
break;
|
|
50
57
|
case "type-check":
|
|
51
58
|
typeCheck();
|
|
52
59
|
break;
|
|
60
|
+
case "test":
|
|
61
|
+
test(rest);
|
|
62
|
+
break;
|
|
53
63
|
case "build":
|
|
54
64
|
build(rest);
|
|
55
65
|
break;
|
package/guide.md
CHANGED
|
@@ -14,9 +14,11 @@ package. Stay a thin, standard Next.js app (bun · oxlint + oxfmt · strict TS).
|
|
|
14
14
|
`as`-cast it.** Every `/api` route and webhook handler takes untrusted input;
|
|
15
15
|
an `as` cast is a lie the type-checker can't catch at runtime.
|
|
16
16
|
- Format/lint with **oxlint + oxfmt** via `nk` (`@ingram-tech/nk-dev`); don't
|
|
17
|
-
reintroduce ESLint
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
reintroduce ESLint or Prettier (SQL isn't formatted — it's generated). nk-dev
|
|
18
|
+
owns the toolchain: don't re-declare `oxfmt`/`oxlint`/`typescript` or the
|
|
19
|
+
retired `@ingram-tech/{oxlint,typescript}-config` — `nk doctor` flags the drift.
|
|
20
|
+
`nk` is optional convenience that only orchestrates the standard tools — the
|
|
21
|
+
site must stay buildable with plain `next build` / `next dev`.
|
|
20
22
|
|
|
21
23
|
## Route & URL conventions
|
|
22
24
|
|
|
@@ -81,6 +83,6 @@ the UI/page tree, and never expose internal plumbing under `/api/`.
|
|
|
81
83
|
- `@ingram-tech/nk-billing` — Stripe primitives: subscriptions, a Stripe-side wallet, and an optional Postgres credit ledger behind the `/credits` subpath. Prices resolve at runtime by Stripe `lookup_key` — **never hardcode a price id**, so test and live share one code path
|
|
82
84
|
- `@ingram-tech/bot-protection` — invisible form protection (honeypot + timing + Vercel BotID)
|
|
83
85
|
- `@ingram-tech/nk-i18n` — type-safe, English-as-key i18n: the English source text *is* the key (no `en.json`), ICU MessageFormat, colocated JSON catalogs; routing is left to the site
|
|
84
|
-
- `@ingram-tech/nk-dev` — the whole dev toolchain in one devDependency: the `nk` command (`nk dev` boots local PGlite via `@ingram-tech/nk-db` if installed, then Next; plus `nk format` / `lint` / `knip` / `check` / `type-check` / `build`), the shared oxlint + oxfmt / TypeScript / Vitest config, knip, the oxfmt format-on-commit hook, and this guide. `nk check` runs every fast checker (oxlint, oxfmt,
|
|
86
|
+
- `@ingram-tech/nk-dev` — the whole dev toolchain in one devDependency: the `nk` command (`nk dev` boots local PGlite via `@ingram-tech/nk-db` if installed, then Next; plus `nk format` / `lint` / `knip` / `check` / `type-check` / `test` / `build`), the shared oxlint + oxfmt / TypeScript / Vitest config, knip, the oxfmt format-on-commit hook, and this guide. `nk check` runs every fast checker (oxlint, oxfmt, knip) in one gate; `nk doctor --fix` reconciles a site back to the canonical toolchain. `nk init` scaffolds a site to use it all.
|
|
85
87
|
|
|
86
88
|
For detail on any package, read its README in `node_modules/@ingram-tech/<pkg>/`.
|
package/lib/doctor.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { SUPERSEDED_DEPS } from "./drift.js";
|
|
4
|
+
|
|
5
|
+
// The canonical script → command mapping for a site on the nk-dev toolchain.
|
|
6
|
+
// The key is matched loosely (`type-check` and `typecheck` are both accepted);
|
|
7
|
+
// the value is what the script should run.
|
|
8
|
+
const CANONICAL_SCRIPTS = {
|
|
9
|
+
lint: "nk lint",
|
|
10
|
+
format: "nk format",
|
|
11
|
+
check: "nk check",
|
|
12
|
+
"type-check": "nk type-check",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const GUIDE_IMPORT = "@./node_modules/@ingram-tech/nk-dev/guide.md";
|
|
16
|
+
const OXLINTRC_EXTENDS = "./node_modules/@ingram-tech/nk-dev/oxlintrc.json";
|
|
17
|
+
const TSCONFIG_EXTENDS = "@ingram-tech/nk-dev/tsconfig/nextjs.json";
|
|
18
|
+
|
|
19
|
+
/** Read + parse a JSON file, or null if absent/unparseable. */
|
|
20
|
+
function readJson(file) {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Write JSON in the house style (tabs, trailing newline). */
|
|
29
|
+
function writeJson(file, value) {
|
|
30
|
+
writeFileSync(file, `${JSON.stringify(value, null, "\t")}\n`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Collect drift findings for the repo at `cwd`. Each finding is
|
|
35
|
+
* `{ id, level, message, fix }`, where `fix(cwd)` applies the correction and
|
|
36
|
+
* returns a short past-tense note. `level` is "error" (breaks the model) or
|
|
37
|
+
* "warn" (cosmetic / cleanup).
|
|
38
|
+
*/
|
|
39
|
+
function findings(cwd) {
|
|
40
|
+
const out = [];
|
|
41
|
+
const pkgPath = resolve(cwd, "package.json");
|
|
42
|
+
const pkg = readJson(pkgPath);
|
|
43
|
+
if (!pkg) return out; // not a package — nothing to reconcile
|
|
44
|
+
|
|
45
|
+
// 1. Scripts point at `nk`. Accept either `type-check` or `typecheck` key.
|
|
46
|
+
const scripts = pkg.scripts ?? {};
|
|
47
|
+
for (const [name, wanted] of Object.entries(CANONICAL_SCRIPTS)) {
|
|
48
|
+
const key =
|
|
49
|
+
name === "type-check" &&
|
|
50
|
+
scripts["typecheck"] !== undefined &&
|
|
51
|
+
scripts["type-check"] === undefined
|
|
52
|
+
? "typecheck"
|
|
53
|
+
: name;
|
|
54
|
+
const current = scripts[key];
|
|
55
|
+
if (current === wanted) continue;
|
|
56
|
+
// Only flag `check` as missing if the repo has scripts at all; a repo
|
|
57
|
+
// truly without a check script still wants one on the nk model.
|
|
58
|
+
out.push({
|
|
59
|
+
id: `script:${key}`,
|
|
60
|
+
level: "error",
|
|
61
|
+
message:
|
|
62
|
+
current === undefined
|
|
63
|
+
? `missing \`${key}\` script (want "${wanted}")`
|
|
64
|
+
: `\`${key}\` script is "${current}" (want "${wanted}")`,
|
|
65
|
+
fix: (dir) => {
|
|
66
|
+
const p = resolve(dir, "package.json");
|
|
67
|
+
const j = readJson(p);
|
|
68
|
+
j.scripts ??= {};
|
|
69
|
+
j.scripts[key] = wanted;
|
|
70
|
+
writeJson(p, j);
|
|
71
|
+
return `set \`${key}\` → "${wanted}"`;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 2. Superseded deps still declared.
|
|
77
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
78
|
+
if (deps["@ingram-tech/nk-dev"]) {
|
|
79
|
+
const superseded = SUPERSEDED_DEPS.filter((d) => d in deps);
|
|
80
|
+
for (const dep of superseded) {
|
|
81
|
+
out.push({
|
|
82
|
+
id: `dep:${dep}`,
|
|
83
|
+
level: "error",
|
|
84
|
+
message: `depends on \`${dep}\` — superseded by @ingram-tech/nk-dev`,
|
|
85
|
+
fix: (dir) => {
|
|
86
|
+
const p = resolve(dir, "package.json");
|
|
87
|
+
const j = readJson(p);
|
|
88
|
+
delete j.dependencies?.[dep];
|
|
89
|
+
delete j.devDependencies?.[dep];
|
|
90
|
+
writeJson(p, j);
|
|
91
|
+
return `removed \`${dep}\``;
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
out.push({
|
|
97
|
+
id: "dep:nk-dev-missing",
|
|
98
|
+
level: "warn",
|
|
99
|
+
message:
|
|
100
|
+
"does not depend on @ingram-tech/nk-dev (run `nk init` to adopt the toolchain)",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 3. .oxlintrc.json extends nk-dev (not the retired oxlint-config).
|
|
105
|
+
const oxPath = resolve(cwd, ".oxlintrc.json");
|
|
106
|
+
const ox = readJson(oxPath);
|
|
107
|
+
if (ox) {
|
|
108
|
+
const ext = [].concat(ox.extends ?? []);
|
|
109
|
+
if (!ext.includes(OXLINTRC_EXTENDS)) {
|
|
110
|
+
out.push({
|
|
111
|
+
id: "oxlintrc:extends",
|
|
112
|
+
level: "error",
|
|
113
|
+
message: `.oxlintrc.json does not extend nk-dev's config (extends: ${JSON.stringify(ox.extends ?? null)})`,
|
|
114
|
+
fix: (dir) => {
|
|
115
|
+
const p = resolve(dir, ".oxlintrc.json");
|
|
116
|
+
const j = readJson(p);
|
|
117
|
+
// Keep any non-superseded extends (e.g. a local tier-b tweak
|
|
118
|
+
// living outside oxlint-config), drop oxlint-config, ensure ours.
|
|
119
|
+
const kept = []
|
|
120
|
+
.concat(j.extends ?? [])
|
|
121
|
+
.filter(
|
|
122
|
+
(e) =>
|
|
123
|
+
!String(e).includes("oxlint-config") &&
|
|
124
|
+
e !== OXLINTRC_EXTENDS,
|
|
125
|
+
);
|
|
126
|
+
j.extends = [OXLINTRC_EXTENDS, ...kept];
|
|
127
|
+
j.$schema ??= "./node_modules/oxlint/configuration_schema.json";
|
|
128
|
+
writeJson(p, j);
|
|
129
|
+
return "repointed .oxlintrc.json extends → nk-dev";
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 4. tsconfig.json extends nk-dev's tsconfig (not the retired typescript-config).
|
|
136
|
+
const tsPath = resolve(cwd, "tsconfig.json");
|
|
137
|
+
const ts = readJson(tsPath);
|
|
138
|
+
if (ts && String(ts.extends ?? "").includes("typescript-config")) {
|
|
139
|
+
out.push({
|
|
140
|
+
id: "tsconfig:extends",
|
|
141
|
+
level: "error",
|
|
142
|
+
message: `tsconfig.json extends "${ts.extends}" — use nk-dev's tsconfig`,
|
|
143
|
+
fix: (dir) => {
|
|
144
|
+
const p = resolve(dir, "tsconfig.json");
|
|
145
|
+
const j = readJson(p);
|
|
146
|
+
j.extends = TSCONFIG_EXTENDS;
|
|
147
|
+
writeJson(p, j);
|
|
148
|
+
return `repointed tsconfig.json extends → ${TSCONFIG_EXTENDS}`;
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 5. CLAUDE.md imports the shared guide.
|
|
154
|
+
const claudePath = resolve(cwd, "CLAUDE.md");
|
|
155
|
+
if (deps["@ingram-tech/nk-dev"]) {
|
|
156
|
+
const claude = existsSync(claudePath) ? readFileSync(claudePath, "utf8") : null;
|
|
157
|
+
if (claude === null || !/@\S*nk-dev\/guide\.md/.test(claude)) {
|
|
158
|
+
out.push({
|
|
159
|
+
id: "claude:guide-import",
|
|
160
|
+
level: "error",
|
|
161
|
+
message:
|
|
162
|
+
claude === null
|
|
163
|
+
? "no CLAUDE.md importing the nk-dev guide"
|
|
164
|
+
: "CLAUDE.md does not @import the nk-dev guide",
|
|
165
|
+
fix: (dir) => {
|
|
166
|
+
const p = resolve(dir, "CLAUDE.md");
|
|
167
|
+
const body = existsSync(p)
|
|
168
|
+
? readFileSync(p, "utf8")
|
|
169
|
+
: "# Project\n";
|
|
170
|
+
writeFileSync(
|
|
171
|
+
p,
|
|
172
|
+
`${body.replace(/\n*$/, "")}\n\n${GUIDE_IMPORT}\n`,
|
|
173
|
+
);
|
|
174
|
+
return "added the guide import to CLAUDE.md";
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 6. knip.json ignoreDependencies referencing superseded packages (stale).
|
|
181
|
+
const knipPath = resolve(cwd, "knip.json");
|
|
182
|
+
const knip = readJson(knipPath);
|
|
183
|
+
if (knip && Array.isArray(knip.ignoreDependencies)) {
|
|
184
|
+
const stale = knip.ignoreDependencies.filter((d) =>
|
|
185
|
+
SUPERSEDED_DEPS.includes(d),
|
|
186
|
+
);
|
|
187
|
+
if (stale.length) {
|
|
188
|
+
out.push({
|
|
189
|
+
id: "knip:stale-ignores",
|
|
190
|
+
level: "warn",
|
|
191
|
+
message: `knip.json ignoreDependencies references removed packages: ${stale.join(", ")}`,
|
|
192
|
+
fix: (dir) => {
|
|
193
|
+
const p = resolve(dir, "knip.json");
|
|
194
|
+
const j = readJson(p);
|
|
195
|
+
j.ignoreDependencies = j.ignoreDependencies.filter(
|
|
196
|
+
(d) => !SUPERSEDED_DEPS.includes(d),
|
|
197
|
+
);
|
|
198
|
+
writeJson(p, j);
|
|
199
|
+
return `pruned stale knip ignoreDependencies: ${stale.join(", ")}`;
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 7. .prettierignore is now dead weight (nk no longer formats SQL).
|
|
206
|
+
const prettierIgnore = resolve(cwd, ".prettierignore");
|
|
207
|
+
if (existsSync(prettierIgnore)) {
|
|
208
|
+
out.push({
|
|
209
|
+
id: "prettierignore",
|
|
210
|
+
level: "warn",
|
|
211
|
+
message:
|
|
212
|
+
".prettierignore is unused (nk no longer runs Prettier) — remove it",
|
|
213
|
+
fix: (dir) => {
|
|
214
|
+
rmSync(resolve(dir, ".prettierignore"));
|
|
215
|
+
return "removed .prettierignore";
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* `nk doctor [--fix]` — report drift from the canonical nk-dev model (scripts,
|
|
225
|
+
* dependencies, oxlint/tsconfig extends, the CLAUDE.md guide import, stale knip
|
|
226
|
+
* ignores, a dead .prettierignore). With `--fix`, apply every auto-fixable
|
|
227
|
+
* finding, then remind to reinstall.
|
|
228
|
+
*/
|
|
229
|
+
export function doctor(args = []) {
|
|
230
|
+
const fix = args.includes("--fix");
|
|
231
|
+
const cwd = process.cwd();
|
|
232
|
+
const results = findings(cwd);
|
|
233
|
+
|
|
234
|
+
if (results.length === 0) {
|
|
235
|
+
console.log("nk doctor: ✓ on the canonical nk-dev toolchain — no drift.");
|
|
236
|
+
process.exit(0);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!fix) {
|
|
240
|
+
const errors = results.filter((r) => r.level === "error").length;
|
|
241
|
+
console.log(`nk doctor: ${results.length} finding(s):\n`);
|
|
242
|
+
for (const r of results) {
|
|
243
|
+
const mark = r.level === "error" ? "✗" : "•";
|
|
244
|
+
console.log(` ${mark} ${r.message}`);
|
|
245
|
+
}
|
|
246
|
+
console.log(
|
|
247
|
+
`\n Run \`nk doctor --fix\` to apply the ${results.filter((r) => r.fix).length} auto-fixable one(s).`,
|
|
248
|
+
);
|
|
249
|
+
// Exit non-zero only when there are model-breaking findings, so `nk
|
|
250
|
+
// doctor` can gate CI while cosmetic warnings don't.
|
|
251
|
+
process.exit(errors > 0 ? 1 : 0);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let applied = 0;
|
|
255
|
+
let touchedDeps = false;
|
|
256
|
+
for (const r of results) {
|
|
257
|
+
if (!r.fix) {
|
|
258
|
+
console.log(` – ${r.message} (manual)`);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const note = r.fix(cwd);
|
|
262
|
+
console.log(` ✓ ${note}`);
|
|
263
|
+
applied++;
|
|
264
|
+
if (r.id.startsWith("dep:")) touchedDeps = true;
|
|
265
|
+
}
|
|
266
|
+
console.log(`\nnk doctor: applied ${applied} fix(es).`);
|
|
267
|
+
if (touchedDeps) console.log(" → run `bun install` to sync the lockfile.");
|
|
268
|
+
process.exit(0);
|
|
269
|
+
}
|
package/lib/drift.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Packages whose job `@ingram-tech/nk-dev` now owns — either because it bundles
|
|
6
|
+
* the tool (oxfmt, oxlint) or because it superseded a standalone shared-config /
|
|
7
|
+
* CLI / hooks package. A site on the nk-dev toolchain shouldn't re-declare any
|
|
8
|
+
* of these: doing so either splits a tool's version across two pins (the exact
|
|
9
|
+
* drift nk-dev centralizes away) or keeps a dead dependency on a retired package.
|
|
10
|
+
*/
|
|
11
|
+
export const SUPERSEDED_DEPS = [
|
|
12
|
+
"oxfmt",
|
|
13
|
+
"oxlint",
|
|
14
|
+
"prettier",
|
|
15
|
+
"prettier-plugin-sql",
|
|
16
|
+
"@ingram-tech/oxlint-config",
|
|
17
|
+
"@ingram-tech/typescript-config",
|
|
18
|
+
"@ingram-tech/nk-cli",
|
|
19
|
+
"@ingram-tech/git-hooks",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The superseded packages this repo still declares. Empty when the repo is
|
|
24
|
+
* clean, or when it doesn't depend on nk-dev at all (nothing to enforce).
|
|
25
|
+
*/
|
|
26
|
+
export function toolDrift(cwd = process.cwd()) {
|
|
27
|
+
let pkg;
|
|
28
|
+
try {
|
|
29
|
+
pkg = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf8"));
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
34
|
+
if (!deps["@ingram-tech/nk-dev"]) return [];
|
|
35
|
+
return SUPERSEDED_DEPS.filter((d) => d in deps);
|
|
36
|
+
}
|
package/lib/format.js
CHANGED
|
@@ -1,123 +1,17 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
import { join, relative } from "node:path";
|
|
5
1
|
import { FORMATTER } from "./formatter.js";
|
|
6
2
|
import { run } from "./run.js";
|
|
7
3
|
|
|
8
|
-
const require = createRequire(import.meta.url);
|
|
9
|
-
|
|
10
|
-
// House SQL defaults, used only when the site has no Prettier config of its own
|
|
11
|
-
// (matches the house tabs/4/88 style + the PostgreSQL dialect; without the
|
|
12
|
-
// explicit widths Prettier falls back to 80/2).
|
|
13
|
-
const SQL_DEFAULTS = {
|
|
14
|
-
useTabs: true,
|
|
15
|
-
tabWidth: 4,
|
|
16
|
-
printWidth: 88,
|
|
17
|
-
language: "postgresql",
|
|
18
|
-
};
|
|
19
|
-
|
|
20
4
|
/**
|
|
21
5
|
* `nk format` / `nk format --check`.
|
|
22
6
|
*
|
|
23
|
-
* Code (JS/TS/JSON/CSS) goes through oxfmt
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
7
|
+
* Code (JS/TS/JSON/CSS) goes through oxfmt — the one formatter nk owns. SQL is
|
|
8
|
+
* intentionally NOT formatted: it's almost entirely generated (drizzle
|
|
9
|
+
* migrations, `pg_dump` baselines, pglite fixtures), so a SQL formatter only
|
|
10
|
+
* churns generated files and chokes on psql directives (`\restrict`, …) for no
|
|
11
|
+
* real gain. Hand-written SQL is rare and fine unformatted. This is why nk-dev
|
|
12
|
+
* carries no `prettier` dependency.
|
|
27
13
|
*/
|
|
28
|
-
export
|
|
14
|
+
export function format({ check }) {
|
|
29
15
|
const op = check ? FORMATTER.checkFormat : FORMATTER.write;
|
|
30
|
-
|
|
31
|
-
if (code !== 0) process.exitCode = code;
|
|
32
|
-
|
|
33
|
-
if (await formatSql({ check })) process.exitCode = 1;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Format (or, with `check`, verify) every tracked `.sql` file via Prettier.
|
|
38
|
-
* Returns true when a check found unformatted files — the caller owns the exit
|
|
39
|
-
* code (inferring failure from the `process.exitCode` global misattributes any
|
|
40
|
-
* earlier failure to SQL).
|
|
41
|
-
*/
|
|
42
|
-
export async function formatSql({ check }) {
|
|
43
|
-
const files = sqlFiles();
|
|
44
|
-
if (files.length === 0) return false;
|
|
45
|
-
|
|
46
|
-
const prettier = require("prettier");
|
|
47
|
-
const pluginPath = require.resolve("prettier-plugin-sql");
|
|
48
|
-
|
|
49
|
-
let unformatted = 0;
|
|
50
|
-
let written = 0;
|
|
51
|
-
for (const file of files) {
|
|
52
|
-
// git ls-files lists tracked files deleted from the worktree without
|
|
53
|
-
// `git rm`; reading one would throw an unhandled ENOENT.
|
|
54
|
-
if (!existsSync(file)) continue;
|
|
55
|
-
const source = readFileSync(file, "utf8");
|
|
56
|
-
// The site's own .prettierrc / package.json "prettier" wins over our
|
|
57
|
-
// defaults; we always inject the bundled SQL plugin + parser.
|
|
58
|
-
const siteConfig = (await prettier.resolveConfig(file)) ?? {};
|
|
59
|
-
const options = {
|
|
60
|
-
...SQL_DEFAULTS,
|
|
61
|
-
...siteConfig,
|
|
62
|
-
parser: "sql",
|
|
63
|
-
plugins: [
|
|
64
|
-
pluginPath,
|
|
65
|
-
...(siteConfig.plugins ?? []).filter(
|
|
66
|
-
(p) => !String(p).includes("prettier-plugin-sql"),
|
|
67
|
-
),
|
|
68
|
-
],
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
if (check) {
|
|
72
|
-
if (!(await prettier.check(source, options))) {
|
|
73
|
-
unformatted++;
|
|
74
|
-
console.error(` ${relative(process.cwd(), file)}`);
|
|
75
|
-
}
|
|
76
|
-
} else {
|
|
77
|
-
const out = await prettier.format(source, options);
|
|
78
|
-
if (out !== source) {
|
|
79
|
-
writeFileSync(file, out);
|
|
80
|
-
written++;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (check && unformatted > 0) {
|
|
86
|
-
console.error(
|
|
87
|
-
`nk: ${unformatted} SQL file(s) need formatting — run \`nk format\`.`,
|
|
88
|
-
);
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
if (!check && written > 0) {
|
|
92
|
-
console.log(`nk: formatted ${written} SQL file(s).`);
|
|
93
|
-
}
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Tracked + untracked-not-ignored `.sql` files; falls back to an fs walk. */
|
|
98
|
-
function sqlFiles() {
|
|
99
|
-
// -z: NUL-separated, unquoted — the default output octal-escapes non-ASCII
|
|
100
|
-
// filenames, which then match no real path.
|
|
101
|
-
const res = spawnSync(
|
|
102
|
-
"git",
|
|
103
|
-
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "*.sql"],
|
|
104
|
-
{ encoding: "utf8" },
|
|
105
|
-
);
|
|
106
|
-
if (res.status === 0) {
|
|
107
|
-
return res.stdout.split("\0").filter(Boolean);
|
|
108
|
-
}
|
|
109
|
-
return walkSql(process.cwd());
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const SKIP_DIRS = new Set(["node_modules", ".next", ".git", "dist", "build"]);
|
|
113
|
-
|
|
114
|
-
function walkSql(dir, out = []) {
|
|
115
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
116
|
-
if (entry.isDirectory()) {
|
|
117
|
-
if (!SKIP_DIRS.has(entry.name)) walkSql(join(dir, entry.name), out);
|
|
118
|
-
} else if (entry.name.endsWith(".sql")) {
|
|
119
|
-
out.push(join(dir, entry.name));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return out;
|
|
16
|
+
process.exit(run(op[0], op[1]));
|
|
123
17
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// nextkit oxlint JS plugin: catch Radix-era props left on Base UI
|
|
2
|
+
// (@base-ui/react) shadcn wrappers that silently do nothing post-migration.
|
|
3
|
+
//
|
|
4
|
+
// The trap: a Radix prop whose name is also a valid native DOM attribute is
|
|
5
|
+
// accepted by Base UI's wrapper prop types (they extend the rendered element's
|
|
6
|
+
// DOM props) and then dropped at runtime. `onSelect` on a menu item is the
|
|
7
|
+
// canonical case: it type-checks as the DOM text-selection handler on the
|
|
8
|
+
// rendered <div>, so tsc stays silent, and it never fires on click. The menu
|
|
9
|
+
// action just does nothing. Base UI's Menu.Item activates via `onClick`.
|
|
10
|
+
//
|
|
11
|
+
// tsc CANNOT catch this class (the prop is a legitimate DOM attribute), which
|
|
12
|
+
// is exactly why it needs a lint rule. Note the mirror image: non-DOM Radix
|
|
13
|
+
// renames (asChild -> render, forceMount -> keepMounted, onValueCommit ->
|
|
14
|
+
// onValueCommitted, delayDuration -> delay, Accordion type -> openMultiple) are
|
|
15
|
+
// NOT listed here because they are not valid DOM attributes, so tsc already
|
|
16
|
+
// errors on them. Only add an entry when the old prop is a valid DOM attribute
|
|
17
|
+
// on the element the wrapper renders (i.e. genuinely silent). Otherwise you add
|
|
18
|
+
// noise that duplicates the type-checker.
|
|
19
|
+
//
|
|
20
|
+
// Matching is by the shadcn wrapper's JSX element name, which is stable across
|
|
21
|
+
// nextkit sites. Aliased imports (rare) are not matched; that is an accepted
|
|
22
|
+
// limitation, not a correctness hole (the type-checker still guards renames).
|
|
23
|
+
//
|
|
24
|
+
// The same component names exist in Radix-based shadcn, where `onSelect` is the
|
|
25
|
+
// CORRECT API. So the rule only activates for projects that actually depend on
|
|
26
|
+
// @base-ui/react; on Radix (or UI-less) sites it is inert. This lets the shared
|
|
27
|
+
// nextkit ruleset enable it as an error fleet-wide without false positives.
|
|
28
|
+
|
|
29
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
30
|
+
import { dirname, join } from "node:path";
|
|
31
|
+
|
|
32
|
+
const baseUiCache = new Map();
|
|
33
|
+
|
|
34
|
+
const pkgDeclaresBaseUi = (pkgPath) => {
|
|
35
|
+
try {
|
|
36
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
37
|
+
return Boolean(
|
|
38
|
+
pkg.dependencies?.["@base-ui/react"] ||
|
|
39
|
+
pkg.devDependencies?.["@base-ui/react"] ||
|
|
40
|
+
pkg.peerDependencies?.["@base-ui/react"],
|
|
41
|
+
);
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Walk up to the nearest package.json and report whether it declares
|
|
48
|
+
// @base-ui/react, memoizing every directory visited so a lint run pays the fs
|
|
49
|
+
// cost once per project subtree.
|
|
50
|
+
const projectUsesBaseUi = (fromDir) => {
|
|
51
|
+
const seen = [];
|
|
52
|
+
let dir = fromDir;
|
|
53
|
+
let result = null;
|
|
54
|
+
while (result === null) {
|
|
55
|
+
if (baseUiCache.has(dir)) {
|
|
56
|
+
result = baseUiCache.get(dir);
|
|
57
|
+
} else {
|
|
58
|
+
seen.push(dir);
|
|
59
|
+
const pkgPath = join(dir, "package.json");
|
|
60
|
+
const parent = dirname(dir);
|
|
61
|
+
if (existsSync(pkgPath)) result = pkgDeclaresBaseUi(pkgPath);
|
|
62
|
+
else if (parent === dir) result = false;
|
|
63
|
+
else dir = parent;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const d of seen) baseUiCache.set(d, result);
|
|
67
|
+
return result;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// component name -> { bannedProp: replacementProp }
|
|
71
|
+
const BANNED = {
|
|
72
|
+
// Base UI Menu.Item (and its checkbox/radio variants) activate via onClick.
|
|
73
|
+
DropdownMenuItem: { onSelect: "onClick" },
|
|
74
|
+
DropdownMenuCheckboxItem: { onSelect: "onClick" },
|
|
75
|
+
DropdownMenuRadioItem: { onSelect: "onClick" },
|
|
76
|
+
ContextMenuItem: { onSelect: "onClick" },
|
|
77
|
+
ContextMenuCheckboxItem: { onSelect: "onClick" },
|
|
78
|
+
ContextMenuRadioItem: { onSelect: "onClick" },
|
|
79
|
+
MenubarItem: { onSelect: "onClick" },
|
|
80
|
+
MenubarCheckboxItem: { onSelect: "onClick" },
|
|
81
|
+
MenubarRadioItem: { onSelect: "onClick" },
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const elementNameOf = (attributeNode) => {
|
|
85
|
+
const opening = attributeNode.parent;
|
|
86
|
+
if (!opening || opening.type !== "JSXOpeningElement") return null;
|
|
87
|
+
const name = opening.name;
|
|
88
|
+
return name && name.type === "JSXIdentifier" ? name.name : null;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const noRadixPropsOnBaseUi = {
|
|
92
|
+
meta: {
|
|
93
|
+
type: "problem",
|
|
94
|
+
docs: {
|
|
95
|
+
description:
|
|
96
|
+
"Disallow Radix-era props that Base UI wrappers silently ignore",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
create(context) {
|
|
100
|
+
const filename = context.physicalFilename || context.filename || "";
|
|
101
|
+
if (!filename || !projectUsesBaseUi(dirname(filename))) return {};
|
|
102
|
+
return {
|
|
103
|
+
JSXAttribute(node) {
|
|
104
|
+
if (!node.name || node.name.type !== "JSXIdentifier") return;
|
|
105
|
+
const banned = BANNED[elementNameOf(node)];
|
|
106
|
+
if (!banned) return;
|
|
107
|
+
const replacement = banned[node.name.name];
|
|
108
|
+
if (!replacement) return;
|
|
109
|
+
context.report({
|
|
110
|
+
node: node.name,
|
|
111
|
+
message: `\`${node.name.name}\` does nothing on this Base UI component and is silently dropped at runtime. Use \`${replacement}\` instead (a Radix -> Base UI leftover the type-checker cannot catch).`,
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export default {
|
|
119
|
+
meta: { name: "nextkit" },
|
|
120
|
+
rules: { "no-radix-props-on-base-ui": noRadixPropsOnBaseUi },
|
|
121
|
+
};
|
package/lib/passthrough.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { checkAgentGuideImport } from "./agent-guide.js";
|
|
2
|
-
import {
|
|
2
|
+
import { toolDrift } from "./drift.js";
|
|
3
3
|
import { FORMATTER } from "./formatter.js";
|
|
4
4
|
import { hasKnipConfig, runKnip } from "./knip.js";
|
|
5
5
|
import { run } from "./run.js";
|
|
@@ -11,15 +11,15 @@ export function lint() {
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* `nk check` — the CI gate. Runs every fast checker and reports them all before
|
|
14
|
-
* failing: oxlint, oxfmt (format),
|
|
15
|
-
*
|
|
14
|
+
* failing: oxlint, oxfmt (format), knip (when configured), and the agent-guide
|
|
15
|
+
* import gate. Tooling drift (superseded deps) is reported as a non-fatal
|
|
16
|
+
* warning — `nk doctor --fix` resolves it.
|
|
16
17
|
*/
|
|
17
|
-
export
|
|
18
|
+
export function check() {
|
|
18
19
|
// Run every gate before deciding (no short-circuit), so one failure doesn't
|
|
19
20
|
// hide another. oxc splits lint (oxlint) and format (oxfmt), so we run both.
|
|
20
21
|
const lintFailed = run(FORMATTER.lint[0], FORMATTER.lint[1]) !== 0;
|
|
21
22
|
const fmtFailed = run(FORMATTER.checkFormat[0], FORMATTER.checkFormat[1]) !== 0;
|
|
22
|
-
const sqlFailed = await formatSql({ check: true });
|
|
23
23
|
// knip (unused deps/exports/files). Opt-in: only when the repo has a knip
|
|
24
24
|
// config — knip has no shareable config, so absence means "not adopted".
|
|
25
25
|
const knipFailed = hasKnipConfig() ? runKnip() !== 0 : false;
|
|
@@ -32,8 +32,19 @@ export async function check() {
|
|
|
32
32
|
" → add `@./node_modules/@ingram-tech/nk-dev/guide.md` to your CLAUDE.md (or run `nk init`).",
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
warnToolDrift();
|
|
36
|
+
process.exit(lintFailed || fmtFailed || knipFailed || !guide.ok ? 1 : 0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Non-fatal: surface superseded deps so drift doesn't silently re-accumulate. */
|
|
40
|
+
function warnToolDrift() {
|
|
41
|
+
const drift = toolDrift();
|
|
42
|
+
if (drift.length === 0) return;
|
|
43
|
+
console.error(
|
|
44
|
+
`nk check: ${drift.length} dependency(ies) superseded by @ingram-tech/nk-dev — ${drift.join(", ")}`,
|
|
45
|
+
);
|
|
46
|
+
console.error(
|
|
47
|
+
" → nk-dev already provides these; run `nk doctor --fix` to remove them.",
|
|
37
48
|
);
|
|
38
49
|
}
|
|
39
50
|
|
|
@@ -44,6 +55,11 @@ export function typeCheck() {
|
|
|
44
55
|
process.exit(run("tsc", ["--noEmit"]));
|
|
45
56
|
}
|
|
46
57
|
|
|
58
|
+
/** `nk test [...]` — vitest run, with extra args passed through. */
|
|
59
|
+
export function test(extraArgs = []) {
|
|
60
|
+
process.exit(run("vitest", ["run", ...extraArgs]));
|
|
61
|
+
}
|
|
62
|
+
|
|
47
63
|
/** `nk build [...]` — next build, with extra args passed through. */
|
|
48
64
|
export function build(extraArgs = []) {
|
|
49
65
|
process.exit(run("next", ["build", ...extraArgs]));
|
package/oxlintrc.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"plugins": ["typescript", "unicorn", "oxc", "react", "jsx-a11y", "import"],
|
|
3
|
+
"jsPlugins": ["@ingram-tech/nk-dev/oxlint-plugin"],
|
|
3
4
|
"categories": {
|
|
4
5
|
"correctness": "error"
|
|
5
6
|
},
|
|
6
7
|
"rules": {
|
|
8
|
+
"nextkit/no-radix-props-on-base-ui": "error",
|
|
7
9
|
"no-unused-vars": "warn",
|
|
8
10
|
"typescript/no-non-null-assertion": "error",
|
|
9
11
|
"typescript/no-explicit-any": "error",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ingram-tech/nk-dev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The nextkit dev toolchain in one package: the `nk` CLI plus shared oxlint/oxfmt, TypeScript, and Vitest config, the format-on-commit hook, and the AI agent guide. `nk init` scaffolds a site to use it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"./oxlintrc.json": "./oxlintrc.json",
|
|
31
31
|
"./oxfmtrc.json": "./oxfmtrc.json",
|
|
32
32
|
"./tier-b.json": "./tier-b.json",
|
|
33
|
+
"./oxlint-plugin": "./lib/oxlint-plugins/base-ui.js",
|
|
33
34
|
"./tsconfig": "./tsconfig/nextjs.json",
|
|
34
35
|
"./tsconfig/base.json": "./tsconfig/base.json",
|
|
35
36
|
"./tsconfig/nextjs.json": "./tsconfig/nextjs.json",
|
|
@@ -45,13 +46,11 @@
|
|
|
45
46
|
"dependencies": {
|
|
46
47
|
"@testing-library/jest-dom": "^6.9.1",
|
|
47
48
|
"jsdom": "^29.1.1",
|
|
48
|
-
"knip": "^6.
|
|
49
|
-
"oxfmt": "^0.
|
|
50
|
-
"oxlint": "^1.
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"typescript": "^6.0.3",
|
|
54
|
-
"vitest": "^4.1.9"
|
|
49
|
+
"knip": "^6.25.0",
|
|
50
|
+
"oxfmt": "^0.58.0",
|
|
51
|
+
"oxlint": "^1.73.0",
|
|
52
|
+
"typescript": "^7.0.2",
|
|
53
|
+
"vitest": "^4.1.10"
|
|
55
54
|
},
|
|
56
55
|
"engines": {
|
|
57
56
|
"node": ">=20"
|