Skip to content

chore: arethetypeswrong で exports と型定義の対応を CI で検査する - #380

Merged
yokotaso merged 2 commits into
mainfrom
attw-addToCi
Oct 15, 2026
Merged

yokotaso merged 2 commits into
mainfrom
attw-addToCi

Conversation

@shinagawa-web

@shinagawa-web shinagawa-web commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

概要

配布物の exports と型定義の対応が正しいかを、arethetypeswrong(attw)で CI で検査する。この後に tsup から tsc へ移行する予定で、そのときに exports や top-level の types を壊したら気づけるよう、移行の前に入れておく。

変更

  • pkgs/typed-api-spec の devDependencies に @arethetypeswrong/cli を追加
  • scripts に test:attw を追加
attw --pack --profile node16 --ignore-rules false-cjs && attw --pack --entrypoints . --ignore-rules false-cjs

CI の test job は build の後に npm test(run-p -c test:*)を実行するので、workflow は変えていない。test:attw は test:pack-smoke と同じく build 済みの dist を pack して検査する。

2回に分けて実行している理由

1回目は全サブパスを --profile node16 で検査する。この profile は node10(moduleResolution: node)の結果を表に出すが、合否判定には使わない。サブパスは exports でしか公開していないので、node10 では解決できなくて当然だから。

ただしこれだと、root の node10 も合否判定から外れる。root は node10 でも top-level の main / types で解決でき、TypeScript 5 で module: "commonjs" にして moduleResolution を書かないと node10 になるので、この解決を使う利用者はいる。tsc への移行で top-level の types を書き誤っても1回目では検出できないので、2回目で root だけを strict profile(node10 も合否判定に入れる)で検査する。

2回とも同じディレクトリに同じ名前の tarball を作るので、別の test:* にして並列に走らせず、&& で順番に実行している。

なお top-level の main が指すファイルが無くても、attw は型しか見ないので検出しない。pack-smoke も exports 経由で読み込むので検出しない。これは pack-smoke 側で別に対応する。

--ignore-rules false-cjs で除外している指摘

