From 0d4c5fa3898df0f5682a625760ebfd00d640c2a7 Mon Sep 17 00:00:00 2001 From: Gabriel Taveira Date: Mon, 27 Jul 2026 20:36:04 -0300 Subject: [PATCH] chore(tooling): track magic by tag and ban hand-built className MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Point the shared checkup job at `GSTJ/magic/.github/workflows/ci.yml@v1` instead of `@main`. `@main` reruns this repo's CI on whatever was merged there five minutes ago; `@v1` is a moving major tag, so fixes still arrive on the next run but an unreviewed merge cannot. The call passes no inputs, so there is no signature to reconcile — every `ci.yml` input is optional. Turn on `magic/no-manual-classname` at error, in apps/nextjs only. The rule is a Tailwind rule and Tailwind stops at that app: mobile styles with styled-components and `packages/*` ships no JSX, so loading the jsPlugin there would cost startup time to lint nothing. Zero violations today — every class string in the app already goes through `cn` from `src/lib/utils.ts` — which makes this a ratchet rather than a cleanup. Adding the plugin needed `magic-oxlint-plugin@1.1.0` in the `minimumReleaseAgeExclude` list. pnpm 11 refuses a package published less than 24h ago and 1.1.0 went out today; pnpm writes the entry itself on the first local install, and it reads like machine noise but is the only thing that lets `--frozen-lockfile` resolve on CI. It comes back out once the version ages past the window. The other `magic-*` deps are already on the current latest (1.2.0 for the three configs, 1.1.0 for the codemods), so nothing to bump there. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1 --- .github/workflows/branch-check.yml | 2 +- apps/nextjs/oxlint.config.mts | 30 ++++++++++++++++++++++++------ package.json | 1 + pnpm-lock.yaml | 16 ++++++++++++++++ pnpm-workspace.yaml | 1 + 5 files changed, 43 insertions(+), 7 deletions(-) diff --git a/.github/workflows/branch-check.yml b/.github/workflows/branch-check.yml index bd4ec90..34e82c3 100644 --- a/.github/workflows/branch-check.yml +++ b/.github/workflows/branch-check.yml @@ -50,7 +50,7 @@ jobs: checkup: name: 👮 Checkup needs: dedupe - uses: GSTJ/magic/.github/workflows/ci.yml@main + uses: GSTJ/magic/.github/workflows/ci.yml@v1 # Kept out of the shared checkup job: only this one needs a database, and # folding it in would spin up Postgres for typecheck/lint/format too. No diff --git a/apps/nextjs/oxlint.config.mts b/apps/nextjs/oxlint.config.mts index 25a4bbf..ae116d8 100644 --- a/apps/nextjs/oxlint.config.mts +++ b/apps/nextjs/oxlint.config.mts @@ -1,9 +1,27 @@ +import { extendConfig } from "magic-oxlint-config"; +import next from "magic-oxlint-config/next"; + /** * A nested config replaces the root one for everything under `apps/nextjs`, so - * this file has to be complete on its own. Re-exporting the preset — rather - * than `defineConfig({ extends: [next] })` — is what makes it complete: - * `extends` still drops the extended config's `ignorePatterns` on oxlint - * 1.75.0, so the extends form needs them copied back by hand. The re-export - * loads the preset as *the* config and every field applies. + * this file has to be complete on its own. `extendConfig` flattens the preset + * and the additions below into one config, which is what makes it complete: + * oxlint's own `extends` still drops the extended config's `ignorePatterns` on + * 1.75.0 and needs them re-listed by hand. + * + * This is the only config in the repo that loads the `magic` jsPlugin. + * `magic/no-manual-classname` is a Tailwind rule and Tailwind stops at this + * app — the mobile app styles with styled-components and `packages/*` has no + * JSX at all, so loading the plugin there would cost startup time to lint + * nothing. */ -export { default } from "magic-oxlint-config/next"; +export default extendConfig(next, { + jsPlugins: [{ name: "magic", specifier: "magic-oxlint-plugin" }], + + rules: { + // Class strings go through `cn` (src/lib/utils.ts, `twMerge(clsx(...))`) + // or a `cva` variant table. A hand-built one loses the conflict + // resolution: `p-2 ${active ? "p-4" : ""}` emits both paddings and leaves + // the winner to stylesheet order, where `cn` keeps the last one. + "magic/no-manual-classname": "error", + }, +}); diff --git a/package.json b/package.json index f7d16b6..a69221a 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "magic-codemods": "^1.1.0", "magic-oxfmt-config": "^1.2.0", "magic-oxlint-config": "^1.2.0", + "magic-oxlint-plugin": "^1.1.0", "magic-tsconfig": "^1.2.0", "oxfmt": "0.60.0", "oxlint": "1.75.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09b3883..5e97a69 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,9 @@ importers: magic-oxlint-config: specifier: ^1.2.0 version: 1.2.0(eslint@9.39.5(jiti@1.21.0)(supports-color@8.1.1))(oxlint@1.75.0) + magic-oxlint-plugin: + specifier: ^1.1.0 + version: 1.1.0(oxlint@1.75.0) magic-tsconfig: specifier: ^1.2.0 version: 1.2.0 @@ -5739,6 +5742,15 @@ packages: oxlint: optional: true + magic-oxlint-plugin@1.1.0: + resolution: {integrity: sha512-SdxWuNO9B0g5vMivlk9gP0B+PIKmBoImn8QWn9HQy0tIiyELzSmdrBkGtB2OB7IY23KK6b3gx1WDy8gxy763EA==} + engines: {node: '>=22'} + peerDependencies: + oxlint: '>=1.75.0' + peerDependenciesMeta: + oxlint: + optional: true + magic-tsconfig@1.2.0: resolution: {integrity: sha512-odQYUHWzEvgB85RsdRvKwYQPm4/zMMgXABhPPHyNpkEd4T4OqPp8SI4LfYQAn5LXLIktLHzqSJktH5g0Y5EeIQ==} @@ -13732,6 +13744,10 @@ snapshots: transitivePeerDependencies: - eslint + magic-oxlint-plugin@1.1.0(oxlint@1.75.0): + optionalDependencies: + oxlint: 1.75.0 + magic-tsconfig@1.2.0: {} make-dir@4.0.0: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 51b27be..356c632 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -64,6 +64,7 @@ minimumReleaseAgeExclude: - magic-oxfmt-config@1.2.0 - magic-oxlint-config@1.2.0 - magic-tsconfig@1.2.0 + - magic-oxlint-plugin@1.1.0 # pnpm 10+ does not run dependency install scripts unless they are listed here, # and pnpm 11 makes an unanswered list a hard install failure. Every entry ran