feat!: modernize toolchain and ship ESM-only build#1373
Conversation
Bundler, test runner, linter, husky, publish action and TypeScript major
all swapped in one go to align with @tada5hi/typescript-template and the
upcoming typeorm@1.0 release.
Build:
- Replace rollup + @swc/core with tsdown (rolldown + oxc). One config
emits both the library (dist/index.mjs + dist/index.d.mts) and the
CLI (bin/cli.mjs).
- Split the build script into build:types (tsc --noEmit) and build:js
(tsdown), composed by build.
- Preserve the two rollup-only behaviours: the CLI bundle marks
cross-domain imports as external and rewrites them to "typeorm-extension"
so the CLI shares singleton state with the consumer's installed library;
the library bundle suffixes runtime typeorm/<deep> imports with .js so
Node's strict ESM resolver accepts them.
- The CLI rewrite plugin is now POSIX-safe across platforms.
Tests:
- Replace jest + ts-jest with vitest 4 + unplugin-swc (so TypeORM
decorator metadata is still emitted).
- Inline locter via server.deps.inline so dynamic .ts loading from inside
locter.load() goes through vite-node and shares entity-class identity
with the test module.
- __dirname -> import.meta.dirname in 3 test files + 1 fixture.
Lint:
- ESLint 8 + @tada5hi/eslint-config-typescript + .eslintrc -> ESLint 10
flat config + @tada5hi/eslint-config v2 in eslint.config.mjs.
- Drop import/no-cycle (eslint-plugin-import-lite does not ship it).
TypeScript 6:
- import type { CompilerOptions, TypeAcquisition } from 'typescript' is
no longer valid; the public types restructured behind the ts.* namespace.
src/utils/tsconfig/type.ts updated accordingly.
- tsconfig sets moduleResolution: node10 + ignoreDeprecations: "6.0" so
type-only deep imports into typeorm subpaths still resolve.
- Relaxed strict options (noUncheckedIndexedAccess, noUnusedLocals,
noUnusedParameters, verbatimModuleSyntax) explicitly set to false to
preserve the pre-existing source patterns; tightening them is intentional
future work.
CI / Release:
- googleapis/release-please-action@v5, actions/setup-node@v4, cache@v4.
- workspaces-publish -> tada5hi/monoship@v2.
- husky 9 hook in v9 format (no shebang, no _/husky.sh source).
- commitlint.config.js -> commitlint.config.mjs.
Docs:
- README and docs/guide/cli.md no longer mention the dual binary or
cli.cjs. Added docs/guide/migration-guide-v4.md and wired it into the
VitePress sidebar.
- AGENTS.md + .agents/* updated to describe the new toolchain. Added a
Documentation Sync Rule to AGENTS.md.
BREAKING CHANGE: typeorm-extension is now ESM-only. CJS consumers on
Node 22+ can still require('typeorm-extension') thanks to require(esm)
support; older Node versions can no longer consume the package.
Minimum Node is now 22 (was 20.19). The typeorm-extension-esm binary
alias is removed - use typeorm-extension. See docs/guide/migration-guide-v4.md.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (13)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Modernizes typeorm-extension’s build/test/lint/release toolchain and switches the package to an ESM-only distribution, while refactoring a few internal modules (query, database context/method wiring) to match the new template.
Changes:
- Replace Rollup/Jest/ESLint v8 configs with tsdown + Vitest + ESLint flat config, and update CI/release workflows accordingly.
- Publish ESM-only output (
"type": "module"), simplify CLI binary to a single entry, and update docs/migration guides. - Refactor query and database modules (types/options wiring, context normalization) and apply related test updates.
Reviewed changes
Copilot reviewed 77 out of 84 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| tsdown.config.ts | Adds tsdown build config and re-implements prior Rollup behaviors via plugins. |
| tsconfig.json | Updates TS compiler settings for ESM/noEmit/tooling alignment. |
| tsconfig.eslint.json | Removes Jest-era ESLint project config. |
| test/vitest.config.ts | Introduces Vitest config (swc transform + coverage + deps inline). |
| test/unit/utils/tsconfig.spec.ts | Updates ESM path resolution usage (import.meta.dirname). |
| test/unit/utils/file-path.spec.ts | Refactors loops/object literals (formatting + minor cleanups). |
| test/unit/seeder/seeder.spec.ts | Refactors object literal formatting. |
| test/unit/seeder/factory.spec.ts | Refactors loop style. |
| test/unit/query/relations.spec.ts | Refactors object literal formatting. |
| test/unit/query/index.spec.ts | Adds pagination defaulting coverage + formatting tweaks. |
| test/unit/query/filters.spec.ts | Formatting changes (includes trailing whitespace issues). |
| test/unit/helper/entity-join-columns.spec.ts | Refactors object literal formatting. |
| test/unit/env/module.spec.ts | Refactors object literal formatting. |
| test/unit/database/migration.spec.ts | Ensures DataSource init/destroy around migration generation. |
| test/unit/database/index.spec.ts | Refactors imports/object literals. |
| test/unit/data-source/options/module.spec.ts | Refactors object literal formatting. |
| test/unit/data-source/options/env.spec.ts | Refactors object literals; updates calls. |
| test/unit/data-source/find.spec.ts | Switches to node:path + ESM dirname usage. |
| test/jest.config.js | Removes Jest config. |
| test/data/typeorm/utils.ts | Refactors object literal formatting and calls. |
| test/data/typeorm/FakeSelectQueryBuilder.ts | Updates type signature; introduces whitespace-only line. |
| test/data/typeorm/factory.ts | Refactors object literal formatting. |
| test/data/typeorm/data-source-default.ts | Switches to node:path + import.meta.dirname. |
| test/data/seed/user.ts | Re-formats insert object literal (introduces trailing spaces). |
| test/data/seed/role.ts | Refactors object literal formatting. |
| test/data/entity/user.ts | Re-formats imports/indentation. |
| test/data/entity/role.ts | Fixes indentation for fields. |
| src/utils/tsconfig/type.ts | Re-formats types (but still uses TS6-incompatible type imports). |
| src/utils/tsconfig/module.ts | Minor string API modernization (includes) and catch cleanup. |
| src/utils/separator.ts | Minor loop + string API modernization. |
| src/utils/object.ts | Adds extendObject() helper. |
| src/utils/file-system.ts | Simplifies catch clause. |
| src/utils/file-path.ts | Refactors loops/includes usage and key iteration. |
| src/seeder/utils/prepare.ts | Refactors loops and parsing (Number.parseInt). |
| src/seeder/factory/utils.ts | Refactors loops and config iteration. |
| src/seeder/factory/module.ts | Changes faker import/loading approach; refactors loops. |
| src/seeder/executor.ts | Loop refactors + Number.parseInt usage. |
| src/query/utils/option.ts | Loop refactor. |
| src/query/utils/alias.ts | Loop refactor. |
| src/query/type.ts | Reworks QueryApplyOptions typing to local option types. |
| src/query/parameter/sort/module.ts | Loop refactor for parse output application. |
| src/query/parameter/relations/type.ts | Adds onJoin hook type and option. |
| src/query/parameter/relations/module.ts | Adds onJoin callback invocation; defaults options param. |
| src/query/parameter/filters/type.ts | Type formatting adjustments. |
| src/query/parameter/filters/module.ts | Loop refactors. |
| src/query/parameter/fields/type.ts | Type formatting adjustments. |
| src/query/module.ts | Threads QueryApplyOptions into parse-output application + merges relation options. |
| src/helpers/entity/uniqueness.ts | Adds doc comments + loop refactors (introduces trailing spaces). |
| src/helpers/entity/metadata.ts | Import formatting adjustments. |
| src/helpers/entity/join-columns.ts | Refactors object literal and loop style. |
| src/env/utils.ts | Import formatting adjustments. |
| src/env/module.ts | Import formatting adjustments. |
| src/database/utils/schema.ts | Refactors runMigrations call formatting. |
| src/database/utils/migration.ts | Changes generated template to import type; removes init/destroy responsibility; refactors file write. |
| src/database/utils/context.ts | Refactors to normalized options builder and new context input types. |
| src/database/methods/type.ts | Adds new database context types. |
| src/database/methods/index.ts | New barrel for database methods. |
| src/database/methods/drop/module.ts | New dropDatabase method entry using new context types. |
| src/database/methods/drop/index.ts | New barrel export. |
| src/database/methods/create/module.ts | New createDatabase method entry using new context types. |
| src/database/methods/create/index.ts | New barrel export. |
| src/database/methods/check/types.ts | Removes base/create/drop context types (moved to new type file). |
| src/database/methods/check/module.ts | Updates imports/paths and catch cleanup. |
| src/database/methods/check/index.ts | New barrel export. |
| src/database/index.ts | Re-exports via methods barrel. |
| src/database/drop.ts | Removes legacy dropDatabase entry. |
| src/database/create.ts | Removes legacy createDatabase entry. |
| src/database/driver/utils/create.ts | Minor includes() refactor. |
| src/database/driver/utils/build.ts | Updates DriverOptions type import path. |
| src/database/driver/types.ts | Adds DriverOptions type definition file. |
| src/database/driver/sqlite.ts | Updates context input types + refactors initialization. |
| src/database/driver/postgres.ts | Updates context input types + DriverOptions import path. |
| src/database/driver/oracle.ts | Updates context input types + DriverOptions import path. |
| src/database/driver/mysql.ts | Updates context input types + DriverOptions import path. |
| src/database/driver/mssql.ts | Updates context input types + DriverOptions import path. |
| src/database/driver/mongodb.ts | Updates context input types + DriverOptions import path. |
| src/database/driver/index.ts | Exports types instead of removed type. |
| src/database/driver/cockroachdb.ts | Updates context input types. |
| src/data-source/find/module.ts | Refactors loops and module export iteration. |
| src/cli/commands/seed/create.ts | Simplifies catch clause. |
| src/cli/commands/database/drop.ts | Refactors option declaration formatting. |
| src/cli/commands/database/create.ts | Updates to new DatabaseCreateContextInput type usage. |
| rollup.config.mjs | Removes Rollup build config. |
| release-please-config.json | Minor formatting/key ordering updates. |
| README.MD | Updates CLI usage examples for ESM + tsx. |
| package.json | Switches to ESM-only metadata, toolchain scripts, and dependency changes. |
| eslint.config.mjs | Adds ESLint flat config with project overrides/ignores. |
| docs/guide/query.md | Fixes minor grammar. |
| docs/guide/migration-guide-v4.md | Adds v4 migration guide (includes TypeORM peer claim mismatch). |
| docs/guide/migration-guide-v3.md | Adds note referencing v4 ESM-only change. |
| docs/guide/cli.md | Updates CLI docs for ESM loader usage. |
| docs/.vitepress/config.mjs | Sidebar/navigation update + formatting fixes. |
| commitlint.config.mjs | Converts commitlint config to ESM. |
| commitlint.config.js | Removes CJS commitlint config. |
| CLAUDE.md | Adds agent doc references. |
| CHANGELOG.md | Updates changelog content. |
| AGENTS.md | Adds agent guide + documentation sync rule. |
| .release-please-manifest.json | Updates manifest version. |
| .husky/commit-msg | Updates Husky v9 hook format. |
| .gitignore | Updates ignored paths. |
| .github/workflows/release.yml | Updates release flow (release-please v5 + monoship). |
| .github/workflows/main.yml | Updates CI triggers, concurrency, and job ordering. |
| .github/dependabot.yml | Retargets updates to master. |
| .github/actions/install/action.yml | Updates setup-node/cache versions and cache key. |
| .github/actions/build/action.yml | Updates cache action version and formatting. |
| .eslintrc | Removes legacy ESLint config. |
| .agents/testing.md | Adds testing/tooling documentation for Vitest + swc. |
| .agents/structure.md | Adds project structure documentation. |
| .agents/conventions.md | Adds conventions/tooling documentation. |
| .agents/architecture.md | Adds architecture documentation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "name": "typeorm-extension", | ||
| "version": "3.9.0", | ||
| "type": "module", |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
src/cli/commands/seed/create.ts (1)
87-87: ⚡ Quick winUnused catch binding removed correctly.
The modernization from
catch (e)tocatchis appropriate since the error was never used.Consider logging the actual error for easier debugging when file writes fail:
📝 Optional: Log error details for debugging
try { await fs.promises.writeFile(filePath, template, { encoding: 'utf-8' }); - } catch { + } catch (e) { consola.warn(`The seed could not be written to the path ${filePath}.`); + consola.error(e); process.exit(1); }This would help users diagnose issues like permission errors, disk space, or path problems.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/cli/commands/seed/create.ts` at line 87, The empty catch was intentionally simplified but now loses useful error context; change the bare `catch` back to `catch (err)` in the failing file-write block in create.ts and log the error (e.g., `console.error("Failed to write seed file:", err)` or use the module's existing logger) so permission/disk/path errors are visible for debugging.README.MD (1)
68-75: 💤 Low valueOptional: Add language identifier to code fence.
The code block starting at line 68 is missing a language identifier. Adding
jsonwould enable syntax highlighting and satisfy markdown linting:-``` +```json "scripts": {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.MD` around lines 68 - 75, The code block showing the package.json "scripts" object is missing a Markdown language identifier; update the fence that contains the "scripts" block (which includes keys like "db:create", "db:drop", "seed:run", "seed:create") to use ```json so the block begins with ```json to enable JSON syntax highlighting and satisfy the linter.docs/guide/cli.md (1)
11-18: 💤 Low valueOptional: Add language identifier to code fence.
The code block starting at line 11 is missing a language identifier. Adding
jsonwould enable syntax highlighting:-``` +```json "scripts": {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/guide/cli.md` around lines 11 - 18, The fenced code block showing the npm "scripts" object should specify a language for syntax highlighting; update the opening fence from ``` to ```json so the block containing "scripts" and keys like "db:create", "db:drop", "seed:run", and "seed:create" is marked as JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/actions/build/action.yml:
- Line 9: Replace the floating tag "uses: actions/cache@v4" with a pinned commit
SHA to ensure reproducible builds; locate the composite action step that uses
"actions/cache@v4" and change it to the full commit reference
(actions/cache@<full-commit-sha>) by finding the latest stable commit on the
actions/cache repository and using that SHA in the action.yml.
In @.github/actions/install/action.yml:
- Line 15: Replace the floating action tags used in the workflow (specifically
the two occurrences "actions/setup-node@v4" and "actions/cache@v4") with pinned
commit SHAs to prevent supply-chain risk; locate the lines that call those
actions in the action.yml and swap the tag portion (e.g., "`@v4`") with the
corresponding commit SHA for each repository (e.g.,
"`@b1f6e04751a816d7b3694d8e5c19456590111534`" for setup-node) and update both
occurrences consistently so the workflow references immutable SHAs instead of
floating tags.
In @.github/workflows/main.yml:
- Around line 50-52: Replace each floating checkout action usage in the
"Checkout" step with a pinned commit SHA of actions/checkout and add
persist-credentials: false; specifically update every "Checkout" step that
currently uses actions/checkout@v6 (the four occurrences) to reference the full
commit SHA instead of `@v6` and include persist-credentials: false under that step
to disable passing GITHUB_TOKEN to subsequent steps.
In @.github/workflows/release.yml:
- Line 33: The workflow uses mutable action tags
(googleapis/release-please-action@v5 and the other action at `@v2`) which should
be replaced with immutable commit SHAs; update the uses entries to point to the
corresponding full-length commit SHA for googleapis/release-please-action and
the other action (the ad-m/github-push-action referenced at `@v2`) by looking up
the action repositories' stable release commit on GitHub and substituting the
tag (e.g., `@v5/`@v2) with the full 40-character commit SHA so the release
workflow is pinned immutably.
In `@src/utils/file-path.ts`:
- Around line 70-72: The loop in src/utils/file-path.ts is incorrectly using
substring matching (base.includes(jsExtension)) which mis-detects extensions
like "my-json.ts"; change the check to test the filename's extension suffix
instead (e.g., use base.endsWith(jsExtension) or compare path.extname(base)
against entries in jsExtensions) so only actual file extensions trigger the
early return in the loop over jsExtensions; update the loop that references
base, jsExtensions and input accordingly.
In `@tsconfig.json`:
- Line 7: The tsconfig.json currently sets "moduleResolution": "node10", which
forces legacy CommonJS-style resolution; update the moduleResolution compiler
option to a modern ESM-aware resolver (for example "nodenext" or
"node16"/"bundler" depending on your Node target) so package.json exports and
conditional resolution work correctly; open tsconfig.json and replace the
"moduleResolution" value from "node10" with the chosen modern resolver and run a
quick build to confirm no import resolution regressions.
In `@tsdown.config.ts`:
- Around line 31-33: The current rule in tsdown.config.ts blindly externalizes
any import with source.startsWith('../') to { id: 'typeorm-extension', external:
true }; change it to resolve the import path and only externalize when that
resolved path actually escapes the CLI directory. Use path.resolve/path.dirname
on the importer (handle importer possibly undefined) and path.relative to the
project's src/cli root to determine if the import is outside src/cli
(relativePath.startsWith('..') or path.isAbsolute check), and only then return
the externalization object; otherwise let the import pass through. Ensure you
reference the existing source and importer variables and the return value { id:
'typeorm-extension', external: true } in the updated condition.
---
Nitpick comments:
In `@docs/guide/cli.md`:
- Around line 11-18: The fenced code block showing the npm "scripts" object
should specify a language for syntax highlighting; update the opening fence from
``` to ```json so the block containing "scripts" and keys like "db:create",
"db:drop", "seed:run", and "seed:create" is marked as JSON.
In `@README.MD`:
- Around line 68-75: The code block showing the package.json "scripts" object is
missing a Markdown language identifier; update the fence that contains the
"scripts" block (which includes keys like "db:create", "db:drop", "seed:run",
"seed:create") to use ```json so the block begins with ```json to enable JSON
syntax highlighting and satisfy the linter.
In `@src/cli/commands/seed/create.ts`:
- Line 87: The empty catch was intentionally simplified but now loses useful
error context; change the bare `catch` back to `catch (err)` in the failing
file-write block in create.ts and log the error (e.g., `console.error("Failed to
write seed file:", err)` or use the module's existing logger) so
permission/disk/path errors are visible for debugging.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bbdc0c7d-396b-45e1-a55c-208ba68464fb
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (83)
.agents/conventions.md.agents/structure.md.agents/testing.md.eslintrc.github/actions/build/action.yml.github/actions/install/action.yml.github/workflows/main.yml.github/workflows/release.yml.gitignore.husky/commit-msgAGENTS.mdREADME.MDcommitlint.config.jscommitlint.config.mjsdocs/.vitepress/config.mjsdocs/guide/cli.mddocs/guide/migration-guide-v3.mddocs/guide/migration-guide-v4.mdeslint.config.mjspackage.jsonrelease-please-config.jsonrollup.config.mjssrc/cli/commands/database/create.tssrc/cli/commands/database/drop.tssrc/cli/commands/seed/create.tssrc/data-source/find/module.tssrc/database/driver/sqlite.tssrc/database/driver/utils/create.tssrc/database/methods/check/module.tssrc/database/utils/context.tssrc/database/utils/migration.tssrc/database/utils/schema.tssrc/env/module.tssrc/env/utils.tssrc/helpers/entity/join-columns.tssrc/helpers/entity/metadata.tssrc/helpers/entity/uniqueness.tssrc/query/module.tssrc/query/parameter/fields/type.tssrc/query/parameter/filters/module.tssrc/query/parameter/filters/type.tssrc/query/parameter/relations/module.tssrc/query/parameter/sort/module.tssrc/query/type.tssrc/query/utils/alias.tssrc/query/utils/option.tssrc/seeder/executor.tssrc/seeder/factory/module.tssrc/seeder/factory/utils.tssrc/seeder/utils/prepare.tssrc/utils/file-path.tssrc/utils/file-system.tssrc/utils/object.tssrc/utils/separator.tssrc/utils/tsconfig/module.tssrc/utils/tsconfig/type.tstest/data/entity/role.tstest/data/entity/user.tstest/data/seed/role.tstest/data/seed/user.tstest/data/typeorm/FakeSelectQueryBuilder.tstest/data/typeorm/data-source-default.tstest/data/typeorm/factory.tstest/data/typeorm/utils.tstest/jest.config.jstest/unit/data-source/find.spec.tstest/unit/data-source/options/env.spec.tstest/unit/data-source/options/module.spec.tstest/unit/database/index.spec.tstest/unit/database/migration.spec.tstest/unit/env/module.spec.tstest/unit/helper/entity-join-columns.spec.tstest/unit/query/filters.spec.tstest/unit/query/index.spec.tstest/unit/query/relations.spec.tstest/unit/seeder/factory.spec.tstest/unit/seeder/seeder.spec.tstest/unit/utils/file-path.spec.tstest/unit/utils/tsconfig.spec.tstest/vitest.config.tstsconfig.eslint.jsontsconfig.jsontsdown.config.ts
💤 Files with no reviewable changes (5)
- rollup.config.mjs
- test/jest.config.js
- commitlint.config.js
- tsconfig.eslint.json
- .eslintrc
| key: ${{ runner.os }}-build-${{ github.sha }} | ||
| - name: Use cache | ||
| id: 'cache' | ||
| uses: actions/cache@v4 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/actions/build/action.yml | head -20Repository: tada5hi/typeorm-extension
Length of output: 708
🏁 Script executed:
# Check if there are other pinned actions in the repository to understand the pattern
rg "uses: .+@" .github/ -A 1 -B 1Repository: tada5hi/typeorm-extension
Length of output: 2413
Pin actions/cache to a specific commit SHA in composite actions.
This composite action uses actions/cache@v4 (floating tag). Pinning to a specific commit SHA ensures reproducible CI runs and prevents unexpected upstream changes if the maintainer reassigns the version tag.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/actions/build/action.yml at line 9, Replace the floating tag "uses:
actions/cache@v4" with a pinned commit SHA to ensure reproducible builds; locate
the composite action step that uses "actions/cache@v4" and change it to the full
commit reference (actions/cache@<full-commit-sha>) by finding the latest stable
commit on the actions/cache repository and using that SHA in the action.yml.
| shell: bash | ||
| run: echo ${{ github.ref }} | ||
| - name: Use Node.js ${{ inputs.node-version }} | ||
| uses: actions/setup-node@v4 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/actions/install/action.yml | head -30Repository: tada5hi/typeorm-extension
Length of output: 1173
Pin setup/install action dependencies to commit SHAs.
Lines 15 and 21 use floating tags (actions/setup-node@v4, actions/cache@v4). Replace these with pinned commit SHAs (e.g., actions/setup-node@b1f6e04751a816d7b3694d8e5c19456590111534) to prevent supply chain risks from tag reassignments and improve auditability.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/actions/install/action.yml at line 15, Replace the floating action
tags used in the workflow (specifically the two occurrences
"actions/setup-node@v4" and "actions/cache@v4") with pinned commit SHAs to
prevent supply-chain risk; locate the lines that call those actions in the
action.yml and swap the tag portion (e.g., "`@v4`") with the corresponding commit
SHA for each repository (e.g., "`@b1f6e04751a816d7b3694d8e5c19456590111534`" for
setup-node) and update both occurrences consistently so the workflow references
immutable SHAs instead of floating tags.
| - name: Checkout | ||
| uses: actions/checkout@v6 | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check if the workflow file exists and its size
ls -lh .github/workflows/main.yml 2>/dev/null || echo "File not found"Repository: tada5hi/typeorm-extension
Length of output: 145
🏁 Script executed:
# Read the entire workflow file to understand its structure
cat -n .github/workflows/main.ymlRepository: tada5hi/typeorm-extension
Length of output: 2687
🌐 Web query:
GitHub Actions security hardening commit SHA pinning best practices
💡 Result:
Pinning GitHub Actions to a full-length commit SHA is a critical security best practice that transforms a mutable action reference into an immutable one [1][2][3][4]. Because tags (e.g., @v4) and branches (e.g., @main) can be moved or deleted by action maintainers or malicious actors, they are not secure trust anchors [3][5][6]. Pinning to a full-length SHA ensures that the exact, validated code you tested is the same code executed in your pipeline, mitigating the risk of supply chain attacks where malicious code is injected into a tag or repository after you have already audited it [1][4][7][8]. Best Practices for SHA Pinning: 1. Use Full-Length SHAs: Always use the full 40-character commit SHA rather than a shortened version to ensure absolute uniqueness and collision resistance [3][9][4]. 2. Include Version Comments: To maintain readability and ease of maintenance, append the original tag as a comment in your workflow file [10][6][7]. Example: uses: actions/checkout@11bd719 # v4.2.2 3. Automate Updates: Pinning creates a maintenance overhead, as you will not receive automatic security patches [1][6]. Use dependency management tools like Dependabot or Renovate to monitor, create pull requests for, and update these SHAs automatically [1][6]. 4. Enforce via Organization Policy: Organizations can enforce this security standard by enabling the "Require actions to be pinned to a full-length commit SHA" policy in GitHub settings [1][2][3][7]. This policy applies to the entire dependency tree, including transitive actions (sub-actions), and will cause workflows to fail if unpinned actions are detected [1][2]. 5. Verify Sources: Only pin SHAs from trusted, official action repositories [3][9]. Do not pin SHAs from forks, as these may not be maintained or could be inherently insecure [3][11]. 6. Layered Security: While SHA pinning provides immutability, it should be part of a broader security strategy, including limiting the permissions granted to workflows, using scoped secrets, and monitoring outbound network traffic [12][5][13].
Citations:
- 1: https://www.romainlespinasse.dev/posts/github-actions-commit-sha-pinning/
- 2: https://github.blog/changelog/2025-08-15-github-actions-policy-now-supports-blocking-and-sha-pinning-actions/
- 3: https://docs.github.com/en/actions/reference/security/secure-use
- 4: https://www.stepsecurity.io/blog/pinning-github-actions-for-enhanced-security-a-complete-guide
- 5: https://samsond.github.io/posts/why-tags-fail/
- 6: https://blog.rafaelgss.dev/why-you-should-pin-actions-by-commit-hash
- 7: https://dev.to/ciberconscientes/github-actions-why-you-should-pin-your-actions-to-a-specific-version-1513
- 8: Use Commit SHAs Instead of Version Tags in GitHub Actions for Improved Security and Stability antiwork/gumroad#610
- 9: https://github.com/ihs7/action-sha-pinned-actions
- 10: Pin all GitHub Actions to immutable SHA commits for supply chain security github/gh-aw#5691
- 11: https://stackoverflow.com/questions/78903499/how-do-i-pin-an-action-to-a-specific-sha
- 12: https://dev.to/gitguardian/the-future-of-github-actions-security-and-what-you-can-do-right-now-32nj
- 13: https://gbhackers.com/compromised-github-action/
🌐 Web query:
GitHub Actions persist-credentials false security recommendation
💡 Result:
The persist-credentials input in the actions/checkout action controls whether the repository's authentication token (typically GITHUB_TOKEN) is configured in the local git environment for the runner [1][2][3]. Security Recommendation It is a widely adopted security best practice to set persist-credentials: false in your actions/checkout step unless your workflow specifically requires authenticated git operations (like git push) [4][5]. Reasoning By default (persist-credentials: true), the actions/checkout action stores the authentication token in the local git configuration [1][2][5]. This makes the token accessible to all subsequent steps in the same job [6][5]. If a workflow includes third-party actions or scripts that are compromised or malicious, those processes could potentially access and misuse the persisted token [7][8][5]. Setting this to false removes the credentials from the git config, significantly reducing the attack surface for such scenarios [4][5]. Important Context - Evolution of Security: Recent versions of actions/checkout have improved security by storing credentials in a separate file under $RUNNER_TEMP instead of directly in .git/config, which helps prevent leaks (e.g., via artifact uploads) [1][2][9]. However, the principle of least privilege still makes persist-credentials: false the recommended choice for read-only workflows [5]. - Functional Impact: If your workflow performs authenticated git operations later in the job—such as using git-auto-commit-action—those operations may fail if you set persist-credentials to false [6]. In such cases, you must manually re-authenticate (e.g., using git remote set-url with an explicit token) or keep the default persistence enabled only for specific jobs that require it [6][4]. - Broader Security: While persist-credentials: false mitigates one specific vector, it is not a complete solution. Always ensure your workflow permissions are scoped to the least privileges necessary using the permissions key in your workflow file [7][10].
Citations:
- 1: https://github.com/actions/checkout
- 2: https://github.com/actions/checkout/blob/main/README.md
- 3: https://github.com/marketplace/actions/checkout
- 4: Replace persist-credentials with explicit git re-authentication github/gh-aw#2664
- 5: Add persist-credentials: false to GitHub Actions checkout steps aquasecurity/trivy#10305
- 6: Reconfigure credentials when `persist-credentials: false` is set stefanzweifel/git-auto-commit-action#356
- 7: Remove
persist-credentialsor change the default tofalseactions/checkout#485 - 8: https://gbhackers.com/compromised-github-action/
- 9: Persist creds to a separate file actions/checkout#2286
- 10: https://www.aikido.dev/blog/checklist-github-actions
Pin checkout action to commit SHA and disable persisted credentials across all jobs.
The workflow uses floating tags for actions/checkout@v6 and does not set persist-credentials: false in four places: lines 24–25, 36–37, 50–51, and 70–71. This exposes the repository to supply chain attacks where malicious code could be injected into a mutable tag reference, and allows downstream steps to access the GITHUB_TOKEN unnecessarily.
Pinning to a full-length commit SHA makes the action reference immutable and verifiable. Since all jobs only perform read-only operations (checkout, install, build, lint/test), set persist-credentials: false to prevent compromised actions from accessing or misusing the authentication token.
Suggested hardening patch
- - name: Checkout
- uses: actions/checkout@v6
+ - name: Checkout
+ uses: actions/checkout@<checkout_v6_commit_sha>
+ with:
+ persist-credentials: falseApply this pattern to all four checkout steps (lines 24–25, 36–37, 50–51, and 70–71).
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 50-51: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 51-51: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/main.yml around lines 50 - 52, Replace each floating
checkout action usage in the "Checkout" step with a pinned commit SHA of
actions/checkout and add persist-credentials: false; specifically update every
"Checkout" step that currently uses actions/checkout@v6 (the four occurrences)
to reference the full commit SHA instead of `@v6` and include persist-credentials:
false under that step to disable passing GITHUB_TOKEN to subsequent steps.
| - uses: google-github-actions/release-please-action@v4 | ||
| - name: Release Please | ||
| id: release | ||
| uses: googleapis/release-please-action@v5 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/release.yml | head -80Repository: tada5hi/typeorm-extension
Length of output: 2926
🏁 Script executed:
rg "uses:" .github/workflows/release.yml -nRepository: tada5hi/typeorm-extension
Length of output: 430
🏁 Script executed:
web_search
GitHub Actions pinning to commit SHA supply chain security best practiceRepository: tada5hi/typeorm-extension
Length of output: 165
🌐 Web query:
GitHub Actions pinning commit SHA immutable security supply chain
💡 Result:
GitHub Actions pinning to a full-length commit SHA is the recommended way to mitigate supply-chain risk from malicious or compromised actions, because it makes the action reference an “immutable release” (the exact commit) rather than a mutable tag/branch reference.[1] 1) What SHA pinning fixes (and why tags are riskier) - GitHub’s security guidance states: “Pinning an action to a full-length commit SHA is currently the only way to use an action as an immutable release.”[1] - GitHub also notes that if you pin to a tag, you “risk” the tag being moved or deleted by an attacker with access to the action repository.[1] 2) How SHA pinning helps supply-chain attacks - If an action repository maintainer account is compromised or the maintainer updates/mutates a tag/branch, workflows referencing mutable identifiers could start executing attacker-controlled code. GitHub’s changelog describes this class of risk and recommends SHA pinning to prevent malicious code introduced in new/updated branches/tags from being automatically used.[2] 3) Example syntax - Use: owner/repo@<40-char-full-SHA> (optionally with a comment documenting the intended version). This is the pattern GitHub shows in its “Secure use reference” guidance.[1] 4) Organization-level enforcement (so unpinned actions fail) - GitHub introduced an “allowed actions policy” capability to enforce SHA pinning: administrators can require actions to be pinned to a full commit SHA, and workflows that attempt to use actions that aren’t pinned will fail.[2] 5) Important limitations / remaining supply-chain risk - SHA pinning does not eliminate all supply-chain risk: the pinned action could itself fetch and execute other dependencies (e.g., other actions, packages, container images). SHA pinning prevents the workflow from switching to a different version of the pinned action without changing the SHA, but you still need to manage transitive dependencies and execution behavior. Bottom line - For CI/CD integrity, pin GitHub Actions to full-length commit SHAs and enable/require SHA-pinning policies so tag/branch references are blocked; this is GitHub’s documented approach to using actions as immutable releases.[1][2]
Citations:
- 1: https://docs.github.com/en/actions/reference/security/secure-use
- 2: https://github.blog/changelog/2025-08-15-github-actions-policy-now-supports-blocking-and-sha-pinning-actions/
Pin release workflow actions to immutable commit SHAs for supply-chain hardening.
Lines 33 and 62 reference mutable tags (@v5, @v2) instead of full-length commit SHAs. GitHub's security guidance documents that SHA pinning is the only way to use actions as immutable releases; mutable tags risk being moved or mutated by an attacker with repository access. This is particularly critical in release pipelines and is commonly enforced by organizational pinning policies.
Suggested patch
- uses: googleapis/release-please-action@v5
+ uses: googleapis/release-please-action@<release_please_v5_commit_sha>
...
- uses: tada5hi/monoship@v2
+ uses: tada5hi/monoship@<monoship_v2_commit_sha>🧰 Tools
🪛 zizmor (1.25.2)
[error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release.yml at line 33, The workflow uses mutable action
tags (googleapis/release-please-action@v5 and the other action at `@v2`) which
should be replaced with immutable commit SHAs; update the uses entries to point
to the corresponding full-length commit SHA for googleapis/release-please-action
and the other action (the ad-m/github-push-action referenced at `@v2`) by looking
up the action repositories' stable release commit on GitHub and substituting the
tag (e.g., `@v5/`@v2) with the full 40-character commit SHA so the release
workflow is pinned immutably.
| "lib": ["ESNext"], | ||
| "target": "ES2022", | ||
| "module": "ESNext", | ||
| "moduleResolution": "node10", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
TypeScript 6: what are the behavioral differences between moduleResolution "node10", "node16", "nodenext", and "bundler" for ESM packages using package.json exports?
💡 Result:
In TypeScript 6, the practical behavioral differences among moduleResolution = "node10", "node16", "nodenext", and "bundler" for ESM packages using package.json "exports" mainly come from (a) which Node.js ESM/CJS resolution algorithm is modeled and (b) whether TypeScript enforces Node-style relative-extension rules. 1) moduleResolution: "node10" (legacy) - Models Node.js behavior for versions older than v10, which only support CommonJS require (not modern dual ESM/CJS resolution). TypeScript describes "node10" as for Node.js versions older than v10 and says you probably won’t need it in modern code. [1] - Because it’s the legacy CommonJS-only model, it’s generally not the right choice for ESM packages that rely on modern package.json "exports" + conditional resolution. 2) moduleResolution: "node16" and "nodenext" (Node’s dual ESM/CJS package.json "exports" + conditions) - Both are intended for “modern versions of Node.js” and TypeScript says that, when combined with the corresponding module settings, they “picks the right algorithm for each resolution based on whether Node.js will see an import or require” in emitted output. [2] - For package.json "exports" specifically: TypeScript’s Modules Reference states that when moduleResolution is set to node16, nodenext, or bundler (and resolvePackageJsonExports isn’t disabled), TypeScript “follows Node.js’s package.json "exports" spec when resolving” bare specifiers from a package directory. [3] - Relative import extension behavior: - TypeScript’s module resolution docs state bundler never requires file extensions on relative paths, but Node16/NodeNext do enforce Node ESM extension semantics. [2][4] - A concrete example from the (TypeScript module-resolution) reference: with Node16/NodeNext, an ESM import must include the file extension, e.g. import { x } from "./module.js" is valid, while import { x } from "./module" is an error. [4] - Conditional exports matching (types/import/require): - TypeScript always matches the "types" and "default" conditions (if present) when resolving through conditional "exports". [3] - It also matches versioned "types@{selector}" conditions according to the same rules as typesVersions. [3] - Practical difference between node16 vs nodenext: - The official TSConfig reference groups node16 and nodenext together as modern Node.js modes but doesn’t spell out a separate behavioral distinction in the snippets we found; the key behavioral distinctions that affect package.json exports in practice are shared: Node-like exports-following and relative-extension enforcement. [2][3][4] 3) moduleResolution: "bundler" (export maps supported, but relative-extension rules are relaxed) - TypeScript says bundler “supports package.json "imports" and "exports", but unlike the Node.js resolution modes, bundler never requires file extensions on relative paths in imports.” [2] - For package.json "exports" in the Node.js package sense: Typescript’s Modules Reference likewise says that when moduleResolution is bundler, and resolvePackageJsonExports isn’t disabled, TypeScript follows Node.js’s package.json "exports" spec for resolving from a node_modules lookup. [3] - Relative-extension behavior: - bundler never requires file extensions on relative imports. [2] - This is the main behavioral difference vs node16/nodenext for ESM consumers: Node16/NodeNext will error on extensionless relative imports under ESM-style rules, while bundler does not. [2][4] - TypeScript 6 also clarifies the intent: TypeScript can’t model a scenario where some dependencies are “bundled” and others are left as real external imports; it recommends using bundler resolution only for code paths you expect to be handled by a bundler/runtime consistently. [5] Summary (for ESM packages using package.json "exports") - "node10": legacy CommonJS-only model; not appropriate for modern ESM/exports behavior. [1] - "node16" / "nodenext": both follow Node’s conditional exports when resolving packages, and they enforce Node ESM relative import extensions (e.g. require.js in ESM-style relative imports). [3][2][4] - "bundler": also supports package.json "exports" during resolution, but it relaxes relative import extension requirements (extensionless relative imports are allowed). [2][3] If you tell me your specific scenario (consumer is Node ESM vs bundler like Vite, and an example exports map you’re using), I can say exactly which mode will accept/reject the imports you have.
Citations:
- 1: https://www.typescriptlang.org/tsconfig/moduleResolution.html
- 2: https://typescriptlang.org/tsconfig/moduleResolution.html
- 3: https://www.typescriptlang.org/docs/handbook/modules/reference.html
- 4: https://microsoft-typescript.mintlify.app/compiler/module-resolution
- 5: https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html
Use a modern ESM resolver instead of node10.
moduleResolution: "node10" models Node.js versions older than v10 and only supports CommonJS. It is inappropriate for ESM-only packages relying on modern package.json exports and conditional resolution. Use node16, nodenext (recommended for Node >=22), or bundler instead.
Suggested change
- "moduleResolution": "node10",
+ "moduleResolution": "nodenext",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tsconfig.json` at line 7, The tsconfig.json currently sets
"moduleResolution": "node10", which forces legacy CommonJS-style resolution;
update the moduleResolution compiler option to a modern ESM-aware resolver (for
example "nodenext" or "node16"/"bundler" depending on your Node target) so
package.json exports and conditional resolution work correctly; open
tsconfig.json and replace the "moduleResolution" value from "node10" with the
chosen modern resolver and run a quick build to confirm no import resolution
regressions.
- type.ts: use `import type * as ts from 'typescript'` namespace pattern
so the file stays valid under stricter moduleResolution modes (Copilot).
- file-path.ts: tighten the "already a js extension" check from substring
match to a regex that respects glob brace expansions, so paths like
`my-json.ts` are still transformable while `*.{ts,js}` is still
recognised (CodeRabbit).
- tsdown.config.ts: only externalise `../` CLI imports whose resolved
target actually leaves src/cli/, so internal upward references stay
bundled into the CLI (CodeRabbit).
- package.json: add `"default": "./dist/index.mjs"` to exports so
CJS-via-require(esm) consumers have an explicit fall-through condition
(Copilot).
- migration-guide-v4.md: clarify that the typeorm@^1.0.0 peer-dep bump
is a separate follow-up release (Copilot).
- Strip trailing whitespace from 9 source/test files left by ESLint
--fix during the modernization (Copilot, editorconfig violation).
Filed #1374 for the broader SHA-pinning hardening that applies to all
.github/ workflow files, not just those touched here.
The lock committed in b85dcb9 was incomplete — missing the esbuild@0.28.0 sub-tree (peer of vitest's nested vite). A clean install adds 458 lines and unblocks 'npm ci' on CI.
Summary
Aligns the project with
tada5hi/typescript-templateand prepares for a v4 release alongsidetypeorm@1.0. Build system, test runner, linter, husky, publish action and TypeScript major are all swapped together in one PR.@swc/core+tsc --emitDeclarationOnlyts-jestunplugin-swc@tada5hi/eslint-config-typescript(.eslintrc)@tada5hi/eslint-configv2 (eslint.config.mjs)npx workspaces-publishtada5hi/monoship@v2_/husky.shsource)commitlint.config.jscommitlint.config.mjs\"type\": \"module\", ESM-only distengines.node^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0>=22.0.0typeorm-extension(cjs) +typeorm-extension-esm(mjs)typeorm-extensiononlybuildscriptbuild:types(typecheck) →build:js(tsdown)Build behaviour preserved across the swap
The new
tsdown.config.tsre-implements the two non-trivial rollup behaviours so the dist output stays equivalent:cliRewriteExternalplugin) — relative imports insrc/cli/*that traverse out of the CLI subtree are externalised to\"typeorm-extension\"so the CLI bundle stays small and shares singleton state (DataSource registry, env cache, factory manager) with the library that consumers import directly. The plugin is POSIX-safe across platforms.typeormDeepImportExtensionplugin) — runtimetypeorm/<deep>imports get a.jssuffix so Node's strict ESM resolver finds them (typeorm'spackage.jsonexports map does not expose those subpaths). Uses a negative-lookbehind so already-suffixed paths aren't double-.js.js'd.Vitest specifics
Two non-obvious tuning choices that affect TypeORM behaviour:
unplugin-swc— vitest's defaultoxctransform does not emit decorator metadata. Without swc, TypeORM entity columns lose theirdesign:typereflect-metadata anddataSource.initialize()throws.server.deps.inline: [/locter/]—locter.load()usesawait import(id)for dynamic loading. By default vitest treatsnode_modulesas externals, so thatimport()falls through to Node's native loader, escaping vitest's module graph. The result: entity classes loaded statically (via vitest) and entity classes loaded dynamically through a seeder file (via Node) are different module instances, anddataSource.getRepository(SameNameButDifferentClass)throwsEntityMetadataNotFoundError. Inlining locter forces it through vite-node and restores module identity. Long-term fix belongs in locter — issue to follow.TypeScript 6
CompilerOptions/TypeAcquisitionetc. are no longer top-level exports of thetypescriptpackage; they live under thets.*namespace.src/utils/tsconfig/type.tsswitched toimport type ts from 'typescript'+ts.CompilerOptionsaccordingly.tsconfig.jsonsetsmoduleResolution: node10+ignoreDeprecations: \"6.0\"so type-only deep imports intotypeorm/driver/...still resolve (typeorm's package exports don't expose those subpaths; bundler/nodenext modes refuse them).Four strict options are explicitly relaxed to keep the existing source passing typecheck. Tightening them is intentional follow-up work, not regression scope:
noUncheckedIndexedAccess: falsenoUnusedLocals: falsenoUnusedParameters: falseverbatimModuleSyntax: falseDocs
README.MDanddocs/guide/cli.mdno longer mention the dual binary,cli.cjs, orts-node-esm. Examples updated totsx.docs/guide/migration-guide-v4.mdwalks consumers through the upgrade. Wired into the VitePress sidebar.AGENTS.md,.agents/*) updated to reflect the new toolchain and now include a Documentation Sync Rule requiring future code changes to update both agent docs and user-facing VitePress docs.Verification
npm run build—build:typesclean →build:jsemitsdist/index.mjs(89.69 kB) +dist/index.d.mts(41.41 kB) +bin/cli.mjs(8.59 kB)npm test— 21 test files, 51/51 passingnpm run lint— 0 errorsTest plan
npm pack+ install in a downstream consumer project (verify ESMimport, verifyrequire()via Node 22require(esm), verify CLI binary)Breaking changes (see migration guide)
require(esm)).typeorm-extension-esmbinary removed — usetypeorm-extension(now backed bybin/cli.mjs).^1.0.0alongside this release.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
tsxwith ESM binary instead ofts-node.Chores
bin/cli.mjsentry point.