今の exports は、各サブパスの types が CJS と ESM で共通(./dist/*.d.ts)。そのため ESM から import しても CJS 向けの .d.ts が使われ、attw はすべてのサブパスで 🎭 Masquerading as CJS を指摘する。

tsup は ESM 向けの .d.mts も出力しているので、types を require / import ごとに分ければ今でも直せる。ただし tsc に移行すると出力のパスが変わり、exports を書き直す事になる。二度手間になるので、tsc への移行で直し、それまでは除外する。

--ignore-rules で除外しているのはこのルールだけで、main ブランチで実行した時点では、合否判定の対象(node16 の CJS / ESM と bundler、root の node10)に他の指摘は出ていない(#369 で Incorrect default export は解消済み)。ただしこの指定は、今後新しく出る false-cjs も隠すので、tsc への移行の完了時に必ず外す。

補足

  • lockfile の差分は attw とその依存の追加だけで、既存パッケージのバージョンは変わっていない
  • attw は package のディレクトリに tarball を作り、検査後に消す(型の問題で exit 1 になったときも消す)。pack-smoke は一時ディレクトリに pack するので、run-p で並列に走っても衝突しない。attw が pack の後に強制終了されたり、解析中の例外(exit 3)で落ちたりすると tarball が残る。CI は使い捨ての環境なので実害は無い

AI が済ませた確認

  • build 後に npm run test:attw -w pkgs/typed-api-spec が exit 0
  • --ignore-rules false-cjs を外して1回目を実行すると exit 1 になり、全サブパスで Masquerading as CJS だけが出る
  • top-level の types を存在しないファイルに書き換えると、1回目は通り、2回目が node10: ❌ No types で exit 1 になる
  • npm test -w pkgs/typed-api-spec(lint・format・unit・type-check・pack-smoke・attw)が通り、作業ツリーに tarball が残らない

動作確認方法

npm ci
npm run build -w pkgs/typed-api-spec
npm run test:attw -w pkgs/typed-api-spec

1回目の表では、node16 (from ESM) を含め全サブパスが 🟢 になる(node10 は (ignored))。2回目の表では、root の node10 も含めて 🟢 になり、exit 0 で終わる。除外を外した結果を見る時は、pkgs/typed-api-spec で npx attw --pack --profile node16 を実行する。

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shinagawa-web

shinagawa-web commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

attw-addToCi 対話コンテキスト

目的

配布物の exports と型定義の対応を、arethetypeswrong(attw)で CI で検査する。この後に tsup から tsc への移行を予定しており、移行で exports や top-level の types を壊したときに CI で気づけるよう、移行より前に入れておく。

#319 は配布物の検査と複数の修正を1つにまとめていたので、分けて出し直している。その一部で、msw 依存の除去と test:pack-smoke の追加(#368)、FetchT / JSONT の named type export 化(#369)はマージ済み。

設計方針

  • CI の workflow は変えず、pkgs/typed-api-spec の scripts に test:attw を足すだけにした。CI の test job は build の後に npm test(run-p -c test:*)を実行するので、test:* に足せば CI で走る。test:pack-smoke と同じ扱い
  • test:attw は attw を2回実行する
    • 1回目 attw --pack --profile node16 --ignore-rules false-cjs: 全サブパスを検査する。node16 profile は node10 の結果を表に出すが、合否判定から外す。サブパスは exports でしか公開していないので、node10 では解決できない
    • 2回目 attw --pack --entrypoints . --ignore-rules false-cjs: root だけを strict profile で検査し、root の node10(top-level の main / types による解決)も合否判定に入れる。1回目だけだと root の node10 も判定から外れ、tsc への移行で top-level の types を書き誤っても検出できない。TypeScript 5 で module: "commonjs" にして moduleResolution を書かないと node10 になるので、この解決を使う利用者はいる
    • 2回とも同じディレクトリに同じ名前の tarball を作るので、別の test:* にして run-p で並列にせず、&& で順番に実行する
  • --ignore-rules false-cjs で 🎭 Masquerading as CJS だけを除外した。今の exports は各サブパスの types が CJS と ESM で共通なので、ESM から import すると CJS 向けの .d.ts が使われ、全サブパスでこれが出る。tsup の出力には .d.mts もあるので、types を require / import ごとに分ければ今でも直せる(close した chore: update package.json scripts and add pack-smoke script #319 はそうしていた)。ただし tsc に移行すると出力のパスが変わって exports を書き直すので、二度手間を避けて移行の中で直す。それまでは除外しないと CI が落ちる。2回目も root の node16 ESM で同じ指摘が出るので、両方に付けている
  • バージョン指定は ^0.18.5(ユーザーが選択)。他の devDependencies と同じキャレット指定

却下した代替案

  • 0.18.5 に固定する案: キャレット指定でも lockfile があり CI は npm ci なので、入る版は同じ 0.18.5 で、固定しても CI の安定性は変わらない。Dependabot も versioning-strategy: increase なので、どちらでも更新の PR は来る。他の devDependencies に合わせてキャレットにした
  • root の node10 を検査しない案: 一度はこの方針になったが、TypeScript 5 の module: "commonjs" の既定が node10 である事から、利用者がいると判断して検査を足した
  • 2回目を別の test:* script にする案: run-p で1回目と並列に走り、同じ名前の tarball が衝突するので採らなかった
  • .attw.json を使う案: 「全サブパスを node16 で」と「root を strict で」の2つの検査を1つの設定では書けない。設定ファイルを2つ作っても2回の実行は要るので、scripts に直書きした
  • 一時ディレクトリに npm pack --pack-destination した tarball に attw を当てる Node のラッパーを書く案: 例外時にも tarball を確実に消せる。さらに、false-cjs をルールごと除外せず今の7件だけを許容して増減を検出する事もできる。ただし false-cjs の除外は tsc への移行までの措置で、ラッパーを書いて保守するコストに見合わないので見送った。移行が長引くなら再検討する
  • .gitignore に *.tgz を足す案: 残骸を見えなくするだけなので入れていない

意図的に対応しないこと

  • --ignore-rules false-cjs の削除: tsc への移行で types を分けたときに外す。package.json にはコメントを書けないので、PR 概要欄に書いた
  • top-level の main の検査: attw は型の解決しか見ないので、main が指すファイルが無くても検出しない(ローカルで main を存在しないパスにして exit 0 を確認)。pack-smoke も require / import が exports を優先するので main を読まない。pack した package.json の main / types が指すファイルの存在を pack-smoke で確かめる対応を、pack-smoke の改善として別の PR で行う
  • publish workflow(.github/workflows/publish.yaml)に attw を足すこと: publish は tag の push で build して、テストせずに公開する。通常の PR / main の CI を通ったコミットにタグを打つ運用を信頼して、重複する検査を省いている。pack-smoke も同じ扱い

発見された制約

  • --ignore-rules false-cjs は、ルール単位で全サブパス・全解決モードの FalseCJS を除外する。今後新しく出る false-cjs も隠れるので、tsc への移行の完了時に外すまで、この種の不具合は CI で検出できない
  • attw 0.18.5 には node10 だけを検査する profile が無い。2回目は root の node16 CJS / ESM と bundler も重ねて検査する
  • && でつないでいるので、1回目が失敗すると2回目は走らない。CI は失敗になるので見逃しにはならないが、1回目を直すまで root の node10 の問題はログに出ない
  • test:attw は build 済みの dist を pack するので、ローカルでは build の後でないと落ちる(test:pack-smoke と同じ前提)
  • attw が同梱する TypeScript は 5.6.1-rc で、プロジェクトの TypeScript(6.0.2)ではない。配布物の構造の検査としては十分だが、TypeScript 5.3〜6 での実際の解決を保証するものではない

新たに確認できた事実

  • fix: FetchT / JSONT を default export から named type export に変更 #369 がマージされた main で build して attw --pack --profile node16 を実行すると、合否判定の対象では、全サブパス(.・core・express・fastify・fetch・json・msw)の node16 (from ESM) の Masquerading as CJS だけが出る。node10 では root は 🟢、サブパス6個は Resolution failed で、いずれも (ignored)。--ignore-rules false-cjs 付きで exit 0、外すと exit 1
  • --ignore-rules false-cjs を付けると、結果の表では node16 (from ESM) が 🟢 (CJS) と表示される。除外した指摘は表から消える
  • top-level の types を存在しないファイルにすると、1回目は通り、2回目が node10: ❌ No types で exit 1 になる
  • tsc への移行後の想定(main: ./dist/cjs/index.js、types: ./dist/cjs/index.d.ts、.d.ts から import される宣言も dist/cjs にある)を一時コピーで再現すると、2回とも exit 0(Codex が確認)
  • attw 0.18.5 の --pack は、パッケージのディレクトリに notainc-typed-api-spec-<version>.tgz を作り、検査後に削除する(型の問題で exit 1 のときも削除する)。削除は finally ではないので、pack の後に解析が例外で落ちる(exit 3)か強制終了されると残る
  • pack-smoke は mkdtemp の一時ディレクトリに pack するので、attw と run-p で並列に走っても衝突しない。files の allowlist により tarball が pack の対象に入ることもない
  • lockfile の差分は attw とその依存(28 パッケージ)の追加だけで、既存パッケージのバージョン変更は無い
  • CI と同じ npm ci -w pkgs/typed-api-spec で attw が入り、PR の CI で test:attw が実行される
  • npm scripts の && は Windows の cmd.exe でも動く
  • npm install 時に出る install-scripts の警告(core-js-pure・tree-sitter)は examples の swagger-ui-react 由来で、attw とは関係ない

注意が必要な難所

  • prepack / prepare のような、dist を書き換える lifecycle script を将来足すと、attw と pack-smoke の npm pack が並列にそれを実行する。足すときは並列実行を見直す
  • attw が途中で落ちると tarball がパッケージのディレクトリに残る。.gitignore は *.tgz を無視しないので、ローカルで中断したら git status で確認する
  • tsc への移行では、exports の中だけでなく top-level の main / module / types も書き換える。types は2回目の attw で検出できるが、main は今は検出できない

残作業

  • なし(tsc への移行で --ignore-rules false-cjs を外す事と、pack-smoke で main / types の存在を確かめる事は、それぞれ別の PR で行う)

Comment thread pkgs/typed-api-spec/package.json Outdated
"test:type-check": "tsc --noEmit",
"test:pack-smoke": "node scripts/pack-smoke.mjs"
"test:pack-smoke": "node scripts/pack-smoke.mjs",
"test:attw": "attw --pack --profile node16 --ignore-rules false-cjs"

@shinagawa-web shinagawa-web Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI の test job は build の後に run-p -c test:* を実行するので、ここに足すだけで CI で走ります。workflow は変えていません。

1回目の --profile node16 は、node10 の結果を表に出しますが、合否判定には使いません。サブパスは exports でしか公開していないので、node10 では解決できなくて当然です。ただしこれだと root の node10(top-level の types)も判定から外れるので、2回目で root だけを strict profile で検査しています。同じ名前の tarball を作るので、&& で順番に実行しています。

--ignore-rules false-cjs は 🎭 Masquerading as CJS だけを除外します。tsup から tsc への移行で types を分けて直し、そのときにこの指定を外します。

"url": "https://github.com/nota/typed-api-spec"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.5",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.x なので、^0.18.5 で入るのは 0.18.x までです。判定ルールが変わる minor 更新は Dependabot の PR として来るので、その PR の CI で検出できます。lockfile の差分は attw とその依存の追加だけです。

@shinagawa-web

Copy link
Copy Markdown
Collaborator Author

レビュー報告書

AIとの対話コンテキスト

PR コメント(「attw-addToCi 対話コンテキスト」、source commit 2e50d39)から読み込んだ。

概要:

pull request概要欄

サマリー

  • 現状(before): 実装者の説明では「この後に tsup から tsc へ移行する予定で、そのときに exports を壊したら気づけるよう、移行の前に入れておく」。いまの exports は「各サブパスの types が CJS と ESM で共通(./dist/*.d.ts)」で、ESM から import しても CJS 向けの .d.ts が使われる
  • 変更(after): @arethetypeswrong/cli を devDependencies に追加し、"test:attw": "attw --pack --profile node16 --ignore-rules false-cjs" を scripts に追加。CI は run-p -c test:* で走るので workflow は変えていない
  • 除外の扱い: Masquerading as CJS は「tsup から tsc への移行で types を require / import ごとに分けて直すので、それまでは除外する」。「今後新しく出る false-cjs も隠すので、tsc への移行の完了時に必ず外す」

品質評価

  • 変更前の動作・問題が説明されているか: OK(今は exports の対応を検査する仕組みがない事と、types が CJS / ESM 共通である事が書かれている)
  • 「問題→解決」や「現状→修正」のペアで書かれているか: OK(「tsc への移行で exports を壊したら気づけない → 移行前に attw を CI に入れる」の対になっている)
  • 変更の範囲が明確か: OK(workflow を変えない事、lockfile の差分の中身、除外が1種類だけである事が明記されている)
  • 実装者自身の理解が見えるか: OK(node10 を外す理由、false-cjs の除外が将来の同種の不具合も隠すという副作用、pack-smoke と並列に走っても衝突しない理由を、自分の言葉で書いている)

説明と実装の整合性

差分は pkgs/typed-api-spec/package.json の2行と package-lock.json だけで、概要欄・インラインコメント(実装者 shinagawa-web の2件)・対話コンテキストと実装に大きな齟齬は無い。ローカルで build して次を確認した。

  • npm run test:attw -w pkgs/typed-api-spec は exit 0。--ignore-rules false-cjs を外すと exit 1 で、指摘は全7エントリポイントの node16 (from ESM) の Masquerading as CJS だけ
  • 実行後に tarball は残らない
  • lockfile は新規エントリ 28 件の追加だけで、既存パッケージの version / resolved / integrity の変更は無い
  • PR の CI(test job)のログで test:attw が実行され、表が全て 🟢 になっている

細かい表現のずれが2点ある(Codex も同じ指摘)。

  1. 「--profile node16 は node10 の解決を検査から外す指定」: 正確には、attw は node10 も解析して表に出し、終了コードの判定からだけ外す(profiles.js の ignoreResolutions: ["node10"]、表示は (ignored))。実害は無いが、次の「root の node10 も合否判定から外れる」点を理解する上では区別したほうがよい
  2. 「除外しているのはこの1種類だけで、main で実行した時点では他の指摘は出ていない」: --ignore-rules で除外しているルールが1種類だけなのは正しい。ただし表には、root 以外の6サブパスの node10 に Resolution failed が出ており、これは profile で合否判定から外れている。「合否判定の対象(node16 / bundler)では他の指摘は出ていない」と書くのが正確

また、概要欄の「attw は package のディレクトリに tarball を作り、検査後に消す」は通常の終了(型の問題による exit 1 も含む)では正しいが、attw 0.18.5 の削除処理は finally ではないため、pack の後に解析が例外で落ちる(exit 3)と残る。対話コンテキストは「強制終了」の場合だけを挙げている(後述)。

命名・設計パターンの一貫性

概ね既存の慣習と一致している。

  • devDependency は公開パッケージの pkgs/typed-api-spec に置き、他と同じキャレット指定で、アルファベット順の位置に入っている
  • 短い CLI オプションを scripts に直書きする形は、oxlint .、oxfmt --check .、tsc --noEmit と同じ。.attw.json に切り出してもコメントは書けないので、直書きで十分
  • workflow を変えず test:* に足すのは、test:pack-smoke と同じ組み込み方
  • pack-smoke とは役割が重ならない。pack-smoke は実行時の require / import、attw は型定義の解決を見ている

軽微な点として、既存の test:* は unit / lint / format / type-check / pack-smoke と「何を検査するか」で名付けられているのに対し、test:attw だけがツール名になっている(Codex の指摘)。ただ、attw は短く、何を走らせるかが一意で、検索もしやすい。変更は必須ではないと判断する。

長期視点で命名・設計を考察する

test:attw というツール名の script 名は、ツールを別のものに替えたときに陳腐化する。test:package-types のような目的の名前にしておけば、その心配は無い。
一方で、attw は TypeScript のパッケージで型定義の配布を検査するときの事実上の標準で、置き換える候補は見当たらない。むしろ package-types と書くと、type-check との違いがかえって分かりにくくなる。
現時点では test:attw のままでよいと判断する。

--profile node16 と --ignore-rules false-cjs を scripts の1行に直書きする設計は、tsc への移行で false-cjs を外すという前提に依存している。移行が予定どおりに進めば、オプションは --profile node16 だけになり、直書きのままで困らない。
懸念は移行が長引いた場合である。除外したまま放置されると、新しく足したサブパスの types の誤りも検出されない。さらに、package.json にコメントを書けないので、なぜ除外しているのかが PR 概要欄にしか残らない。
tsc への移行を追跡する公開の Issue は見当たらなかった。移行の時期がはっきりしないなら、除外を外す条件をどこか追跡できる場所に残す事を検討してよい。結論は保留とする。

バグ・脆弱性の調査

Codex CLI に、並列 pack の安全性、tarball の残骸、profile と ignore-rules の影響、attw が同梱する TypeScript、Dependabot との組み合わせ、bin の解決、publish workflow について相談した。Codex の指摘を attw 0.18.5 の実装(node_modules/@arethetypeswrong/cli/dist/index.js・profiles.js)と CI のログで検証した結果は次のとおり。CI が不安定になる要因や脆弱性は見つからなかった。

  1. [中] package root . の node10 解決も合否判定から外れる(Codex と同意見)
    • --profile node16 はエントリポイントを区別せずに node10 を合否判定から外す。サブパス6個が node10 で解決できないのは当然だが、root は top-level の main / types で解決でき、CI のログでも (ignored) 🟢 になっている
    • peerDependencies は typescript: "^5.3 || ^6" で、TypeScript 5 系を moduleResolution: node(node10)で使う利用者は root を top-level の types で解決する。tsc への移行で top-level の types を書き誤っても、exports["."] が正しければ test:attw は通る。pack-smoke は実行時しか見ないので、これを補えない
    • 対処の例: root だけを strict profile で追加検査する(attw --pack --entrypoints . --ignore-rules false-cjs を別に走らせる。ただし同じディレクトリで pack するので、test:attw と並列に走らない形にする必要がある)。tsc への移行は exports と top-level のフィールドを両方触る可能性が高いので、移行より前に入れる意味がある
  2. [中] false-cjs の除外がルール単位である(対話コンテキストでも認識済み)
    • 既知の7件だけでなく、新しいサブパスの誤り・一度直したサブパスの再発・移行完了後の外し忘れも検出できない。Codex は、JSON 出力を読んで現在の7件だけを許容する allowlist 方式のラッパーを提案した
    • 対話コンテキストが言う「tsc への移行で外す」が短期間で済むなら許容できる。移行の時期が未定なら、検出の抜けが長く続く
  3. [低] attw が同梱する TypeScript は 5.6.1-rc(attw --version で確認)
    • attw はプロジェクトの TypeScript 6.0.2 ではなく、自分が同梱する TypeScript で解決を判定する。この検査は配布物の構造の静的な検査としては有用だが、TypeScript 5.3〜6 の実際の解決の互換性までは保証しない。PR の目的(tsc への移行で exports を壊したら気づく)の範囲では問題にならない
  4. [低] tarball が残るのは強制終了時だけではない
    • attw は npm pack の後、正常終了と型の問題による exit 1 のときは unlink するが、読み込み・展開・解析で例外になると handleError() が exit 3 で終わり、削除に到達しない。CI は使い捨ての環境なので実害は無く、files の allowlist により次の pack に紛れ込むことも無い
  5. [低] attw の 0.x の minor 更新も Dependabot の auto-merge の対象になる
    • 0.18 → 0.19 は Dependabot の分類では minor なので、auto-approve と auto-merge の対象になる。main の ruleset で test が required status check になっているので、新しいルールで落ちれば止まる。インラインコメントの「その PR の CI で検出できる」は正しい。ただし、判定が緩くなる方向の変更は CI が通るので、そのままマージされる

並列 pack について: attw はパッケージのディレクトリ、pack-smoke は mkdtemp の一時ディレクトリに tarball を出し、prepack / prepare も無いので、今は衝突しない。対話コンテキストの「難所」に書かれているとおり、dist を書き換える lifecycle script を足すときは見直しが要る。CI で npm ci -w pkgs/typed-api-spec の後に root の .bin/attw が解決できる事、Node 24 で動く事は、PR の CI の成功で確認できる。

考慮漏れの確認

設計判断の根拠

  • workflow を変えず test:* に足す判断: CI のログで test:attw が実行されている事を確認した。妥当
  • --profile node16 の判断: 根拠(「サブパスは exports でしか公開していないので node10 では解決できなくて当然」)はサブパスについては正しい。ただし root の node10 は解決できるのに一緒に外れている事が検討されていない(バグ・脆弱性の調査の 1)
  • --ignore-rules false-cjs の判断: 「直すのは tsc への移行なので、それまでは除外しないと CI が落ちる」とあるが、今の tsup の出力にも各エントリポイントの .d.mts が既にある(dist/index.d.mts など)。close された chore: update package.json scripts and add pack-smoke script #319 では、今の tsup の出力のまま types を require / import の内側に分ける修正が実装されていた。つまり技術的には今でも直せる。tsc への移行で出力ファイル名が変わり、二度手間になるから先送りする、という事情なら妥当だが、その理由は対話コンテキストにも概要欄にも書かれていない。「tsc への移行まで直せない」と読めてしまう

却下した代替案の再評価

  • 0.18.5 に固定する案: 結論(キャレットにする)は妥当。ただし固定案の利点として挙げた「判定ルールの変化で CI が急に落ちるのを避けられる」は前提が不正確。lockfile があり CI は npm ci なので、キャレットでも固定でも CI に入る attw は 0.18.5 で、新しい版が出ただけで CI が変わる事は無い。Dependabot も versioning-strategy: increase なので、固定でも更新の PR は来る。比べた利点がもともと存在しないので、結論は変わらない
  • 一時ディレクトリに pack して attw を当てる Node のラッパー案: 「tarball の残骸を防ぐ」だけで評価して見送っており、利点を過小評価している。ラッパーにすると、(a) root だけを strict profile で追加検査する、(b) FalseCJS をルールごと除外せず、今の7件だけを許容して増減を検出する、(c) try/finally で例外時にも tarball を消す、の3つがまとめて実現できる。一方、false-cjs の除外が tsc への移行までの短期間の措置なら、ラッパーを書いて保守するコストに見合わないという判断もありうる。どちらを採るかは移行の時期次第で、この比較は対話コンテキストに残しておくとよい。なお Codex は、pack-smoke と tarball を共有するには2つの検査を1つの親スクリプトにまとめる必要があり、ラッパーを足すだけでは共有できないと補足している
  • .gitignore に *.tgz を足す案: 「残骸を見えなくするだけ」という却下の理由は妥当

失敗した試行のプロセス検証

対話コンテキストに「試したがダメだった」という記載は無い。除外を外すと exit 1 になる確認は、ローカルで再現できた。

意図的に対応しない事項の妥当性

  • false-cjs 除外の削除を tsc への移行で行う事: 前述のとおり今でも技術的には直せるので、先送りする理由を残しておくべき。また対話コンテキストは「残作業: なし」としているが、除外を外す作業は移行 PR に依存しており、移行を追跡する公開の Issue / PR は見当たらなかった。外し忘れを防ぐには、追跡できる場所に残す必要がある
  • publish workflow に attw を足さない事: 「通常の PR / main の CI を通ったコミットを公開する運用なら不要」という条件付きの判断で、運用(README の手順では main にタグを打つ)と ruleset の required status check を踏まえれば妥当。ただし publish.yaml は tag の push で build して、テストせずに publish する。タグが CI を通ったコミットを指す事は、仕組みでは保証されていない。「不要」ではなく「運用を信頼して重複する検査を省く」判断である事を認識しておけばよい。pack-smoke も同じ扱いなので、この PR の範囲で変える必要は無い

記載されている事実の正確性

  • 「node16 (from ESM) の Masquerading as CJS だけ。node10 の Resolution failed は profile で除外されている」: 正しい。ただし root . の node10 は 🟢 で、Resolution failed はサブパス6個だけ
  • 「型の問題で exit 1 のときも削除する」: 正しい(process.exitCode を設定するだけで、unlink まで進む)。ただし「強制終了されると残る」だけでなく、解析中の例外(exit 3)でも残る
  • 「lockfile の差分は attw とその依存(28 パッケージ)の追加だけ」: 正しい
  • 「CI と同じ npm ci -w pkgs/typed-api-spec で attw が入る」: PR の CI の成功で確認できる
  • 「install-scripts の警告は examples の swagger-ui-react 由来」: Codex が依存の経路を確認し、正しいとしている

レビュー作業において発生した問題

  • Codex CLI に計4回相談した(説明と実装の整合性、命名・設計パターン、バグ・脆弱性、考慮漏れ)。同じディレクトリで attw の --pack が同時に走ると tarball 名が衝突するため、attw を実行しうる相談は順番に実行し、命名・設計の相談では build・attw・npm pack を禁じて並列に実行した
  • 考慮漏れの相談で、Codex は attw と npm pack の実行に失敗した(Codex のサンドボックスから見た ~/.npm の権限エラー。リポジトリが原因ではない)。Codex は attw の実装と CI のログで補完しており、該当する事実(exit コードと tarball の削除)は相談元がローカルで実行して確認済みなので、結論への影響は無い
  • 手順のスキップは無い

結論

merge 可。ただし、対話コンテキストと概要欄の補足、および root の node10 検査の要否の判断を推奨する。

この PR は、tsc への移行前に exports の退行を検出する仕組みを入れるという目的に対して、最小限の差分で正しく動いている。CI でも実行されており、概要欄の品質も4項目すべて OK。CI が不安定になる要因や脆弱性は見つからなかった。

推奨するアクション:

  1. (判断を推奨)root . の node10 検査: --profile node16 で、解決できている root の node10(top-level の main / types)まで合否判定から外れている。TypeScript 5 系の node10 解決の利用者を守るなら、移行より前に root だけ strict profile で検査するか、検査しない事を判断として残す
  2. (推奨)false-cjs を tsc への移行まで先送りする理由を残す: 今の tsup の出力にも .d.mts があり、技術的には今でも直せる。先送りの理由(移行で出力ファイル名が変わる等)と、除外を外す作業を追跡できる場所を残す
  3. (軽微)表現の修正: 「node10 を検査から外す」→「node10 は合否判定から外す(解析と表示はされる)」、「他の指摘は出ていない」→「合否判定の対象では他の指摘は出ていない」、tarball が残るのは強制終了時に加えて解析中の例外時もある事
  4. (軽微)却下した案の比較の前提: 固定案の利点として挙げた「CI が急に落ちるのを避けられる」は、lockfile があるので元々存在しない。ラッパー案は残骸の防止以外の利点(root の strict 検査、7件の allowlist)も含めて比較し直すとよい

--profile node16 は root の node10 も合否判定から外すので、top-level の types を書き誤っても検出できなかった。root だけを strict profile で続けて検査する。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shinagawa-web

shinagawa-web commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

sanity-review の指摘に対応しました。

  • root の node10 が合否判定から外れていた点: root だけを strict profile で検査する2回目の実行を test:attw に足しました(778c92f)
  • false-cjs を今直さない理由(tsup の出力でも直せるが、tsc への移行でパスが変わり二度手間になる)を概要欄と対話コンテキストに書き足しました
  • 概要欄と対話コンテキストの不正確な表現を3か所直しました
    • --profile node16 は node10 を検査から外すのではなく、合否判定から外すだけ(表には出る)
    • 「他の指摘は出ていない」は、合否判定の対象に限った話
    • tarball は強制終了のときに加えて、解析中の例外(exit 3)のときも残る
  • 却下した案の比較の前提: 対話コンテキストで訂正しました
  • top-level の main が検出できない点(レビュー後に見つかったもの): pack-smoke の改善として別の PR で対応します

名前(test:attw)、attw が同梱する TypeScript の版、Dependabot の auto-merge、publish workflow の扱いは、この PR では変えていません。

@shinagawa-web
shinagawa-web marked this pull request as ready for review October 13, 2026 05:23

@yokotaso yokotaso left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

  • 修正が本体に影響がないことを確認しました

@yokotaso
yokotaso merged commit e2ac8fa into main Oct 15, 2026
6 checks passed
@yokotaso
yokotaso deleted the attw-addToCi branch October 15, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants