Prune migration notes and align the notes with the current lint setup
Check / check (pull_request) Successful in 39s

This commit is contained in:
Danny Kim
2026-10-16 23:41:04 +09:00
parent 662e946fad
commit 15fe481181
12 changed files with 47 additions and 174 deletions
@@ -12,7 +12,9 @@ Node + Vite+ migration (PR #20, `node-migration`, 2026-10-15/16):
consumer (`uses: .../commons/bot-agents@<branch>`) on a throwaway PR. consumer (`uses: .../commons/bot-agents@<branch>`) on a throwaway PR.
- **Profiles are JSON.** `profiles.yml` became `profiles.json` with the same - **Profiles are JSON.** `profiles.yml` became `profiles.json` with the same
content (encrypted `v1:` values unchanged), read with `JSON.parse`. That content (encrypted `v1:` values unchanged), read with `JSON.parse`. That
removed the only runtime dependency (`@std/yaml`); keep it dependency-free. removed the only runtime dependency (`@std/yaml`); keep it free of runtime
dependencies (`@eslint/core` and the other devDependencies serve only the
tooling).
- **Node 24.2+**, not 22: `src/post.ts` uses `import.meta.main`. `engines.node` - **Node 24.2+**, not 22: `src/post.ts` uses `import.meta.main`. `engines.node`
is `>=24.2`, which the user accepted for this repo is `>=24.2`, which the user accepted for this repo
([[bot-agents-runtime-no-build]]). ([[bot-agents-runtime-no-build]]).
+8 -7
View File
@@ -1,17 +1,18 @@
--- ---
description: bot-agents lint has prefer-readonly-parameter-types off (Node/web types can't satisfy it) and one scripts/** override for no-unsafe-enum-comparison; nothing else description: bot-agents lint has prefer-readonly-parameter-types off (Node/web types can't satisfy it) and no overrides; nothing else
--- ---
The repo follows [[lint-config-shape]] with two recorded exceptions, both The repo follows [[lint-config-shape]] with one recorded exception, present
present when the migration PR was approved: when the migration PR was approved:
- `typescript/prefer-readonly-parameter-types` is `"off"` in - `typescript/prefer-readonly-parameter-types` is `"off"` in
`scripts/project-lint.ts`, as in the explicit-types template, because `scripts/project-lint.ts`, as in the explicit-types template, because
Node/web types (`Response`, `Stats`, `RequestInit`) can never satisfy it. Node/web types (`Response`, `Stats`, `RequestInit`) can never satisfy it.
- `vite.config.ts` has one `overrides` entry, for `scripts/**` only: - `vite.config.ts` has no `overrides`. The `scripts/**` override for
`typescript/no-unsafe-enum-comparison` off, because the lint plugin compares `typescript/no-unsafe-enum-comparison` was removed (2026-10-16):
AST node `type`s, which the AST types declare as an enum. This is the `scripts/project-lint.ts` compares node types with `AST_NODE_TYPES` members
tooling exception [[lint-overrides-policy]] allows. from `@typescript-eslint/types` instead of string literals
([[lint-overrides-policy]]).
Oxlint's default size limits apply to `src/`; the migration split `run.ts` Oxlint's default size limits apply to `src/`; the migration split `run.ts`
into `run-context.ts`, `agents.ts`, and `agent-{claude,codex,opencode}.ts`, into `run-context.ts`, `agents.ts`, and `agent-{claude,codex,opencode}.ts`,
-45
View File
@@ -1,45 +0,0 @@
---
description: Every repo moved from Deno to Node + Vite+ in Oct 2026 because Cloudflare bought Deno; nothing goes back to Deno
---
On 2026-10-12 the user moved every project from Deno to Node.js + Vite+
(`vp`). Cloudflare bought Deno on 2026-10-09; the runtime gets roughly 12
months of maintenance and Deno Deploy shuts down around April 2027. None of the
repos used Deploy or KV, so the move is about the toolchain, not hosting.
Do not reintroduce `deno.json`, `deno.lock`, `Deno.*` APIs, `jsr:` or `npm:`
specifiers, `mod.ts` entries, or `deno task` anywhere, docs and scripts
included.
## Order the migration followed
1. The custom Deno lint plugins were ported to npm first, as
`@cunarist/eslint-plugin-explicit-types`, `-import-check`, and `-lit`.
`cunarist/eslint-plugin-explicit-types` is the template every other repo
copies (config shape, `scripts/project-lint.ts`, AGENTS.md, notes).
2. Then `commons/bot-agents` (six repos use it as an action), then the apps:
home, eximium, stockvibe, brain, memona, memona-home.
3. deno-practice, interview-practice, web-landing, and vemium were left out
(archivable). Old `cunarist/deno-*` repos are archived one at a time, and
only after their `eslint-plugin-*` replacement is published to npm.
## What "migrated" means
- Same behavior. A migration PR changes tooling, not features.
- Lint coverage is at least what Deno enforced: [[lint-parity-never-shrinks]].
- Only three config files and only `.ts`: [[toolchain-node-vite-plus]],
[[ts-only-files-and-imports]].
- Deno.test suites became Vitest: [[tests-layout-vitest]].
The custom plugins' Deno names map like this:
| Deno | npm |
| ----------------------------------------------- | ---------------------------------------- |
| `jsr:@cunarist/deno-lint-plugin-explicit-types` | `@cunarist/eslint-plugin-explicit-types` |
| `jsr:@cunarist/deno-import-check` (`./lint`) | `@cunarist/eslint-plugin-import-check` |
| `jsr:@cunarist/deno-lint-plugin-lit` | `@cunarist/eslint-plugin-lit` |
| `scripts/deno-lint-plugin.ts` (per app) | `scripts/project-lint.ts` (per app) |
Until those packages are on npm, a repo that depends on them fails `npm ci` in
CI; that is expected and stated in the PR, never worked around by committing
tarballs or `file:` dependencies.
+4 -3
View File
@@ -1,5 +1,5 @@
--- ---
description: Shared code comes through # aliases in package.json imports, each directory's entry is index.ts, barrels keep imports few; never ../ out of a directory description: Shared code comes through # aliases in package.json imports, each src/ subfolder's entry is index.ts, barrels keep imports few; never ../ out of a directory
--- ---
## `#` aliases ## `#` aliases
@@ -44,8 +44,9 @@ so they never reach package consumers.
## `index.ts` entries and barrels ## `index.ts` entries and barrels
- Every directory's entry file is `index.ts`, never `mod.ts` (decided - Every `src/` subfolder's entry file is `index.ts`, never `mod.ts` (decided
2026-10-12; import-check's `enforce-mod-file` now requires `index.ts`). 2026-10-12; import-check's `enforce-mod-file` requires it only in folders a
`#` alias owns). Folders outside `src/`, such as `scripts/`, have none.
- A directory used from outside exposes its API through its `index.ts` - A directory used from outside exposes its API through its `index.ts`
barrel, and callers import the barrel (`#ui`), not files inside it barrel, and callers import the barrel (`#ui`), not files inside it
(`no-barrel-bypass`). (`no-barrel-bypass`).
+4 -5
View File
@@ -22,11 +22,10 @@ lint: {
- **Default limits stay** (2026-10-16): pedantic's `max-lines` 300, - **Default limits stay** (2026-10-16): pedantic's `max-lines` 300,
`max-lines-per-function` 50, `max-classes-per-file` 1, `max-lines-per-function` 50, `max-classes-per-file` 1,
`max-dependencies` 10, `max-params` 3, `complexity` 20. Don't raise them to `max-dependencies` 10, `max-params` 3, `complexity` 20. Don't raise them to
make code pass; refactor (see [[lint-overrides-policy]]). Apps follow this make code pass; refactor (see [[lint-overrides-policy]]). `max-lines` stays
strictly. The plugin template already turns off `max-lines`, at 300 everywhere, `scripts/` included, with no override (2026-10-16): split
`max-lines-per-function`, `typescript/prefer-readonly-parameter-types`, and a long file by theme instead. Do not add new opt-outs anywhere without
`typescript/no-unsafe-enum-comparison` because AST visitors fight them; do asking the user.
not add new opt-outs anywhere without asking the user.
- **`vite.config.ts` names no rule.** Every rule id, built-in ones included, - **`vite.config.ts` names no rule.** Every rule id, built-in ones included,
lives in `configs.project` of `scripts/project-lint.ts` lives in `configs.project` of `scripts/project-lint.ts`
([[project-lint-plugin]]). `vite.config.ts` only spreads configs. ([[project-lint-plugin]]). `vite.config.ts` only spreads configs.
+4 -3
View File
@@ -29,9 +29,10 @@ registerHooks({
``` ```
- Node runs the `.ts` script directly (type stripping), so no build step. - Node runs the `.ts` script directly (type stripping), so no build step.
- **Gotcha:** the obvious `await import("lit-analyzer/cli.js")` fails - Import the package entry, or spawn the package's bin with
`project/no-non-ts-imports` (a `.js` subpath). Import the package entry, or `node:child_process`, rather than `await import("lit-analyzer/cli.js")`.
spawn the package's bin with `node:child_process`, instead. `project/no-asset-imports` allows `.js` subpaths, but the CLI file runs on
import and bypasses the package's public entry.
- CI runs `npm run lit-check` as its own step. - CI runs `npm run lit-check` as its own step.
- It is tooling under `scripts/`: no tests; a narrow `overrides` entry for it - It is tooling under `scripts/`: no tests; a narrow `overrides` entry for it
(e.g. `no-console`) is allowed if truly needed. (e.g. `no-console`) is allowed if truly needed.
+6 -25
View File
@@ -1,32 +1,13 @@
--- ---
description: package.json key order is metadata, scripts, exports, imports, devDependencies, dependencies; standard fields only; no peerDependencies or packageManager description: package.json keys follow Oxfmt's standard order (vp check --fix); standard fields only; no peerDependencies or packageManager
--- ---
## Key order (2026-10-14) ## Key order (2026-10-16)
Metadata first, then `scripts`, `exports`, `imports`, `devDependencies`, Oxfmt's standard `package.json` sorting is accepted: there is no
`dependencies`. Oxfmt would re-sort the file, so `vite.config.ts` sets `fmt: { sortPackageJson: false }`, and `vp check --fix` sets the order. This
`fmt: { sortPackageJson: false }`; keep it, and keep the order by hand. replaces the earlier hand-written order (metadata, `scripts`, `exports`,
`imports`, `devDependencies`, `dependencies`).
```jsonc
{
"name": "@cunarist/eslint-plugin-x",
"version": "0.1.0",
"description": "...",
"keywords": ["eslint", "eslint-plugin", "eslintplugin", "oxlint"],
"license": "MIT",
"repository": { "type": "git", "url": "git+https://github.com/..." },
"type": "module",
"files": ["dist"],
"engines": { "node": ">=22" },
"eslint-doc-generator": {/* tool options that can only live here */},
"scripts": { "docs": "eslint-doc-generator" },
"exports": { ".": "./dist/index.js" },
"imports": { "#helpers": "./src/helpers/index.ts" },
"devDependencies": { "vite-plus": "^1.1.0" },
"dependencies": {},
}
```
The `imports` key order can carry meaning: import-check's The `imports` key order can carry meaning: import-check's
`enforce-layer-order` reads it as the layer order. Never reorder it casually. `enforce-layer-order` reads it as the layer order. Never reorder it casually.
+6 -2
View File
@@ -11,11 +11,15 @@ Oxlint plugin with `meta.name` `"project"`, loaded through `jsPlugins` in
plugin configs: Deno parity rules, extras, per-rule options, and opt-outs, plugin configs: Deno parity rules, extras, per-rule options, and opt-outs,
built-in Oxlint ids included (`curly`, `id-match`, `import/no-cycle`, ...). built-in Oxlint ids included (`curly`, `id-match`, `import/no-cycle`, ...).
- **Custom rules**, enabled as `project/<rule>`. Every repo has - **Custom rules**, enabled as `project/<rule>`. Every repo has
`no-disable-comments` and `no-non-ts-imports`. Apps also port the rules of `no-disable-comments` and `no-asset-imports`. Apps also port the rules of
their old `scripts/deno-lint-plugin.ts` here under the same names and their old `scripts/deno-lint-plugin.ts` here under the same names and
behavior (`no-self-sync`, `app-module-conventions`, `restricted-types`, ...). behavior (`no-self-sync`, `app-module-conventions`, `restricted-types`, ...).
Rules and plugin stay in this one file; don't add `scripts/rules/`. `scripts/` stays flat; don't add `scripts/rules/`. A file that would pass
`max-lines` (300) splits into flat themed files sharing the prefix:
`scripts/project-lint.ts` keeps the plugin object and the rule list and
imports the others (`project-lint-modules.ts`, `project-lint-types.ts`,
`project-lint-lit.ts`, ...).
```ts ```ts
import type { DummyRuleMap } from "vite-plus/lint"; import type { DummyRuleMap } from "vite-plus/lint";
+1 -1
View File
@@ -23,7 +23,7 @@ Standing convention (2026-10-12), applied to every package:
(`--generate-notes`, edited there). There is no `CHANGELOG.md`; never add (`--generate-notes`, edited there). There is no `CHANGELOG.md`; never add
one. one.
- **The version lives in two places in plugin packages:** `package.json` and - **The version lives in two places in plugin packages:** `package.json` and
`meta.version` in `src/index.ts`; `tests/package-meta.test.ts` fails if they `meta.version` in `src/plugin/index.ts`; `tests/package-meta.test.ts` fails if they
differ. differ.
- **Before 1.0**, a change that breaks users (renamed rule id, a rule that - **Before 1.0**, a change that breaks users (renamed rule id, a rule that
fires on more code, removed behavior) is a minor bump. fires on more code, removed behavior) is a minor bump.
+2 -2
View File
@@ -44,8 +44,8 @@ before any push. CI installs the toolchain with `voidzero-dev/setup-vp`.
- `vp check` does **not** report `isolatedDeclarations` errors (TS9010); only - `vp check` does **not** report `isolatedDeclarations` errors (TS9010); only
`vp pack` does, so packages run `vp pack` in CI on every PR. `vp pack` does, so packages run `vp pack` in CI on every PR.
- Oxfmt sorts `package.json` keys by default, which destroys the required key - Oxfmt sorts `package.json` keys by default; that order is accepted, so
order. Set `fmt: { sortPackageJson: false }` ([[package-json-shape]]). there is no `fmt` option for it ([[package-json-shape]]).
- Oxlint and Oxfmt skip what `.gitignore` lists; that is the only reason - Oxlint and Oxfmt skip what `.gitignore` lists; that is the only reason
`dist/` and `node_modules/` are not linted. Keep build output gitignored. `dist/` and `node_modules/` are not linted. Keep build output gitignored.
- Vite+ bundles `vite.config.ts` itself and resolves `#` imports in it. - Vite+ bundles `vite.config.ts` itself and resolves `#` imports in it.
+9 -6
View File
@@ -1,5 +1,5 @@
--- ---
description: Every file is .ts (no js/mjs/cjs/jsx/tsx, configs included) and source imports only .ts; CSS comes from HTML, JSON/SVG become .ts modules description: Every file is .ts (no js/mjs/cjs/jsx/tsx, configs included); imports name only .ts files or .js package subpaths; CSS comes from HTML, JSON/SVG become .ts modules
--- ---
The user's rule, from the Deno days and restated for Node (2026-10-12): "No The user's rule, from the Deno days and restated for Node (2026-10-12): "No
@@ -12,12 +12,14 @@ plain `.ts`. Build output in a gitignored folder is the only JS.
`noUncheckedIndexedAccess`). If a tool or framework seems to need a JS or TSX `noUncheckedIndexedAccess`). If a tool or framework seems to need a JS or TSX
file, stop and flag it instead of adding one. file, stop and flag it instead of adding one.
## Only `.ts` imports (2026-10-14) ## No asset imports (2026-10-14, widened 2026-10-16)
`project/no-non-ts-imports` (in `scripts/project-lint.ts`) fails: `.ts` and `.js` imports are allowed: our own files stay `.ts`, and `.js`
covers package subpaths such as `lit/decorators.js`.
`project/no-asset-imports` (in `scripts/project-lint.ts`) fails:
- a relative, absolute, `#`, or package-subpath specifier whose last segment - a relative, absolute, `#`, or package-subpath specifier whose last segment
has a non-`.ts` extension: `.css`, `.json`, `.svg`, `.js`, `.mjs`, `.tsx`; has any other extension: `.css`, `.json`, `.svg`, `.mjs`, `.tsx`;
- any `?query` suffix (`?raw`, `?url`, `?inline`); - any `?query` suffix (`?raw`, `?url`, `?inline`);
- any import attributes (`with { type: "json" }`, `with { type: "text" }`). - any import attributes (`with { type: "json" }`, `with { type: "text" }`).
@@ -44,5 +46,6 @@ import { scenes } from "#data/scenes"; // yes: scenes.ts
and fails on drift (the plugin repos do this for `meta.name`/`meta.version`). and fails on drift (the plugin repos do this for `meta.name`/`meta.version`).
- **Reading a file is not importing it.** `readFileSync` + `JSON.parse` is - **Reading a file is not importing it.** `readFileSync` + `JSON.parse` is
fine in tooling and tests. fine in tooling and tests.
- **A tool's `.js` entry:** don't `import("pkg/cli.js")`; use the package - **A tool's CLI file:** prefer the package entry or spawning the package's
entry or spawn the package's bin ([[lit-analyzer-ts6]]). bin over importing `pkg/cli.js`, which runs the CLI as a side effect
([[lit-analyzer-ts6]]).
-74
View File
@@ -1,74 +0,0 @@
---
name: migrate-deno-repo
description: Checklist for moving a Deno repo (or a leftover Deno part of one) to Node 22+ + Vite+ with npm, same behavior, full lint parity, one PR
---
Template: `cunarist/eslint-plugin-explicit-types` on `main`. Read its
`AGENTS.md`, `vite.config.ts`, `scripts/project-lint.ts`, `package.json`, and
CI before starting, and copy their shape. Never ask the user mid-way when
they're away: decide by the memories, record the choice in the PR.
Branch `node-migration` (apps) or `initial-port` (new plugin repos); one PR
with everything.
**Inventory first**
- [ ] List every `deno.json` task, import-map entry, lint rule (tags,
includes, `@hugoalh`, custom plugins), test, `Deno.*` call, JSON/CSS/SVG
import, and every place the output is consumed (Rust, HTML,
Containerfile, CI).
**Toolchain**
- [ ] `package.json` in the required key order, `engines.node ">=22"`, no
`packageManager`, no `peerDependencies`; `npm install`; commit
`package-lock.json`.
- [ ] `tsconfig.json` from the template; `vite.config.ts` with `fmt:
{ sortPackageJson: false }` and the category-based `lint` block.
- [ ] Delete `deno.json`, `deno.lock`, and Deno-only files. `mod.ts` →
`index.ts`; `deno.json` imports → `package.json` `imports` (`#x/*`
patterns for trailing-slash mappings; aliases may target only direct
subfolders of `src/`, anything else becomes a relative import);
`jsr:`/`npm:` → npm packages.
**Code**
- [ ] Only `.ts` files; no non-`.ts` imports: CSS via `<link>`, JSON and SVG
as `.ts` modules, icons in memona's style.
- [ ] `Deno.*` → `node:*` / `process`. Generated JS written as `.ts`, built,
gitignored.
- [ ] Decorators stay standard with `accessor`; add `scripts/decorators.ts`
if the repo has any. Lit repos: `scripts/lit-check.ts` + `npm run
lit-check`.
- [ ] `scripts/` is flat: no subfolders, no `index.ts` (barrels live only in
`src/`); `vite.config.ts` imports `./scripts/<name>.ts` directly. A
scripts subfolder that is really an app moves under `src/`.
**Lint**
- [ ] `scripts/project-lint.ts` copied; old local Deno lint rules ported into
it; parity mapping for every former rule; `id-match`; no disable
comments; no source `overrides`; refactor size-rule hits.
**Tests**
- [ ] `Deno.test` → Vitest (codemod, then finish TODOs); unit tests next to
code, integration in `tests/`; no case dropped.
**Build, ship, CI**
- [ ] `vp build` in app mode with the same output paths, or `vp pack` for
packages. Containerfile node stage on `node:24-alpine`. CI with
`setup-vp`: `vp check`, build, `vp test` (+ lit-check). Packages:
release-triggered `publish.yml`.
- [ ] `AGENTS.md` with the damem header, `.agents/` notes from the damem kit,
README updated (Node.js 22+, no Deno instructions). `damem doctor`
clean.
**Finish**
- [ ] `vp check`, `vp test`, build all pass locally; the app runs.
- [ ] Commit in plain imperative sentences; open the PR (summary, changes,
parity mapping, deviations/risks, "CI fails until @cunarist/eslint-plugin-*
are published" if relevant, verification, footer line).
- [ ] Run `review-cycle`. Never merge, publish, or archive.