├── .changeset ├── bob-the-bundler-299-dependencies.md ├── bob-the-bundler-320-dependencies.md ├── bob-the-bundler-323-dependencies.md ├── bob-the-bundler-324-dependencies.md ├── bob-the-bundler-336-dependencies.md ├── bob-the-bundler-411-dependencies.md ├── bright-drinks-provide.md ├── config.json ├── neat-eyes-help.md ├── rare-lemons-count.md ├── thin-mails-clap.md └── wise-tigers-roll.md ├── .eslintrc.cjs ├── .github └── workflows │ ├── ci.yaml │ ├── pr.yml │ └── release.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.mjs ├── .vscode └── settings.json ├── CHANGELOG.md ├── README.md ├── jest-resolver.cjs ├── package.json ├── pnpm-lock.yaml ├── renovate.json ├── serializer.ts ├── src ├── command.ts ├── commands │ ├── bootstrap.ts │ ├── build.ts │ └── check.ts ├── config.ts ├── constants.ts ├── index.ts └── utils │ ├── __fixtures__ │ └── foo │ │ └── index.ts │ ├── get-root-package-json.ts │ ├── get-workspace-package-paths.ts │ ├── get-workspaces.ts │ ├── rewrite-code-imports.spec.ts │ ├── rewrite-code-imports.ts │ └── rewrite-exports.ts ├── test ├── __fixtures__ │ ├── simple-esm-only │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── simple-exports │ │ ├── package.json │ │ ├── src │ │ │ ├── index.ts │ │ │ └── sub │ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── simple-monorepo-pnpm │ │ ├── package.json │ │ ├── packages │ │ │ ├── a │ │ │ │ ├── package.json │ │ │ │ └── src │ │ │ │ │ └── index.ts │ │ │ ├── b │ │ │ │ ├── package.json │ │ │ │ └── src │ │ │ │ │ ├── foo.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── log-the-world.ts │ │ │ └── c │ │ │ │ ├── package.json │ │ │ │ └── src │ │ │ │ └── index.ts │ │ ├── pnpm-workspace.yaml │ │ └── tsconfig.json │ ├── simple-monorepo │ │ ├── package.json │ │ ├── packages │ │ │ ├── a │ │ │ │ ├── package.json │ │ │ │ └── src │ │ │ │ │ └── index.ts │ │ │ ├── b │ │ │ │ ├── package.json │ │ │ │ └── src │ │ │ │ │ ├── foo.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── log-the-world.ts │ │ │ └── c │ │ │ │ ├── package.json │ │ │ │ └── src │ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── simple-types-only │ │ ├── README.md │ │ ├── foo.json │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── simple │ │ ├── README.md │ │ ├── foo.json │ │ ├── package.json │ │ ├── src │ │ │ ├── file-that-throws.ts │ │ │ ├── index.ts │ │ │ └── style.css │ │ └── tsconfig.json │ └── tsconfig-build-json │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ └── index.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json ├── exports.test.ts ├── integration.spec.ts ├── jest-resolver.test.ts └── ts-tests │ ├── .gitignore │ ├── fixture.ts │ ├── package.json │ ├── run-tests.mjs │ ├── tsconfig.commonjs-node.json │ ├── tsconfig.commonjs-node16.json │ ├── tsconfig.commonjs-nodenext.json │ ├── tsconfig.esnext-node.json │ ├── tsconfig.node16-node16.json │ └── tsconfig.nodenext-nodenext.json ├── tsconfig.json └── vite.config.ts /.changeset/bob-the-bundler-299-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | "bob-the-bundler": patch 3 | --- 4 | dependencies updates: 5 | - Updated dependency [`globby@^14.0.0` ↗︎](https://www.npmjs.com/package/globby/v/14.0.0) (from `^13.1.3`, in `dependencies`) 6 | -------------------------------------------------------------------------------- /.changeset/bob-the-bundler-320-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | "bob-the-bundler": patch 3 | --- 4 | dependencies updates: 5 | - Updated dependency [`execa@^7.2.0` ↗︎](https://www.npmjs.com/package/execa/v/7.2.0) (from `7.1.1`, in `dependencies`) 6 | -------------------------------------------------------------------------------- /.changeset/bob-the-bundler-323-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | "bob-the-bundler": patch 3 | --- 4 | dependencies updates: 5 | - Updated dependency [`execa@^9.0.0` ↗︎](https://www.npmjs.com/package/execa/v/9.0.0) (from `^7.2.0`, in `dependencies`) 6 | -------------------------------------------------------------------------------- /.changeset/bob-the-bundler-324-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | "bob-the-bundler": patch 3 | --- 4 | dependencies updates: 5 | - Updated dependency [`p-limit@^6.0.0` ↗︎](https://www.npmjs.com/package/p-limit/v/6.0.0) (from `^4.0.0`, in `dependencies`) 6 | -------------------------------------------------------------------------------- /.changeset/bob-the-bundler-336-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | "bob-the-bundler": patch 3 | --- 4 | dependencies updates: 5 | - Added dependency [`get-tsconfig@^4.8.1` ↗︎](https://www.npmjs.com/package/get-tsconfig/v/4.8.1) (to `dependencies`) 6 | -------------------------------------------------------------------------------- /.changeset/bob-the-bundler-411-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | "bob-the-bundler": patch 3 | --- 4 | dependencies updates: 5 | - Updated dependency [`yargs@^18.0.0` ↗︎](https://www.npmjs.com/package/yargs/v/18.0.0) (from `^17.6.2`, in `dependencies`) 6 | -------------------------------------------------------------------------------- /.changeset/bright-drinks-provide.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'bob-the-bundler': major 3 | --- 4 | 5 | Drop "module" package.json field 6 | 7 | The field was just a proposal and was never officially (and fully) defined by Node. Node instead uses (and recommends) the ["exports" field](https://nodejs.org/api/packages.html#exports). 8 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.1.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "kamilkisiela/bob" }], 4 | "commit": false, 5 | "linked": [], 6 | "access": "restricted", 7 | "baseBranch": "master", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [], 10 | "snapshot": { 11 | "useCalculatedVersion": true, 12 | "prereleaseTemplate": "{tag}-{datetime}-{commit}" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.changeset/neat-eyes-help.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'bob-the-bundler': patch 3 | --- 4 | 5 | Check whether package exports map files exist 6 | -------------------------------------------------------------------------------- /.changeset/rare-lemons-count.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'bob-the-bundler': major 3 | --- 4 | 5 | Drop "typescript" package.json field 6 | -------------------------------------------------------------------------------- /.changeset/thin-mails-clap.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'bob-the-bundler': major 3 | --- 4 | 5 | Build modern CommonJS and support package.json exports 6 | -------------------------------------------------------------------------------- /.changeset/wise-tigers-roll.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'bob-the-bundler': major 3 | --- 4 | 5 | "main" package.json field matches the location of "type" output 6 | 7 | > The "type" field defines the module format that Node.js uses for all .js files that have that package.json file as their nearest parent. 8 | > 9 | > Files ending with .js are loaded as ES modules when the nearest parent package.json file contains a top-level field "type" with a value of "module". 10 | > 11 | > If the nearest parent package.json lacks a "type" field, or contains "type": "commonjs", .js files are treated as CommonJS. If the volume root is reached and no package.json is found, .js files are treated as CommonJS. 12 | 13 | _[Node documentation](https://nodejs.org/api/packages.html#type)_ 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | parserOptions: { 5 | ecmaVersion: 2020, 6 | }, 7 | overrides: [ 8 | { 9 | files: 'src/**/*.ts', 10 | excludedFiles: ['*.spec.ts'], 11 | plugins: ['import'], 12 | rules: { 13 | 'import/extensions': ['error', 'ignorePackages'], 14 | }, 15 | }, 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | jobs: 11 | unit: 12 | name: unit / ${{matrix.os}} / node v${{matrix.node_version}} 13 | runs-on: ${{matrix.os}} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest] 18 | node_version: [20, 22, 24] 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 22 | - name: Set up env 23 | uses: the-guild-org/shared-config/setup@v1 24 | with: 25 | node-version: ${{ matrix.node_version }} 26 | - name: Build dependencies 27 | run: pnpm run build 28 | - name: Run Tests 29 | run: pnpm run test 30 | - name: Run TS Smoke Tests 31 | run: pnpm run test:ts 32 | - name: Lint Prettier 33 | run: pnpm run lint:prettier 34 | - name: Lint ESLint 35 | run: pnpm run lint 36 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: pr 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | release: 9 | if: ${{ github.actor != 'dependabot[bot]' && github.actor != 'dependabot' }} 10 | uses: the-guild-org/shared-config/.github/workflows/release-snapshot.yml@main 11 | with: 12 | packageManager: 'pnpm' 13 | npmTag: alpha 14 | buildScript: build 15 | nodeVersion: 18 16 | secrets: 17 | githubToken: ${{ secrets.GITHUB_TOKEN }} 18 | npmToken: ${{ secrets.NODE_AUTH_TOKEN }} 19 | 20 | dependencies: 21 | uses: the-guild-org/shared-config/.github/workflows/changesets-dependencies.yaml@main 22 | with: 23 | packageManager: 'pnpm' 24 | secrets: 25 | githubToken: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | stable: 9 | uses: the-guild-org/shared-config/.github/workflows/release-stable.yml@main 10 | with: 11 | packageManager: 'pnpm' 12 | releaseScript: release 13 | nodeVersion: 18 14 | secrets: 15 | githubToken: ${{ secrets.GITHUB_TOKEN }} 16 | npmToken: ${{ secrets.NODE_AUTH_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store/ 2 | .idea/ 3 | node_modules/ 4 | coverage/ 5 | dist/ 6 | public/ 7 | *.map 8 | .bob/ 9 | *.log 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .bob/ 3 | test/ts-tests/fixture.js 4 | jest.resolver.cjs 5 | pnpm-lock.yaml 6 | .changeset/ 7 | -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | export { default } from '@theguild/prettier-config'; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 7.0.1 4 | 5 | ### Patch Changes 6 | 7 | - [#261](https://github.com/kamilkisiela/bob/pull/261) 8 | [`2af3e24`](https://github.com/kamilkisiela/bob/commit/2af3e24df964e7926fdea8182d70a09dc8b99e82) 9 | Thanks [@gilgardosh](https://github.com/gilgardosh)! - Remove engines.pnpm/yarn/npm in the the 10 | output package.json 11 | 12 | ## 7.0.0 13 | 14 | ### Major Changes 15 | 16 | - [#247](https://github.com/kamilkisiela/bob/pull/247) 17 | [`0a5ff75`](https://github.com/kamilkisiela/bob/commit/0a5ff7507dfc6a569fc3721bc99db45330f46517) 18 | Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Remove the `bob runify` command. 19 | 20 | - [`87da001`](https://github.com/kamilkisiela/bob/commit/87da00194ad616a31f4788c63a7ebca226a2b85f) 21 | Thanks [@n1ru4l](https://github.com/n1ru4l)! - Require Node 16 22 | 23 | - [#248](https://github.com/kamilkisiela/bob/pull/248) 24 | [`20117cd`](https://github.com/kamilkisiela/bob/commit/20117cd209400f1ab61c6df94c937c77bf8db7ef) 25 | Thanks [@n1ru4l](https://github.com/n1ru4l)! - Require `engines.node` entry with `bob check` 26 | command. 27 | 28 | - [#253](https://github.com/kamilkisiela/bob/pull/253) 29 | [`f74688c`](https://github.com/kamilkisiela/bob/commit/f74688c024ab8bc6329a745a6c5c25114fdab454) 30 | Thanks [@n1ru4l](https://github.com/n1ru4l)! - Drop support for typescript 4. 31 | 32 | ### Patch Changes 33 | 34 | - [#201](https://github.com/kamilkisiela/bob/pull/201) 35 | [`edf3301`](https://github.com/kamilkisiela/bob/commit/edf33014afa82c065ef4900ccbbc414cc864b1b4) 36 | Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 37 | 38 | - Updated dependency [`execa@7.1.1` ↗︎](https://www.npmjs.com/package/execa/v/7.1.1) (from 39 | `6.1.0`, in `dependencies`) 40 | 41 | - [#229](https://github.com/kamilkisiela/bob/pull/229) 42 | [`2519f67`](https://github.com/kamilkisiela/bob/commit/2519f6784486d5451873f51940084773ff1dd768) 43 | Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 44 | 45 | - Updated dependency [`typescript@^5.0.0` ↗︎](https://www.npmjs.com/package/typescript/v/4.7.4) 46 | (from `^4.7.4`, in `peerDependencies`) 47 | 48 | - [#244](https://github.com/kamilkisiela/bob/pull/244) 49 | [`f7b5824`](https://github.com/kamilkisiela/bob/commit/f7b58246e0aeb00f12b8238d30c986e00aa92891) 50 | Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 51 | 52 | - Updated dependency [`consola@^3.0.0` ↗︎](https://www.npmjs.com/package/consola/v/3.0.0) (from 53 | `^2.15.3`, in `dependencies`) 54 | 55 | - [#247](https://github.com/kamilkisiela/bob/pull/247) 56 | [`0a5ff75`](https://github.com/kamilkisiela/bob/commit/0a5ff7507dfc6a569fc3721bc99db45330f46517) 57 | Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - dependencies updates: 58 | 59 | - Removed dependency 60 | [`@vercel/ncc@^0.36.0` ↗︎](https://www.npmjs.com/package/@vercel/ncc/v/0.36.0) (from 61 | `dependencies`) 62 | - Removed dependency 63 | [`dependency-graph@^0.11.0` ↗︎](https://www.npmjs.com/package/dependency-graph/v/0.11.0) (from 64 | `dependencies`) 65 | - Removed dependency [`tsup@^6.5.0` ↗︎](https://www.npmjs.com/package/tsup/v/6.5.0) (from 66 | `dependencies`) 67 | 68 | ## 6.0.0 69 | 70 | ### Major Changes 71 | 72 | - [#202](https://github.com/kamilkisiela/bob/pull/202) 73 | [`2236863`](https://github.com/kamilkisiela/bob/commit/223686314b011c54fd1930a2555eb6dab7ccd9b5) 74 | Thanks [@enisdenjo](https://github.com/enisdenjo)! - Custom tsconfig path for the build command, 75 | default to `tsconfig.build.json` and fallback to `tsconfig.json`. 76 | 77 | - [#203](https://github.com/kamilkisiela/bob/pull/203) 78 | [`3b7efdc`](https://github.com/kamilkisiela/bob/commit/3b7efdc8cea6f01f9a4640f3bf7e90da5dac7d05) 79 | Thanks [@ardatan](https://github.com/ardatan)! - **Breaking** `jest-resolver.js` renamed to 80 | `jest-resolver.cjs` because Bob package is an ESM package. 81 | 82 | Please make sure to adjust your `jest.config.js`. 83 | 84 | ```diff 85 | - resolver: 'bob-the-bundler/jest-resolver.js' 86 | + resolver: 'bob-the-bundler/jest-resolver.cjs' 87 | ``` 88 | 89 | ### Patch Changes 90 | 91 | - [#214](https://github.com/kamilkisiela/bob/pull/214) 92 | [`1567b4d`](https://github.com/kamilkisiela/bob/commit/1567b4d8e667286adc388cb2fac78bb92dd2b053) 93 | Thanks [@enisdenjo](https://github.com/enisdenjo)! - Include empty cjs/esm entry points for 94 | types-only packages 95 | 96 | - [#37](https://github.com/kamilkisiela/bob/pull/37) 97 | [`c912002`](https://github.com/kamilkisiela/bob/commit/c912002e6625d9fb5d56b7fb3dbd79ac80018aec) 98 | Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - better windows support for paths in the 99 | runify command. 100 | 101 | - [#210](https://github.com/kamilkisiela/bob/pull/210) 102 | [`ad9fb40`](https://github.com/kamilkisiela/bob/commit/ad9fb4044ac59c6962203f03f1dfbc0ec0df271f) 103 | Thanks [@enisdenjo](https://github.com/enisdenjo)! - exports field should exist in types-only 104 | builds 105 | 106 | ## 5.0.1 107 | 108 | ### Patch Changes 109 | 110 | - [#183](https://github.com/kamilkisiela/bob/pull/183) 111 | [`d6f981f`](https://github.com/kamilkisiela/bob/commit/d6f981f6d50608d4954c4a421b19b7cfe8f4b076) 112 | Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 113 | 114 | - Updated dependency 115 | [`resolve.exports@^2.0.0` ↗︎](https://www.npmjs.com/package/resolve.exports/v/2.0.0) (from 116 | `^1.1.0`, in `dependencies`) 117 | 118 | - [#187](https://github.com/kamilkisiela/bob/pull/187) 119 | [`d7a8fd5`](https://github.com/kamilkisiela/bob/commit/d7a8fd54fb22973c3da426a0d907064b4b62ac40) 120 | Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 121 | 122 | - Updated dependency [`mkdirp@^2.0.0` ↗︎](https://www.npmjs.com/package/mkdirp/v/2.0.0) (from 123 | `^1.0.4`, in `dependencies`) 124 | 125 | - [#188](https://github.com/kamilkisiela/bob/pull/188) 126 | [`4f9c7d4`](https://github.com/kamilkisiela/bob/commit/4f9c7d453911c252a813b084de5d183d994ae287) 127 | Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 128 | 129 | - Updated dependency 130 | [`resolve.exports@^2.0.0` ↗︎](https://www.npmjs.com/package/resolve.exports/v/2.0.0) (from 131 | `^1.1.0`, in `dependencies`) 132 | 133 | - [#156](https://github.com/kamilkisiela/bob/pull/156) 134 | [`57cd333`](https://github.com/kamilkisiela/bob/commit/57cd333f9fe04026295cf076b10c9b2f7295f04d) 135 | Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Use Node18 as target in runfiy 136 | 137 | - [#191](https://github.com/kamilkisiela/bob/pull/191) 138 | [`5b5f85e`](https://github.com/kamilkisiela/bob/commit/5b5f85e95adc1e858c1a628e2eb1f88febd7247c) 139 | Thanks [@adriencohen](https://github.com/adriencohen)! - Include peerDependenciesMeta in build 140 | package.json 141 | 142 | ## 5.0.0 143 | 144 | ### Major Changes 145 | 146 | - [#167](https://github.com/kamilkisiela/bob/pull/167) 147 | [`d095697`](https://github.com/kamilkisiela/bob/commit/d0956978c70419c9f5203b3d8f85028589a51e66) 148 | Thanks [@B2o5T](https://github.com/B2o5T)! - bundle bob in esm instead cjs 149 | 150 | ### Minor Changes 151 | 152 | - [#160](https://github.com/kamilkisiela/bob/pull/160) 153 | [`9ce6e27`](https://github.com/kamilkisiela/bob/commit/9ce6e279f362b78a6e925f1e038a1b611931ad55) 154 | Thanks [@B2o5T](https://github.com/B2o5T)! - Support pnpm workspaces from `pnpm-workspace.yaml`.. 155 | Throw an error in case both `pnpm-workspace.yaml` and `package.json#workspaces` fields exist. Add 156 | missing dependency `execa`. Cleanup and remove unused dependencies. 157 | 158 | ### Patch Changes 159 | 160 | - [#145](https://github.com/kamilkisiela/bob/pull/145) 161 | [`a4ebd65`](https://github.com/kamilkisiela/bob/commit/a4ebd655dd76c8703edb64605f0de2dd58870263) 162 | Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 163 | 164 | - Updated dependency 165 | [`rollup-plugin-typescript2@^0.34.0` ↗︎](https://www.npmjs.com/package/rollup-plugin-typescript2/v/0.34.0) 166 | (from `^0.33.0`, in `dependencies`) 167 | 168 | - [#150](https://github.com/kamilkisiela/bob/pull/150) 169 | [`e18b500`](https://github.com/kamilkisiela/bob/commit/e18b500cc3807d7a76ad38639ef1cc59c1eab987) 170 | Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 171 | 172 | - Updated dependency 173 | [`@vercel/ncc@^0.36.0` ↗︎](https://www.npmjs.com/package/@vercel/ncc/v/0.36.0) (from `^0.34.0`, 174 | in `dependencies`) 175 | 176 | - [#154](https://github.com/kamilkisiela/bob/pull/154) 177 | [`616e125`](https://github.com/kamilkisiela/bob/commit/616e1254ba3b3db638a2030abd244a50ff6ddb2b) 178 | Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 179 | 180 | - Updated dependency 181 | [`@rollup/plugin-json@^6.0.0` ↗︎](https://www.npmjs.com/package/@rollup/plugin-json/v/6.0.0) 182 | (from `^4.1.0`, in `dependencies`) 183 | 184 | - [#167](https://github.com/kamilkisiela/bob/pull/167) 185 | [`d095697`](https://github.com/kamilkisiela/bob/commit/d0956978c70419c9f5203b3d8f85028589a51e66) 186 | Thanks [@B2o5T](https://github.com/B2o5T)! - dependencies updates: 187 | - Updated dependency [`execa@6.1.0` ↗︎](https://www.npmjs.com/package/execa/v/6.1.0) (from 188 | `5.1.1`, in `dependencies`) 189 | - Updated dependency [`globby@^13.1.3` ↗︎](https://www.npmjs.com/package/globby/v/13.1.3) (from 190 | `^11.0.0`, in `dependencies`) 191 | - Updated dependency [`p-limit@^4.0.0` ↗︎](https://www.npmjs.com/package/p-limit/v/4.0.0) (from 192 | `^3.1.0`, in `dependencies`) 193 | - Updated dependency [`yargs@^17.6.2` ↗︎](https://www.npmjs.com/package/yargs/v/17.6.2) (from 194 | `^17.5.1`, in `dependencies`) 195 | - Updated dependency [`zod@^3.20.2` ↗︎](https://www.npmjs.com/package/zod/v/3.20.2) (from 196 | `^3.17.3`, in `dependencies`) 197 | 198 | ## 4.1.1 199 | 200 | ### Patch Changes 201 | 202 | - [#151](https://github.com/kamilkisiela/bob/pull/151) 203 | [`4695d0b`](https://github.com/kamilkisiela/bob/commit/4695d0b4f1bf849d87f626f97445b9b9b5bb9cba) 204 | Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - dependencies updates: 205 | 206 | - Updated dependency [`tsup@^6.5.0` ↗︎](https://www.npmjs.com/package/tsup/v/6.5.0) (from 207 | `^5.11.6`, in `dependencies`) 208 | 209 | - [#151](https://github.com/kamilkisiela/bob/pull/151) 210 | [`4695d0b`](https://github.com/kamilkisiela/bob/commit/4695d0b4f1bf849d87f626f97445b9b9b5bb9cba) 211 | Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Support TypeScript 4.9 and satisfies 212 | operator 213 | 214 | ## 4.1.0 215 | 216 | ### Minor Changes 217 | 218 | - [#123](https://github.com/kamilkisiela/bob/pull/123) 219 | [`b68da59`](https://github.com/kamilkisiela/bob/commit/b68da59ef41d4d66b9c4ec5d7da1a3550b5b58b7) 220 | Thanks [@enisdenjo](https://github.com/enisdenjo)! - better performance by incrementally building 221 | only packages that had changes 222 | 223 | ### Patch Changes 224 | 225 | - [#129](https://github.com/kamilkisiela/bob/pull/129) 226 | [`cd16844`](https://github.com/kamilkisiela/bob/commit/cd16844210db99a3e74fa95c3311fb644ad48594) 227 | Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 228 | 229 | - Updated dependency 230 | [`rollup-plugin-typescript2@^0.33.0` ↗︎](https://www.npmjs.com/package/rollup-plugin-typescript2/v/null) 231 | (from `^0.32.1`, in `dependencies`) 232 | 233 | - [#144](https://github.com/kamilkisiela/bob/pull/144) 234 | [`76fd23c`](https://github.com/kamilkisiela/bob/commit/76fd23c0f0887a8e324a92b0cff705214e1883bc) 235 | Thanks [@enisdenjo](https://github.com/enisdenjo)! - Correct package.json for types-only packages 236 | 237 | ## 4.0.0 238 | 239 | ### Major Changes 240 | 241 | - f685733: Change the exports map again, to please TypeScript commonjs :) 242 | 243 | This is a major breaking change as it requires adjusting your `package.json` exports map. 244 | 245 | The `require` entries file extension must be changed from `.d.ts` to `.d.cts`. 246 | 247 | ```diff 248 | { 249 | "exports": { 250 | ".": { 251 | "require": { 252 | - "types": "./dist/typings/index.d.ts", 253 | + "types": "./dist/typings/index.d.cts" 254 | } 255 | } 256 | } 257 | } 258 | ``` 259 | 260 | ### Minor Changes 261 | 262 | - 14fa965: Disable commonjs output via package.json 263 | 264 | ```json 265 | { 266 | "name": "my-package", 267 | "bob": { 268 | "commonjs": false 269 | } 270 | } 271 | ``` 272 | 273 | - b8db426: Ignore `__tests__` and `__testUtils__` from bundling 274 | 275 | ## 3.0.5 276 | 277 | ### Patch Changes 278 | 279 | - 16952de: Use correct path for checking file existence in exports map. 280 | 281 | ## 3.0.4 282 | 283 | ### Patch Changes 284 | 285 | - e096322: Replace babel based export/import source location transform with an improved regex based 286 | transform that reduces code change noise and preserves the original formatting. 287 | 288 | ## 3.0.3 289 | 290 | ### Patch Changes 291 | 292 | - 0c36290: Support type imports/exports. 293 | 294 | ## 3.0.2 295 | 296 | ### Patch Changes 297 | 298 | - b6976a9: Use a more reliable import/export transform for the bootstrap command 299 | 300 | ## 3.0.1 301 | 302 | ### Patch Changes 303 | 304 | - 086c1a8: Run typescript tsc commands in sequence instead of in parallel to avoid race conditions 305 | where the `.bob/cjs` or `.bob/esm` folder is missing. 306 | 307 | ## 3.0.0 308 | 309 | ### Major Changes 310 | 311 | - 0f3f9ac: Remove the `flat-pack`, `validate` and `run` commands that are no longer maintained and 312 | used. 313 | - 1605028: Remove the global config. Please add `bob: false` to the individual `package.json` 314 | workspaces that should not be processed by bob. 315 | 316 | This is the new config format for bob. 317 | 318 | ```ts 319 | type BobConfig = 320 | /** completely disable bob for this package. */ 321 | | false 322 | | { 323 | /** Whether the package should be built. */ 324 | build?: 325 | | false 326 | | { 327 | /** Files to copy from the package root to dist */ 328 | copy?: Array 329 | } 330 | /** Whether the package should be checked. */ 331 | check?: 332 | | false 333 | | { 334 | /** Exports within the package that should not be checked. */ 335 | skip?: Array 336 | } 337 | } 338 | ``` 339 | 340 | ## 2.0.0 341 | 342 | ### Major Changes 343 | 344 | - ae0b4b2: Require specifying typescript fields in the package.json exports map for typescript 345 | modules support. Learn more on the 346 | [TypeScript 4.7 release notes](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#package-json-exports-imports-and-self-referencing). 347 | 348 | ### Minor Changes 349 | 350 | - 0942e1c: unpin and update dependencies 351 | - 59ead17: remove the `--single` flag. The value is now derived from the `package.json` `workspaces` 352 | property. If your workspace is configured properly this is not a breaking change. 353 | 354 | ## v1.7.3 355 | 356 | - Run `next start` directly from nextjs's lib, not CLI code. 357 | 358 | ## v1.7.2 359 | 360 | - Do not add `require` automatically (breaking but we use it only internally) 361 | - Introduce `banner` to add a banner to the generated files (runify + tsup only) 362 | 363 | ## v1.7.1 364 | 365 | - Adds `require` to ESM output (runify with tsup enabled) 366 | 367 | ## v1.7.0 368 | 369 | - Detect `"type": "module"` in `runify` command to decide on ESM vs CJS output (works only with TSUP 370 | enabled). 371 | - Keep the original value of `type` when rewriting `package.json` (runify command) 372 | 373 | ## v1.6.2 374 | 375 | - Make config optional 376 | 377 | ## v1.6.1 378 | 379 | - Do not copy `.next/cache/webpack` 380 | 381 | ## v1.6.0 382 | 383 | - Support tsup in `runify` command 384 | - Introduce `tag` for `runify` command 385 | - Support `--single` run for `runify` command 386 | 387 | ## 1.4.1 - 1.6.0 388 | 389 | - a lot of good things 390 | 391 | ## v1.4.1 392 | 393 | - fix typo `.msj` (should be `.mjs`) 394 | 395 | ## v1.4.0 396 | 397 | - Support multiple dist configurations and ESM [#13](https://github.com/kamilkisiela/bob/pull/13) 398 | 399 | ## v1.3.0 400 | 401 | - added `runify` command to produce stanalone (node_modules included) bundles and to make them 402 | executable with `node index.js` (supports NextJS) 403 | 404 | ## 1.2.1 405 | 406 | ... 407 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bob (The Compiler) 2 | 3 | Bob is the TypeScript build, bundle and verification tool used by almost all 4 | [The Guild](https://the-guild.dev) open source projects. 5 | 6 | Scope: 7 | 8 | - **Build**: Build ESM and CommonJS compatible npm packages 9 | - **Verify**: Ensure all ESM and CommonJS imports within an npm package are usable 10 | 11 | ## Requirements 12 | 13 | - Yarn workspace or single package project 14 | - TypeScript 15 | - It's so strict you shouldn't use it! 16 | 17 | ## Setup 18 | 19 | Setting up bob is currently undocumented. You can check 20 | [GraphQL Code Generator](https://github.com/dotansimha/graphql-code-generator) repository (or any 21 | other The Guild repository). 22 | 23 | ## Configuration 24 | 25 | You can add a `bob` key to each `package.json`. 26 | 27 | **Disable bob for a single package** 28 | 29 | ```jsonc 30 | { 31 | "name": "graphql-lfg", 32 | "bob": false // exclude a single package from all things bob related 33 | } 34 | ``` 35 | 36 | **Disable build for a single package** 37 | 38 | ```json 39 | { 40 | "name": "graphql-lfg", 41 | "bob": { 42 | "build": false 43 | } 44 | } 45 | ``` 46 | 47 | **Disable check for a single package** 48 | 49 | ```json 50 | { 51 | "name": "graphql-lfg", 52 | "bob": { 53 | "check": false 54 | } 55 | } 56 | ``` 57 | 58 | **Disable check for a single export in a package** 59 | 60 | ```json 61 | { 62 | "name": "graphql-lfg", 63 | "bob": { 64 | "check": { 65 | "skip": ["./foo"] 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | ## Usage 72 | 73 | ```bash 74 | $ bob build 75 | $ bob check 76 | ``` 77 | -------------------------------------------------------------------------------- /jest-resolver.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** This is module.exports for a reason. Please don't change it. */ 4 | /** 5 | * @param {string} path 6 | * @param {import("jest-resolve").ResolverOptions} options 7 | */ 8 | module.exports = function jestResolver(path, options) { 9 | /** 10 | * Jest does not like .js extensions when not running Jest in EXPERIMENTAL AND TOTALLY UNSTABLE YELL-AT-YA esm mode. 11 | * We rewrite it so we can run the tests. 12 | * Once we can run everything in ESM only, we can delete this. 13 | */ 14 | if ( 15 | path.startsWith('.') && 16 | path.endsWith('.js') && 17 | // we don't want to rewrite node_modules, only the code we own! 18 | options.basedir.includes('node_modules') === false 19 | ) { 20 | path = path.replace(/\.js$/, ''); 21 | } 22 | 23 | // Call the defaultResolver, so we leverage its cache, error handling, etc. 24 | return options.defaultResolver(path, { 25 | ...options, 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bob-the-bundler", 3 | "version": "7.0.1", 4 | "type": "module", 5 | "description": "Bob The Bundler!", 6 | "repository": { 7 | "url": "git@github.com:kamilkisiela/bob.git", 8 | "type": "git" 9 | }, 10 | "author": { 11 | "email": "kamil.kisiela@gmail.com", 12 | "name": "Kamil Kisiela", 13 | "url": "https://github.com/kamilkisiela" 14 | }, 15 | "license": "MIT", 16 | "packageManager": "pnpm@10.11.1", 17 | "engines": { 18 | "node": ">=16", 19 | "pnpm": ">=8" 20 | }, 21 | "bin": { 22 | "bob": "dist/index.js" 23 | }, 24 | "main": "dist/index.js", 25 | "exports": { 26 | "./jest-resolver": { 27 | "default": "./jest-resolver.cjs" 28 | }, 29 | "./package.json": "./package.json" 30 | }, 31 | "files": [ 32 | "dist", 33 | "jest-resolver.cjs", 34 | "README.md" 35 | ], 36 | "scripts": { 37 | "build": "rimraf dist && tsc", 38 | "lint": "cross-env \"ESLINT_USE_FLAT_CONFIG=false\" eslint --cache --cache-location node_modules/.cache/.eslintcache --ignore-path .gitignore .", 39 | "lint:prettier": "prettier --cache --check .", 40 | "prepublish": "pnpm build", 41 | "prerelease": "pnpm build", 42 | "prettier": "prettier --cache --write --list-different .", 43 | "release": "changeset publish", 44 | "test": "vitest .", 45 | "test:ts": "node test/ts-tests/run-tests.mjs" 46 | }, 47 | "peerDependencies": { 48 | "typescript": "^5.0.0" 49 | }, 50 | "dependencies": { 51 | "consola": "^3.0.0", 52 | "execa": "^9.0.0", 53 | "fs-extra": "^11.1.0", 54 | "get-tsconfig": "^4.8.1", 55 | "globby": "^14.0.0", 56 | "js-yaml": "^4.1.0", 57 | "lodash.get": "^4.4.2", 58 | "p-limit": "^6.0.0", 59 | "resolve.exports": "^2.0.0", 60 | "tslib": "^2.0.0", 61 | "yargs": "^18.0.0", 62 | "zod": "^3.20.2" 63 | }, 64 | "devDependencies": { 65 | "@changesets/changelog-github": "0.5.1", 66 | "@changesets/cli": "2.29.4", 67 | "@ianvs/prettier-plugin-sort-imports": "4.4.2", 68 | "@theguild/prettier-config": "3.0.1", 69 | "@types/fs-extra": "11.0.4", 70 | "@types/js-yaml": "4.0.9", 71 | "@types/lodash.get": "4.4.9", 72 | "@types/node": "22.15.29", 73 | "@types/yargs": "17.0.33", 74 | "@typescript-eslint/parser": "8.33.1", 75 | "cross-env": "7.0.3", 76 | "eslint": "9.28.0", 77 | "eslint-plugin-import": "2.31.0", 78 | "jest-resolve": "29.7.0", 79 | "jest-snapshot-serializer-raw": "2.0.0", 80 | "prettier": "3.5.3", 81 | "prettier-plugin-pkg": "0.19.1", 82 | "prettier-plugin-sh": "0.17.4", 83 | "rimraf": "6.0.1", 84 | "typescript": "5.8.3", 85 | "vitest": "3.2.1" 86 | }, 87 | "publishConfig": { 88 | "access": "public" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>the-guild-org/shared-config:renovate"], 4 | "automerge": true, 5 | "rebaseWhen": "conflicted", 6 | "major": { 7 | "automerge": false 8 | }, 9 | "lockFileMaintenance": { 10 | "enabled": true, 11 | "automerge": true 12 | }, 13 | "packageRules": [ 14 | { 15 | "matchPackagePatterns": ["*"], 16 | "matchUpdateTypes": ["minor", "patch"], 17 | "groupName": "all non-major dependencies", 18 | "groupSlug": "all-minor-patch" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /serializer.ts: -------------------------------------------------------------------------------- 1 | import rawSnapshotSerializer from 'jest-snapshot-serializer-raw/always'; 2 | import { expect } from 'vitest'; 3 | 4 | expect.addSnapshotSerializer(rawSnapshotSerializer); 5 | -------------------------------------------------------------------------------- /src/command.ts: -------------------------------------------------------------------------------- 1 | import { type ConsolaInstance } from 'consola'; 2 | import { type CommandModule } from 'yargs'; 3 | 4 | export { CommandModule as Command }; 5 | 6 | export interface CommandAPI { 7 | reporter: ConsolaInstance; 8 | } 9 | 10 | export type CommandFactory = (api: CommandAPI) => CommandModule; 11 | 12 | export function createCommand(factory: CommandFactory) { 13 | return factory; 14 | } 15 | -------------------------------------------------------------------------------- /src/commands/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fse from 'fs-extra'; 3 | import { globby } from 'globby'; 4 | import pLimit from 'p-limit'; 5 | import { createCommand } from '../command.js'; 6 | import { buildArtifactDirectories } from '../constants.js'; 7 | import { getRootPackageJSON } from '../utils/get-root-package-json.js'; 8 | import { getWorkspacePackagePaths } from '../utils/get-workspace-package-paths.js'; 9 | import { getWorkspaces } from '../utils/get-workspaces.js'; 10 | import { rewriteCodeImports } from '../utils/rewrite-code-imports.js'; 11 | 12 | /** The default bob fields that should be within a package.json */ 13 | export const presetFieldsDual = Object.freeze({ 14 | type: 'module', 15 | main: 'dist/esm/index.js', 16 | typings: 'dist/typings/index.d.ts', 17 | exports: { 18 | '.': { 19 | require: { 20 | types: './dist/typings/index.d.cts', 21 | default: './dist/cjs/index.js', 22 | }, 23 | import: { 24 | types: './dist/typings/index.d.ts', 25 | default: './dist/esm/index.js', 26 | }, 27 | /** without this default (THAT MUST BE LAST!!!) webpack will have a midlife crisis. */ 28 | default: { 29 | types: './dist/typings/index.d.ts', 30 | default: './dist/esm/index.js', 31 | }, 32 | }, 33 | './package.json': './package.json', 34 | }, 35 | publishConfig: { 36 | directory: 'dist', 37 | access: 'public', 38 | }, 39 | }); 40 | 41 | export const presetFieldsOnlyESM = { 42 | type: 'module', 43 | main: 'dist/esm/index.js', 44 | typings: 'dist/typings/index.d.ts', 45 | exports: { 46 | '.': { 47 | import: { 48 | types: './dist/typings/index.d.ts', 49 | default: './dist/esm/index.js', 50 | }, 51 | /** without this default (THAT MUST BE LAST!!!) webpack will have a midlife crisis. */ 52 | default: { 53 | types: './dist/typings/index.d.ts', 54 | default: './dist/esm/index.js', 55 | }, 56 | }, 57 | './package.json': './package.json', 58 | }, 59 | publishConfig: { 60 | directory: 'dist', 61 | access: 'public', 62 | }, 63 | }; 64 | 65 | async function applyESMModuleTransform(cwd = process.cwd()) { 66 | const filePaths = await globby('**/*.ts', { 67 | cwd, 68 | absolute: true, 69 | ignore: ['**/node_modules/**', ...buildArtifactDirectories], 70 | }); 71 | 72 | const limit = pLimit(20); 73 | 74 | await Promise.all( 75 | filePaths.map(filePath => 76 | limit(async () => { 77 | const contents = await fse.readFile(filePath, 'utf-8'); 78 | await fse.writeFile(filePath, rewriteCodeImports(contents, filePath)); 79 | }), 80 | ), 81 | ); 82 | } 83 | 84 | async function applyPackageJSONPresetConfig( 85 | packageJSONPath: string, 86 | packageJSON: Record, 87 | ) { 88 | Object.assign(packageJSON, presetFieldsDual); 89 | await fse.writeFile(packageJSONPath, JSON.stringify(packageJSON, null, 2)); 90 | } 91 | 92 | const limit = pLimit(20); 93 | 94 | export const bootstrapCommand = createCommand<{}, {}>(() => { 95 | return { 96 | command: 'bootstrap', 97 | describe: 98 | 'The easiest way of setting all the right exports on your package.json files without hassle.', 99 | builder(yargs) { 100 | return yargs.options({}); 101 | }, 102 | async handler() { 103 | const rootPackageJSON = await getRootPackageJSON(); 104 | const workspaces = await getWorkspaces(rootPackageJSON); 105 | const isSinglePackage = workspaces === null; 106 | 107 | // Make sure all modules are converted to ESM 108 | 109 | if (isSinglePackage) { 110 | await applyESMModuleTransform(); 111 | await applyPackageJSONPresetConfig( 112 | path.join(process.cwd(), 'package.json'), 113 | rootPackageJSON, 114 | ); 115 | return; 116 | } 117 | 118 | const workspacePackagePaths = await getWorkspacePackagePaths(workspaces); 119 | 120 | await Promise.all( 121 | workspacePackagePaths.map(packagePath => 122 | limit(async () => { 123 | const packageJSONPath = path.join(packagePath, 'package.json'); 124 | const packageJSON: Record = await fse.readJSON(packageJSONPath); 125 | await applyESMModuleTransform(packagePath); 126 | await applyPackageJSONPresetConfig(packageJSONPath, packageJSON); 127 | }), 128 | ), 129 | ); 130 | }, 131 | }; 132 | }); 133 | -------------------------------------------------------------------------------- /src/commands/build.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { dirname, join, resolve } from 'path'; 3 | import { type ConsolaInstance } from 'consola'; 4 | import { execa } from 'execa'; 5 | import fse from 'fs-extra'; 6 | import { getTsconfig, parseTsconfig } from 'get-tsconfig'; 7 | import { globby } from 'globby'; 8 | import get from 'lodash.get'; 9 | import pLimit from 'p-limit'; 10 | import { createCommand } from '../command.js'; 11 | import { getBobConfig } from '../config.js'; 12 | import { getRootPackageJSON } from '../utils/get-root-package-json.js'; 13 | import { getWorkspacePackagePaths } from '../utils/get-workspace-package-paths.js'; 14 | import { getWorkspaces } from '../utils/get-workspaces.js'; 15 | import { rewriteExports } from '../utils/rewrite-exports.js'; 16 | import { presetFieldsDual, presetFieldsOnlyESM } from './bootstrap.js'; 17 | 18 | export const DIST_DIR = 'dist'; 19 | 20 | export const DEFAULT_TS_BUILD_CONFIG = 'tsconfig.build.json'; 21 | 22 | interface PackageInfo { 23 | packagePath: string; 24 | cwd: string; 25 | pkg: any; 26 | fullName: string; 27 | } 28 | 29 | /** 30 | * A list of files that we don't need within the published package. 31 | * Also known as test files :) 32 | * This list is derived from scouting various of our repositories. 33 | */ 34 | const filesToExcludeFromDist = [ 35 | '**/test/**', 36 | '**/tests/**', 37 | '**/__tests__/**', 38 | '**/__testUtils__/**', 39 | '**/*.spec.*', 40 | '**/*.test.*', 41 | '**/dist', 42 | '**/temp', 43 | ]; 44 | 45 | function compilerOptionsToArgs(options: Record): string[] { 46 | return Object.entries(options) 47 | .filter(([, value]) => !!value) 48 | .flatMap(([key, value]) => [`--${key}`, `${value}`]); 49 | } 50 | 51 | function assertTypeScriptBuildResult( 52 | result: Awaited>, 53 | reporter: ConsolaInstance, 54 | ) { 55 | if (result.exitCode !== 0) { 56 | reporter.error(result.stdout); 57 | throw new Error('TypeScript compiler exited with non-zero exit code.'); 58 | } 59 | } 60 | 61 | async function buildTypeScript( 62 | buildPath: string, 63 | options: { 64 | cwd: string; 65 | tsconfig?: string; 66 | incremental?: boolean; 67 | }, 68 | reporter: ConsolaInstance, 69 | ) { 70 | let project = options.tsconfig; 71 | if (!project && (await fse.exists(join(options.cwd, DEFAULT_TS_BUILD_CONFIG)))) { 72 | project = join(options.cwd, DEFAULT_TS_BUILD_CONFIG); 73 | } 74 | 75 | const tsconfig = project ? parseTsconfig(project) : getTsconfig(options.cwd)?.config; 76 | 77 | const moduleResolution = (tsconfig?.compilerOptions?.moduleResolution || '').toLowerCase(); 78 | const isModernNodeModuleResolution = ['node16', 'nodenext'].includes(moduleResolution); 79 | const isOldNodeModuleResolution = ['classic', 'node', 'node10'].includes(moduleResolution); 80 | if (moduleResolution && !isOldNodeModuleResolution && !isModernNodeModuleResolution) { 81 | throw new Error( 82 | `'moduleResolution' option '${moduleResolution}' cannot be used to build CommonJS"`, 83 | ); 84 | } 85 | 86 | async function build(out: PackageJsonType) { 87 | const revertPackageJsonsType = await setPackageJsonsType( 88 | { cwd: options.cwd, ignore: [...filesToExcludeFromDist, ...(tsconfig?.exclude || [])] }, 89 | out, 90 | ); 91 | try { 92 | assertTypeScriptBuildResult( 93 | await execa('npx', [ 94 | 'tsc', 95 | ...compilerOptionsToArgs({ 96 | project, 97 | module: isModernNodeModuleResolution 98 | ? moduleResolution // match module with moduleResolution for modern node (nodenext and node16) 99 | : out === 'module' 100 | ? 'es2022' 101 | : isOldNodeModuleResolution 102 | ? 'commonjs' // old commonjs 103 | : 'node16', // modern commonjs 104 | sourceMap: false, 105 | inlineSourceMap: false, 106 | incremental: options.incremental, 107 | outDir: out === 'module' ? join(buildPath, 'esm') : join(buildPath, 'cjs'), 108 | }), 109 | ]), 110 | reporter, 111 | ); 112 | } finally { 113 | await revertPackageJsonsType(); 114 | } 115 | } 116 | 117 | await build('module'); 118 | await build('commonjs'); 119 | } 120 | 121 | export const buildCommand = createCommand< 122 | {}, 123 | { 124 | tsconfig?: string; 125 | incremental?: boolean; 126 | } 127 | >(api => { 128 | const { reporter } = api; 129 | 130 | return { 131 | command: 'build', 132 | describe: 'Build', 133 | builder(yargs) { 134 | return yargs.options({ 135 | tsconfig: { 136 | describe: `Which tsconfig file to use when building TypeScript. By default bob will use ${DEFAULT_TS_BUILD_CONFIG} if it exists, otherwise the TSC's default.`, 137 | type: 'string', 138 | }, 139 | incremental: { 140 | describe: 'Better performance by building only packages that had changes.', 141 | type: 'boolean', 142 | }, 143 | }); 144 | }, 145 | async handler({ tsconfig, incremental }) { 146 | const cwd = process.cwd(); 147 | const rootPackageJSON = await getRootPackageJSON(); 148 | const workspaces = await getWorkspaces(rootPackageJSON); 149 | const isSinglePackage = workspaces === null; 150 | 151 | if (isSinglePackage) { 152 | const buildPath = join(cwd, '.bob'); 153 | 154 | if (!incremental) { 155 | await fse.remove(buildPath); 156 | } 157 | await buildTypeScript(buildPath, { cwd, tsconfig, incremental }, reporter); 158 | const pkg = await fse.readJSON(resolve(cwd, 'package.json')); 159 | const fullName: string = pkg.name; 160 | 161 | const distPath = join(cwd, 'dist'); 162 | 163 | const getBuildPath = (target: 'esm' | 'cjs') => join(buildPath, target); 164 | 165 | await build({ 166 | cwd, 167 | pkg, 168 | fullName, 169 | reporter, 170 | getBuildPath, 171 | distPath, 172 | }); 173 | return; 174 | } 175 | 176 | const limit = pLimit(4); 177 | const workspacePackagePaths = await getWorkspacePackagePaths(workspaces); 178 | 179 | const packageInfoList: PackageInfo[] = await Promise.all( 180 | workspacePackagePaths.map(packagePath => 181 | limit(async () => { 182 | const cwd = packagePath; 183 | const pkg = await fse.readJSON(resolve(cwd, 'package.json')); 184 | const fullName: string = pkg.name; 185 | return { packagePath, cwd, pkg, fullName }; 186 | }), 187 | ), 188 | ); 189 | 190 | const bobBuildPath = join(cwd, '.bob'); 191 | if (!incremental) { 192 | await fse.remove(bobBuildPath); 193 | } 194 | await buildTypeScript(bobBuildPath, { cwd, tsconfig, incremental }, reporter); 195 | 196 | await Promise.all( 197 | packageInfoList.map(({ cwd, pkg, fullName }) => 198 | limit(async () => { 199 | const getBuildPath = (target: 'esm' | 'cjs') => 200 | join(cwd.replace('packages', join('.bob', target)), 'src'); 201 | 202 | const distPath = join(cwd, 'dist'); 203 | 204 | await build({ 205 | cwd, 206 | pkg, 207 | fullName, 208 | reporter, 209 | getBuildPath, 210 | distPath, 211 | }); 212 | }), 213 | ), 214 | ); 215 | }, 216 | }; 217 | }); 218 | 219 | const limit = pLimit(20); 220 | 221 | async function build({ 222 | cwd, 223 | pkg, 224 | fullName, 225 | reporter, 226 | getBuildPath, 227 | distPath, 228 | }: { 229 | cwd: string; 230 | pkg: { 231 | name: string; 232 | bin?: Record; 233 | }; 234 | fullName: string; 235 | reporter: ConsolaInstance; 236 | getBuildPath: (target: 'esm' | 'cjs') => string; 237 | distPath: string; 238 | }) { 239 | const config = getBobConfig(pkg); 240 | 241 | if (config === false || config?.build === false) { 242 | reporter.warn(`Skip build for '${fullName}'`); 243 | return; 244 | } 245 | 246 | const dual = config?.commonjs ?? true; 247 | 248 | validatePackageJson(pkg, { dual }); 249 | 250 | const declarations = await globby('**/*.d.ts', { 251 | cwd: getBuildPath('esm'), 252 | absolute: false, 253 | ignore: filesToExcludeFromDist, 254 | }); 255 | 256 | await fse.ensureDir(join(distPath, 'typings')); 257 | await Promise.all( 258 | declarations.map(filePath => 259 | limit(() => 260 | fse.copy(join(getBuildPath('esm'), filePath), join(distPath, 'typings', filePath)), 261 | ), 262 | ), 263 | ); 264 | 265 | const esmFiles = await globby('**/*.js', { 266 | cwd: getBuildPath('esm'), 267 | absolute: false, 268 | ignore: filesToExcludeFromDist, 269 | }); 270 | 271 | // all files that export nothing, should be completely empty 272 | // this way we wont have issues with linters: 273 | // and we will also make all type-only packages happy 274 | for (const file of esmFiles) { 275 | const src = await fse.readFile(join(getBuildPath('esm'), file)); 276 | if (src.toString().trim() === 'export {};') { 277 | await fse.writeFile(join(getBuildPath('esm'), file), ''); 278 | } 279 | } 280 | 281 | await fse.ensureDir(join(distPath, 'esm')); 282 | await Promise.all( 283 | esmFiles.map(filePath => 284 | limit(() => fse.copy(join(getBuildPath('esm'), filePath), join(distPath, 'esm', filePath))), 285 | ), 286 | ); 287 | 288 | if (dual) { 289 | // Transpile ESM to CJS and move CJS to dist/cjs only if there's something to transpile 290 | await fse.ensureDir(join(distPath, 'cjs')); 291 | 292 | const cjsFiles = await globby('**/*.js', { 293 | cwd: getBuildPath('cjs'), 294 | absolute: false, 295 | ignore: filesToExcludeFromDist, 296 | }); 297 | 298 | // all files that export nothing, should be completely empty 299 | // this way we wont have issues with linters: 300 | // and we will also make all type-only packages happy 301 | for (const file of cjsFiles) { 302 | const src = await fse.readFile(join(getBuildPath('cjs'), file)); 303 | if ( 304 | // TODO: will this always be the case with empty cjs files 305 | src.toString().trim() === 306 | '"use strict";\nObject.defineProperty(exports, "__esModule", { value: true });' 307 | ) { 308 | await fse.writeFile(join(getBuildPath('cjs'), file), ''); 309 | } 310 | } 311 | 312 | await Promise.all( 313 | cjsFiles.map(filePath => 314 | limit(() => fse.copy(join(getBuildPath('cjs'), filePath), join(distPath, 'cjs', filePath))), 315 | ), 316 | ); 317 | 318 | // Add package.json to dist/cjs to ensure files are interpreted as commonjs 319 | await fse.writeFile( 320 | join(distPath, 'cjs', 'package.json'), 321 | JSON.stringify({ type: 'commonjs' }), 322 | ); 323 | // We need to provide .cjs extension type definitions as well :) 324 | // https://github.com/ardatan/graphql-tools/discussions/4581#discussioncomment-3329673 325 | 326 | const declarations = await globby('**/*.d.ts', { 327 | cwd: getBuildPath('cjs'), 328 | absolute: false, 329 | ignore: filesToExcludeFromDist, 330 | }); 331 | await Promise.all( 332 | declarations.map(filePath => 333 | limit(async () => { 334 | const contents = await fse.readFile(join(getBuildPath('cjs'), filePath), 'utf-8'); 335 | await fse.writeFile( 336 | join(distPath, 'typings', filePath.replace(/\.d\.ts/, '.d.cts')), 337 | contents.replace(/\.js";\n/g, `.cjs";\n`).replace(/\.js';\n/g, `.cjs';\n`), 338 | ); 339 | }), 340 | ), 341 | ); 342 | } 343 | 344 | // move the package.json to dist 345 | await fse.writeFile( 346 | join(distPath, 'package.json'), 347 | JSON.stringify(rewritePackageJson(pkg), null, 2), 348 | ); 349 | 350 | // move README.md and LICENSE and other specified files 351 | await copyToDist(cwd, ['README.md', 'LICENSE', ...(config?.build?.copy ?? [])], distPath); 352 | 353 | if (pkg.bin) { 354 | if (globalThis.process.platform === 'win32') { 355 | reporter.warn( 356 | 'Package includes bin files, but cannot set the executable bit on Windows.\n' + 357 | 'Please manually set the executable bit on the bin files before publishing.', 358 | ); 359 | } else { 360 | await Promise.all( 361 | Object.values(pkg.bin).map(filePath => execa('chmod', ['+x', join(cwd, filePath)])), 362 | ); 363 | } 364 | } 365 | 366 | reporter.success(`Built ${pkg.name}`); 367 | } 368 | 369 | function rewritePackageJson(pkg: Record) { 370 | const newPkg: Record = {}; 371 | const fields = [ 372 | 'name', 373 | 'version', 374 | 'description', 375 | 'sideEffects', 376 | 'peerDependenciesMeta', 377 | 'peerDependencies', 378 | 'dependencies', 379 | 'optionalDependencies', 380 | 'repository', 381 | 'homepage', 382 | 'keywords', 383 | 'author', 384 | 'license', 385 | 'engines', 386 | 'name', 387 | 'main', 388 | 'typings', 389 | 'type', 390 | ]; 391 | 392 | fields.forEach(field => { 393 | if (pkg[field] !== undefined) { 394 | newPkg[field] = pkg[field]; 395 | if (field === 'engines') { 396 | // remove all package managers from engines field 397 | const ignoredPackageManagers = ['npm', 'yarn', 'pnpm']; 398 | for (const packageManager of ignoredPackageManagers) { 399 | if (newPkg[field][packageManager]) { 400 | delete newPkg[field][packageManager]; 401 | } 402 | } 403 | } 404 | } 405 | }); 406 | 407 | const distDirStr = `${DIST_DIR}/`; 408 | 409 | newPkg.main = newPkg.main.replace(distDirStr, ''); 410 | newPkg.typings = newPkg.typings.replace(distDirStr, ''); 411 | 412 | if (!pkg.exports) { 413 | newPkg.exports = presetFieldsDual.exports; 414 | } 415 | newPkg.exports = rewriteExports(pkg.exports, DIST_DIR); 416 | 417 | if (pkg.bin) { 418 | newPkg.bin = {}; 419 | 420 | for (const alias in pkg.bin) { 421 | newPkg.bin[alias] = pkg.bin[alias].replace(distDirStr, ''); 422 | } 423 | } 424 | 425 | return newPkg; 426 | } 427 | 428 | export function validatePackageJson( 429 | pkg: any, 430 | opts: { 431 | dual: boolean; 432 | }, 433 | ) { 434 | function expect(key: string, expected: unknown) { 435 | const received = get(pkg, key); 436 | 437 | assert.deepEqual( 438 | received, 439 | expected, 440 | `${pkg.name}: "${key}" equals "${JSON.stringify(received)}"` + 441 | `, should be "${JSON.stringify(expected)}".`, 442 | ); 443 | } 444 | 445 | // If the package has NO binary we need to check the exports map. 446 | // a package should either 447 | // 1. have a bin property 448 | // 2. have an exports property 449 | // 3. have an exports and bin property 450 | if (Object.keys(pkg.bin ?? {}).length > 0) { 451 | if (opts.dual === true) { 452 | expect('main', presetFieldsDual.main); 453 | expect('typings', presetFieldsDual.typings); 454 | } else { 455 | expect('main', presetFieldsOnlyESM.main); 456 | expect('typings', presetFieldsOnlyESM.typings); 457 | } 458 | } else if (pkg.main !== undefined || pkg.exports !== undefined || pkg.typings !== undefined) { 459 | if (opts.dual === true) { 460 | // if there is no bin property, we NEED to check the exports. 461 | expect('main', presetFieldsDual.main); 462 | expect('typings', presetFieldsDual.typings); 463 | 464 | // For now we enforce a top level exports property 465 | expect("exports['.'].require", presetFieldsDual.exports['.'].require); 466 | expect("exports['.'].import", presetFieldsDual.exports['.'].import); 467 | expect("exports['.'].default", presetFieldsDual.exports['.'].default); 468 | } else { 469 | expect('main', presetFieldsOnlyESM.main); 470 | expect('typings', presetFieldsOnlyESM.typings); 471 | 472 | // For now, we enforce a top level exports property 473 | expect("exports['.']", presetFieldsOnlyESM.exports['.']); 474 | } 475 | } 476 | } 477 | 478 | type PackageJsonType = 'module' | 'commonjs'; 479 | 480 | /** 481 | * Sets the {@link cwd workspaces} package.json(s) `"type"` field to the defined {@link type} 482 | * returning a "revert" function which puts the original `"type"` back. 483 | * 484 | * @returns A revert function that reverts the original value of the `"type"` field. 485 | */ 486 | async function setPackageJsonsType( 487 | { cwd, ignore }: { cwd: string; ignore: string[] }, 488 | type: PackageJsonType, 489 | ): Promise<() => Promise> { 490 | const rootPkgJsonPath = join(cwd, 'package.json'); 491 | const rootContents = await fse.readFile(rootPkgJsonPath, 'utf8'); 492 | const rootPkg = JSON.parse(rootContents); 493 | const workspaces = await getWorkspaces(rootPkg); 494 | const isSinglePackage = workspaces === null; 495 | 496 | const reverts: (() => Promise)[] = []; 497 | 498 | for (const pkgJsonPath of [ 499 | // we also want to modify the root package.json TODO: do we in single package repos? 500 | rootPkgJsonPath, 501 | ...(isSinglePackage 502 | ? [] 503 | : await globby( 504 | workspaces.map((w: string) => w + '/package.json'), 505 | { cwd, absolute: true, ignore }, 506 | )), 507 | ]) { 508 | const contents = 509 | pkgJsonPath === rootPkgJsonPath 510 | ? // no need to re-read the root package.json 511 | rootContents 512 | : await fse.readFile(pkgJsonPath, 'utf8'); 513 | const endsWithNewline = contents.endsWith('\n'); 514 | 515 | const pkg = JSON.parse(contents); 516 | if (pkg.type != null && pkg.type !== 'commonjs' && pkg.type !== 'module') { 517 | throw new Error(`Invalid "type" property value "${pkg.type}" in ${pkgJsonPath}`); 518 | } 519 | 520 | const originalPkg = { ...pkg }; 521 | const differentType = 522 | (pkg.type || 523 | // default when the type is not defined 524 | 'commonjs') !== type; 525 | 526 | // change only if the provided type is different 527 | if (differentType) { 528 | pkg.type = type; 529 | await fse.writeFile( 530 | pkgJsonPath, 531 | JSON.stringify(pkg, null, ' ') + (endsWithNewline ? '\n' : ''), 532 | ); 533 | 534 | // revert change, of course only if we changed something 535 | reverts.push(async () => { 536 | await fse.writeFile( 537 | pkgJsonPath, 538 | JSON.stringify(originalPkg, null, ' ') + (endsWithNewline ? '\n' : ''), 539 | ); 540 | }); 541 | } 542 | } 543 | 544 | return async function revert() { 545 | await Promise.all(reverts.map(r => r())); 546 | }; 547 | } 548 | 549 | async function executeCopy(sourcePath: string, destPath: string) { 550 | await fse.mkdirp(dirname(destPath)); 551 | await fse.copyFile(sourcePath, destPath); 552 | } 553 | 554 | async function copyToDist(cwd: string, files: string[], distDir: string) { 555 | const allFiles = await globby(files, { cwd }); 556 | 557 | return Promise.all( 558 | allFiles.map(async file => { 559 | if (await fse.pathExists(join(cwd, file))) { 560 | const sourcePath = join(cwd, file); 561 | if (file.includes('src/')) { 562 | // Figure relevant module types 563 | const allTypes: ('cjs' | 'esm')[] = []; 564 | if (await fse.pathExists(join(distDir, 'esm'))) { 565 | allTypes.push('esm'); 566 | } 567 | if (await fse.pathExists(join(distDir, 'cjs'))) { 568 | allTypes.push('cjs'); 569 | } 570 | 571 | // FOr each type, copy files to the relevant directory 572 | await Promise.all( 573 | allTypes.map(type => 574 | executeCopy(sourcePath, join(distDir, file.replace('src/', `${type}/`))), 575 | ), 576 | ); 577 | } else { 578 | const destPath = join(distDir, file); 579 | executeCopy(sourcePath, destPath); 580 | } 581 | } 582 | }), 583 | ); 584 | } 585 | -------------------------------------------------------------------------------- /src/commands/check.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { execa } from 'execa'; 3 | import fse from 'fs-extra'; 4 | import { globby } from 'globby'; 5 | import pLimit from 'p-limit'; 6 | import * as resolve from 'resolve.exports'; 7 | import zod from 'zod'; 8 | import { createCommand } from '../command.js'; 9 | import { getBobConfig } from '../config.js'; 10 | import { getRootPackageJSON } from '../utils/get-root-package-json.js'; 11 | import { getWorkspacePackagePaths } from '../utils/get-workspace-package-paths.js'; 12 | import { getWorkspaces } from '../utils/get-workspaces.js'; 13 | import { presetFieldsDual } from './bootstrap.js'; 14 | 15 | const ExportsMapEntry = zod.object({ 16 | default: zod.string(), 17 | types: zod.string(), 18 | }); 19 | 20 | const ExportsMapModel = zod.record( 21 | zod.union([ 22 | zod.string(), 23 | zod.object({ 24 | require: zod.optional(ExportsMapEntry), 25 | import: ExportsMapEntry, 26 | default: ExportsMapEntry, 27 | }), 28 | ]), 29 | ); 30 | 31 | const EnginesModel = zod.record(zod.string(), zod.string()); 32 | 33 | const BinModel = zod.record(zod.string()); 34 | 35 | export const checkCommand = createCommand<{}, {}>(api => { 36 | return { 37 | command: 'check', 38 | describe: 39 | 'Check whether all files in the exports map within the built package can be imported.', 40 | builder(yargs) { 41 | return yargs.options({}); 42 | }, 43 | async handler() { 44 | const cwd = process.cwd(); 45 | const rootPackageJSON = await getRootPackageJSON(); 46 | const workspaces = await getWorkspaces(rootPackageJSON); 47 | const isSinglePackage = workspaces === null; 48 | 49 | let checkConfigs: Array<{ 50 | cwd: string; 51 | packageJSON: Record; 52 | }> = []; 53 | 54 | if (isSinglePackage) { 55 | checkConfigs.push({ 56 | cwd, 57 | packageJSON: rootPackageJSON, 58 | }); 59 | } else { 60 | const workspacesPaths = await getWorkspacePackagePaths(workspaces); 61 | const limit = pLimit(20); 62 | await Promise.all( 63 | workspacesPaths.map(workspacePath => 64 | limit(async () => { 65 | const packageJSONPath = path.join(workspacePath, 'package.json'); 66 | const packageJSON: Record = await fse.readJSON(packageJSONPath); 67 | checkConfigs.push({ 68 | cwd: workspacePath, 69 | packageJSON, 70 | }); 71 | }), 72 | ), 73 | ); 74 | } 75 | 76 | const limit = pLimit(20); 77 | 78 | let didFail = false; 79 | await Promise.allSettled( 80 | checkConfigs.map(({ cwd, packageJSON }) => 81 | limit(async () => { 82 | const config = getBobConfig(packageJSON); 83 | if (config === false || config?.check === false) { 84 | api.reporter.warn(`Skip check for '${packageJSON.name}'.`); 85 | return; 86 | } 87 | 88 | const distPackageJSONPath = path.join(cwd, 'dist', 'package.json'); 89 | const distPackageJSON = await fse.readJSON(distPackageJSONPath); 90 | 91 | try { 92 | await checkExportsMapIntegrity({ 93 | cwd: path.join(cwd, 'dist'), 94 | packageJSON: distPackageJSON, 95 | skipExports: new Set(config?.check?.skip ?? []), 96 | dual: config?.commonjs ?? true, 97 | }); 98 | await checkEngines({ 99 | packageJSON: distPackageJSON, 100 | }); 101 | } catch (err) { 102 | api.reporter.error(`Integrity check of '${packageJSON.name}' failed.`); 103 | api.reporter.log(err); 104 | didFail = true; 105 | return; 106 | } 107 | api.reporter.success(`Checked integrity of '${packageJSON.name}'.`); 108 | }), 109 | ), 110 | ); 111 | if (didFail) { 112 | throw new Error('One ore more integrity checks failed.'); 113 | } 114 | }, 115 | }; 116 | }); 117 | 118 | async function checkExportsMapIntegrity(args: { 119 | cwd: string; 120 | packageJSON: { 121 | name: string; 122 | exports: any; 123 | bin: unknown; 124 | }; 125 | skipExports: Set; 126 | dual: boolean; 127 | }) { 128 | const exportsMapResult = ExportsMapModel.safeParse(args.packageJSON['exports']); 129 | if (exportsMapResult.success === false) { 130 | throw new Error( 131 | "Missing exports map within the 'package.json'.\n" + 132 | exportsMapResult.error.message + 133 | '\nCorrect Example:\n' + 134 | JSON.stringify(presetFieldsDual.exports, null, 2), 135 | ); 136 | } 137 | 138 | const exportsMap = exportsMapResult['data']; 139 | 140 | const cjsSkipExports = new Set(); 141 | const esmSkipExports = new Set(); 142 | for (const definedExport of args.skipExports) { 143 | if (args.dual) { 144 | const cjsResult = resolve.resolve(args.packageJSON, definedExport, { 145 | require: true, 146 | })?.[0]; 147 | if (typeof cjsResult === 'string') { 148 | cjsSkipExports.add(cjsResult); 149 | } 150 | } 151 | const esmResult = resolve.resolve(args.packageJSON, definedExport)?.[0]; 152 | if (typeof esmResult === 'string') { 153 | esmSkipExports.add(esmResult); 154 | } 155 | } 156 | 157 | for (const key of Object.keys(exportsMap)) { 158 | if (args.dual) { 159 | const cjsResult = resolve.resolve(args.packageJSON, key, { 160 | require: true, 161 | })?.[0]; 162 | 163 | if (!cjsResult) { 164 | throw new Error( 165 | `Could not resolve CommonJS import '${key}' for '${args.packageJSON.name}'.`, 166 | ); 167 | } 168 | 169 | if (cjsResult.match(/.(js|cjs)$/)) { 170 | const cjsFilePaths = await globby(cjsResult, { 171 | cwd: args.cwd, 172 | }); 173 | if (!cjsFilePaths.length) { 174 | throw new Error( 175 | `No files found matching the path '${cjsResult}' in '${key}' for '${args.packageJSON.name}'.`, 176 | ); 177 | } 178 | 179 | const limit = pLimit(20); 180 | await Promise.all( 181 | cjsFilePaths.map(file => 182 | limit(async () => { 183 | if (cjsSkipExports.has(file)) { 184 | return; 185 | } 186 | 187 | const result = await runRequireJSFileCommand({ 188 | path: file, 189 | cwd: args.cwd, 190 | }); 191 | 192 | if (result.exitCode !== 0) { 193 | throw new Error( 194 | `Require of file '${file}' failed.\n` + 195 | `In case this file is expected to raise an error please add an export to the 'bob.check.skip' field in your 'package.json' file.\n` + 196 | `Error:\n` + 197 | result.stderr, 198 | ); 199 | } 200 | }), 201 | ), 202 | ); 203 | } else { 204 | // package.json or other files 205 | // for now we just make sure they exists 206 | await fse.stat(path.join(args.cwd, cjsResult)); 207 | } 208 | } 209 | 210 | const esmResult = resolve.resolve({ exports: exportsMap }, key)?.[0]; 211 | if (!esmResult) { 212 | throw new Error(`Could not resolve export '${key}' in '${args.packageJSON.name}'.`); 213 | } 214 | 215 | if (esmResult.match(/.(js|mjs)$/)) { 216 | const esmFilePaths = await globby(esmResult, { 217 | cwd: args.cwd, 218 | }); 219 | if (!esmFilePaths.length) { 220 | throw new Error( 221 | `No files found matching the path '${esmResult}' in '${key}' for '${args.packageJSON.name}'.`, 222 | ); 223 | } 224 | 225 | const limit = pLimit(20); 226 | await Promise.all( 227 | esmFilePaths.map(file => 228 | limit(async () => { 229 | if (esmSkipExports.has(file)) { 230 | return; 231 | } 232 | const result = await runImportJSFileCommand({ 233 | path: file, 234 | cwd: args.cwd, 235 | }); 236 | if (result.exitCode !== 0) { 237 | throw new Error(`Import of file '${file}' failed with error:\n` + result.stderr); 238 | } 239 | }), 240 | ), 241 | ); 242 | } else { 243 | // package.json or other files 244 | // for now we just make sure they exists 245 | await fse.stat(path.join(args.cwd, esmResult)); 246 | } 247 | } 248 | 249 | const exportsRequirePath = resolve.resolve({ exports: exportsMap }, '.', { require: true })?.[0]; 250 | if (!exportsRequirePath || typeof exportsRequirePath !== 'string') { 251 | throw new Error('Could not resolve default CommonJS entrypoint in a Module project.'); 252 | } 253 | 254 | if (args.dual) { 255 | const requireResult = await runRequireJSFileCommand({ 256 | path: exportsRequirePath, 257 | cwd: args.cwd, 258 | }); 259 | 260 | if (requireResult.exitCode !== 0) { 261 | throw new Error( 262 | `Require of file '${exportsRequirePath}' failed with error:\n` + requireResult.stderr, 263 | ); 264 | } 265 | } else { 266 | const importResult = await runImportJSFileCommand({ 267 | path: exportsRequirePath, 268 | cwd: args.cwd, 269 | }); 270 | 271 | if (importResult.exitCode !== 0) { 272 | throw new Error( 273 | `Import of file '${exportsRequirePath}' failed with error:\n` + importResult.stderr, 274 | ); 275 | } 276 | } 277 | 278 | const legacyImport = resolve.legacy(args.packageJSON); 279 | if (!legacyImport || typeof legacyImport !== 'string') { 280 | throw new Error('Could not resolve default ESM entrypoint.'); 281 | } 282 | const legacyImportResult = await runImportJSFileCommand({ 283 | path: legacyImport, 284 | cwd: args.cwd, 285 | }); 286 | if (legacyImportResult.exitCode !== 0) { 287 | throw new Error( 288 | `Require of file '${exportsRequirePath}' failed with error:\n` + legacyImportResult.stderr, 289 | ); 290 | } 291 | 292 | if (args.packageJSON.bin) { 293 | const result = BinModel.safeParse(args.packageJSON.bin); 294 | if (result.success === false) { 295 | throw new Error('Invalid format of bin field in package.json.\n' + result.error.message); 296 | } 297 | 298 | const cache = new Set(); 299 | 300 | for (const filePath of Object.values(result.data)) { 301 | if (cache.has(filePath)) { 302 | continue; 303 | } 304 | cache.add(filePath); 305 | 306 | const absoluteFilePath = path.join(args.cwd, filePath); 307 | await fse.stat(absoluteFilePath).catch(() => { 308 | throw new Error("Could not find binary file '" + absoluteFilePath + "'."); 309 | }); 310 | await fse.access(path.join(args.cwd, filePath), fse.constants.X_OK).catch(() => { 311 | throw new Error( 312 | "Binary file '" + 313 | absoluteFilePath + 314 | "' is not executable.\n" + 315 | `Please set the executable bit e.g. by running 'chmod +x "${absoluteFilePath}"'.`, 316 | ); 317 | }); 318 | 319 | const contents = await fse.readFile(absoluteFilePath, 'utf-8'); 320 | if (!contents.startsWith('#!/usr/bin/env node\n')) { 321 | throw new Error( 322 | "Binary file '" + 323 | absoluteFilePath + 324 | "' does not have a shebang.\n Please add '#!/usr/bin/env node' to the beginning of the file.", 325 | ); 326 | } 327 | } 328 | } 329 | } 330 | 331 | async function checkEngines(args: { 332 | packageJSON: { 333 | name: string; 334 | engines: unknown; 335 | }; 336 | }) { 337 | const engines = EnginesModel.safeParse(args.packageJSON.engines); 338 | if (engines.success === false || engines.data['node'] === undefined) { 339 | throw new Error('Please specify the node engine version in your package.json.'); 340 | } 341 | } 342 | 343 | const timeout = `;setTimeout(() => { throw new Error("The Node.js process hangs. There is probably some side-effects. All exports should be free of side effects.") }, 500).unref()`; 344 | 345 | function runRequireJSFileCommand(args: { cwd: string; path: string }) { 346 | return execa('node', ['-e', `require('${args.path}')${timeout}`], { 347 | cwd: args.cwd, 348 | reject: false, 349 | }); 350 | } 351 | 352 | function runImportJSFileCommand(args: { cwd: string; path: string }) { 353 | return execa('node', ['-e', `import('${args.path}').then(() => {${timeout}})`], { 354 | cwd: args.cwd, 355 | reject: false, 356 | }); 357 | } 358 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import zod from 'zod'; 2 | 3 | const BobConfigModel = zod.optional( 4 | zod.union( 5 | [ 6 | zod.literal(false), 7 | zod.object({ 8 | commonjs: zod 9 | .boolean({ 10 | description: 11 | 'Enable CommonJS output creating a dual output ESM+CJS. If set to `false`, will generate only ESM output.', 12 | }) 13 | .optional() 14 | .default(true), 15 | build: zod.union( 16 | [ 17 | zod.literal(false), 18 | zod.optional( 19 | zod.object({ 20 | copy: zod.optional(zod.array(zod.string()), { 21 | description: 22 | 'Specify a list of files that should be copied the the output directory.', 23 | }), 24 | }), 25 | ), 26 | ], 27 | { 28 | description: 29 | 'Build configuration. Set to false for skipping the build of this package.', 30 | }, 31 | ), 32 | check: zod.optional( 33 | zod.union([ 34 | zod.literal(false), 35 | zod.object({ 36 | skip: zod.optional(zod.array(zod.string()), { 37 | description: 38 | 'Skip certain files from being checked. E.g. modules with side-effects.', 39 | }), 40 | }), 41 | ]), 42 | { 43 | description: 44 | 'Check whether the built packages comply with the standards. (ESM & CJS compatible and loadable and Node.js engines specified)', 45 | }, 46 | ), 47 | }), 48 | ], 49 | { 50 | description: 51 | 'Bob configuration. Set this value to false in order to disable running bob on this package.', 52 | }, 53 | ), 54 | ); 55 | 56 | export type BobConfig = zod.TypeOf; 57 | 58 | export function getBobConfig(packageJson: Record) { 59 | return BobConfigModel.parse(packageJson.bob ?? {}); 60 | } 61 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const buildArtifactDirectories = [`**/dist/**`, `**/build/**`] as const; 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { createConsola } from 'consola'; 3 | import yargs, { Argv } from 'yargs'; 4 | import { hideBin } from 'yargs/helpers'; 5 | import { CommandFactory } from './command.js'; 6 | import { bootstrapCommand } from './commands/bootstrap.js'; 7 | import { buildCommand } from './commands/build.js'; 8 | import { checkCommand } from './commands/check.js'; 9 | 10 | async function main() { 11 | const root: Argv = yargs(hideBin(process.argv)).scriptName('bob').detectLocale(false).version(); 12 | 13 | const commands: CommandFactory[] = [buildCommand, bootstrapCommand, checkCommand]; 14 | 15 | const reporter = createConsola({}); 16 | 17 | commands 18 | .reduce((cli, cmd) => cli.command(cmd({ reporter })), root) 19 | .help() 20 | .showHelpOnFail(false).argv; 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/utils/__fixtures__/foo/index.ts: -------------------------------------------------------------------------------- 1 | console.log('hello'); 2 | -------------------------------------------------------------------------------- /src/utils/get-root-package-json.ts: -------------------------------------------------------------------------------- 1 | import fse from 'fs-extra'; 2 | import { globby } from 'globby'; 3 | 4 | export async function getRootPackageJSON(cwd = process.cwd()) { 5 | const [rootPackageJSONPath] = await globby('package.json', { 6 | cwd, 7 | absolute: true, 8 | }); 9 | 10 | if (rootPackageJSONPath === undefined) { 11 | throw new Error('Must be executed within a (monorepo-)package root.'); 12 | } 13 | 14 | const rootPackageJSON: Record = await fse.readJSON(rootPackageJSONPath); 15 | 16 | return rootPackageJSON; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/get-workspace-package-paths.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { globby } from 'globby'; 3 | import { buildArtifactDirectories } from '../constants.js'; 4 | 5 | export async function getWorkspacePackagePaths(workspaces: string[], cwd = process.cwd()) { 6 | const packageJSONPaths = await globby( 7 | workspaces 8 | /** We are only interested in workspaces that are packages (for now.) */ 9 | .filter(workspacePattern => workspacePattern.startsWith('packages/')) 10 | .map(workspacePattern => path.join(workspacePattern, 'package.json')), 11 | { 12 | cwd, 13 | ignore: ['**/node_modules/**', ...buildArtifactDirectories], 14 | }, 15 | ); 16 | 17 | return packageJSONPaths.map(packageJSONPath => path.dirname(packageJSONPath)); 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/get-workspaces.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import fse from 'fs-extra'; 3 | import jsYaml from 'js-yaml'; 4 | import zod from 'zod'; 5 | 6 | const WorkspaceModel = zod.optional( 7 | zod.union([ 8 | zod.array(zod.string()), 9 | zod.object({ 10 | packages: zod.optional(zod.array(zod.string())), 11 | nohoist: zod.optional(zod.array(zod.string())), 12 | }), 13 | ]), 14 | ); 15 | 16 | export async function getWorkspaces( 17 | packageJSON: Record, 18 | ): Promise { 19 | let result = WorkspaceModel.parse(packageJSON.workspaces); 20 | 21 | const pnpmWorkspacePath = path.join(process.cwd(), 'pnpm-workspace.yaml'); 22 | const isPnpmWorkspace = await fse.pathExists(pnpmWorkspacePath); 23 | 24 | if (isPnpmWorkspace) { 25 | if (result) { 26 | throw new Error( 27 | 'Both `pnpm-workspace.yaml` and `package.json#workspaces` are not supported. Remove `package.json#workspaces` field.', 28 | ); 29 | } 30 | 31 | result = jsYaml.load(await fse.readFile(pnpmWorkspacePath, 'utf8')) as { 32 | packages?: string[]; 33 | }; 34 | } 35 | if (result == null) { 36 | return null; 37 | } 38 | if (Array.isArray(result)) { 39 | return result; 40 | } 41 | if (Array.isArray(result?.packages)) { 42 | return result.packages; 43 | } 44 | 45 | return null; 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/rewrite-code-imports.spec.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { expect, it } from 'vitest'; 3 | import { rewriteCodeImports } from './rewrite-code-imports'; 4 | 5 | const fixturePath = path.join(__dirname, '__fixtures__', 'index.ts'); 6 | 7 | it('ignores module import statement', () => { 8 | const fixture = "import foo from 'foo'"; 9 | const result = rewriteCodeImports(fixture, fixturePath); 10 | expect(result).toEqual("import foo from 'foo'"); 11 | }); 12 | 13 | it('handles type statements', () => { 14 | const fixture = "import type foo from 'foo'"; 15 | const result = rewriteCodeImports(fixture, fixturePath); 16 | expect(result).toEqual("import type foo from 'foo'"); 17 | }); 18 | 19 | it('rewrites relative import statement', () => { 20 | const fixture = "import foo from './bar'"; 21 | const result = rewriteCodeImports(fixture, fixturePath); 22 | expect(result).toEqual(`import foo from './bar.js'`); 23 | }); 24 | 25 | it('rewrites relative import statement for folder', () => { 26 | const fixture = "import foo from './foo'"; 27 | const result = rewriteCodeImports(fixture, fixturePath); 28 | expect(result).toEqual(`import foo from './foo/index.js'`); 29 | }); 30 | 31 | it('rewrites relative import statement', () => { 32 | const fixture = "import foo from '../foo'"; 33 | const result = rewriteCodeImports(fixture, fixturePath); 34 | expect(result).toEqual(`import foo from '../foo.js'`); 35 | }); 36 | 37 | it('ignores module export statement', () => { 38 | const fixture = "export {foo} from 'foo'"; 39 | const result = rewriteCodeImports(fixture, fixturePath); 40 | expect(result).toEqual("export {foo} from 'foo'"); 41 | }); 42 | 43 | it('rewrites relative export statement', () => { 44 | const fixture = "export {foo} from './bar'"; 45 | const result = rewriteCodeImports(fixture, fixturePath); 46 | expect(result).toEqual(`export {foo} from './bar.js'`); 47 | }); 48 | 49 | it('rewrites relative export statement for folder', () => { 50 | const fixture = "export {foo} from './foo'"; 51 | const result = rewriteCodeImports(fixture, fixturePath); 52 | expect(result).toEqual(`export {foo} from './foo/index.js'`); 53 | }); 54 | 55 | it('complex example', () => { 56 | const fixture = ` 57 | import { GraphQLError } from '../../../error/GraphQLError'; 58 | 59 | import type { FieldNode } from '../../../language/ast'; 60 | import type { ASTVisitor } from '../../../language/visitor'; 61 | 62 | import { getNamedType } from '../../../type/definition'; 63 | import { isIntrospectionType } from '../../../type/introspection'; 64 | 65 | import type { ValidationContext } from '../../ValidationContext'; 66 | `; 67 | const result = rewriteCodeImports(fixture, fixturePath); 68 | expect(result).toMatchInlineSnapshot(` 69 | import { GraphQLError } from '../../../error/GraphQLError.js'; 70 | 71 | import type { FieldNode } from '../../../language/ast.js'; 72 | import type { ASTVisitor } from '../../../language/visitor.js'; 73 | 74 | import { getNamedType } from '../../../type/definition.js'; 75 | import { isIntrospectionType } from '../../../type/introspection.js'; 76 | 77 | import type { ValidationContext } from '../../ValidationContext.js'; 78 | `); 79 | }); 80 | -------------------------------------------------------------------------------- /src/utils/rewrite-code-imports.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fse from 'fs-extra'; 3 | 4 | function isFolderSync(path: string) { 5 | try { 6 | return fse.statSync(path).isDirectory(); 7 | } catch { 8 | return false; 9 | } 10 | } 11 | 12 | function rewriteSourceValue(sourceValue: string, relativeDirname: string) { 13 | if (sourceValue.startsWith('.') && !sourceValue.endsWith('.js')) { 14 | const targetPath = path.resolve(relativeDirname, sourceValue); 15 | // If the target path is a folder, we need to import from the index.js file 16 | if (isFolderSync(targetPath)) { 17 | sourceValue += '/index'; 18 | } 19 | sourceValue += '.js'; 20 | } 21 | return sourceValue; 22 | } 23 | 24 | /** 25 | * Rewrite import and export statements to append the correct .js file extension when needed 26 | */ 27 | export function rewriteCodeImports(fileContents: string, absoluteFilePath: string): string { 28 | const relativeDirname = path.dirname(absoluteFilePath); 29 | 30 | return fileContents.replace( 31 | /* this regex should hopefully catch all kind of import/export expressions that are relative. */ 32 | /((?:import|export)\s+[\s\w,{}*]*\s+from\s+["'])((?:\.\/|\.\.\/)(?:(?!\.js).)+)(["'])/g, 33 | (_, importFromPart, modulePath, hyphenEndPart) => { 34 | const sourcePath = rewriteSourceValue(modulePath, relativeDirname); 35 | return `${importFromPart}${sourcePath}${hyphenEndPart}`; 36 | }, 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/rewrite-exports.ts: -------------------------------------------------------------------------------- 1 | type Exports = 2 | | string 3 | | { 4 | require?: string | Record; 5 | import?: string | Record; 6 | default?: string | Record; 7 | }; 8 | 9 | export function rewriteExports(exports: Record, distDir: string) { 10 | const newExports = { ...exports }; 11 | 12 | for (const [key, value] of Object.entries(newExports)) { 13 | if (!value) continue; 14 | 15 | let newValue = value as Exports; 16 | 17 | if (typeof newValue === 'string') { 18 | newValue = newValue.replace(`${distDir}/`, ''); 19 | } else if (typeof newValue === 'object' && newValue != null) { 20 | function transformValue(value: string | { [key: string]: string } | undefined) { 21 | if (value == null) { 22 | return; 23 | } 24 | if (typeof value === 'object') { 25 | const newValue: Record = {}; 26 | for (const [key, path] of Object.entries(value)) { 27 | newValue[key] = path.replace(`${distDir}/`, ''); 28 | } 29 | return newValue; 30 | } 31 | return value.replace(`${distDir}/`, ''); 32 | } 33 | 34 | newValue = { 35 | require: transformValue(newValue.require), 36 | import: transformValue(newValue.import), 37 | default: transformValue(newValue.import), 38 | }; 39 | } 40 | newExports[key.replace(`${distDir}/`, '')] = newValue; 41 | } 42 | 43 | return newExports; 44 | } 45 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-esm-only/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-esm-only", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 14.0.0" 6 | }, 7 | "main": "dist/esm/index.js", 8 | "typings": "dist/typings/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "import": { 12 | "types": "./dist/typings/index.d.ts", 13 | "default": "./dist/esm/index.js" 14 | }, 15 | "default": { 16 | "types": "./dist/typings/index.d.ts", 17 | "default": "./dist/esm/index.js" 18 | } 19 | }, 20 | "./package.json": "./package.json" 21 | }, 22 | "publishConfig": { 23 | "directory": "dist", 24 | "access": "public" 25 | }, 26 | "bob": { 27 | "commonjs": false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-esm-only/src/index.ts: -------------------------------------------------------------------------------- 1 | export const someNumber = 1; 2 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-esm-only/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "skipLibCheck": true, 5 | "declaration": true, 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-exports/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-exports", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 12.0.0", 6 | "pnpm": ">= 8.0.0" 7 | }, 8 | "main": "dist/esm/index.js", 9 | "typings": "dist/typings/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "require": { 13 | "types": "./dist/typings/index.d.cts", 14 | "default": "./dist/cjs/index.js" 15 | }, 16 | "import": { 17 | "types": "./dist/typings/index.d.ts", 18 | "default": "./dist/esm/index.js" 19 | }, 20 | "default": { 21 | "types": "./dist/typings/index.d.ts", 22 | "default": "./dist/esm/index.js" 23 | } 24 | }, 25 | "./sub": { 26 | "require": { 27 | "types": "./dist/typings/sub/index.d.cts", 28 | "default": "./dist/cjs/sub/index.js" 29 | }, 30 | "import": { 31 | "types": "./dist/typings/sub/index.d.ts", 32 | "default": "./dist/esm/sub/index.js" 33 | }, 34 | "default": { 35 | "types": "./dist/typings/sub/index.d.ts", 36 | "default": "./dist/esm/sub/index.js" 37 | } 38 | }, 39 | "./package.json": "./package.json" 40 | }, 41 | "publishConfig": { 42 | "directory": "dist", 43 | "access": "public" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-exports/src/index.ts: -------------------------------------------------------------------------------- 1 | export const someLetter = 'a'; 2 | 3 | export default { b: 'c' }; 4 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-exports/src/sub/index.ts: -------------------------------------------------------------------------------- 1 | export const someOtherLetter = 'd'; 2 | 3 | export default { e: 'f' }; 4 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-exports/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "skipLibCheck": true, 5 | "declaration": true, 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monorepo-pnpm", 3 | "private": true 4 | } 5 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/packages/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 14.0.0" 6 | }, 7 | "main": "dist/esm/index.js", 8 | "typings": "dist/typings/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "require": { 12 | "types": "./dist/typings/index.d.cts", 13 | "default": "./dist/cjs/index.js" 14 | }, 15 | "import": { 16 | "types": "./dist/typings/index.d.ts", 17 | "default": "./dist/esm/index.js" 18 | }, 19 | "default": { 20 | "types": "./dist/typings/index.d.ts", 21 | "default": "./dist/esm/index.js" 22 | } 23 | }, 24 | "./*": { 25 | "require": { 26 | "types": "./dist/typings/*.d.cts", 27 | "default": "./dist/cjs/*.js" 28 | }, 29 | "import": { 30 | "types": "./dist/typings/*.d.ts", 31 | "default": "./dist/esm/*.js" 32 | }, 33 | "default": { 34 | "types": "./dist/typings/*.d.ts", 35 | "default": "./dist/esm/*.js" 36 | } 37 | }, 38 | "./package.json": "./package.json" 39 | }, 40 | "publishConfig": { 41 | "directory": "dist", 42 | "access": "public" 43 | }, 44 | "buildOptions": { 45 | "input": "./src/index.ts" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/packages/a/src/index.ts: -------------------------------------------------------------------------------- 1 | export const a = 'WUP'; 2 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/packages/b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "b", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 14.0.0" 6 | }, 7 | "bin": { 8 | "bbb": "dist/cjs/log-the-world.js" 9 | }, 10 | "main": "dist/esm/index.js", 11 | "typings": "dist/typings/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": { 15 | "types": "./dist/typings/index.d.cts", 16 | "default": "./dist/cjs/index.js" 17 | }, 18 | "import": { 19 | "types": "./dist/typings/index.d.ts", 20 | "default": "./dist/esm/index.js" 21 | }, 22 | "default": { 23 | "types": "./dist/typings/index.d.ts", 24 | "default": "./dist/esm/index.js" 25 | } 26 | }, 27 | "./foo": { 28 | "require": { 29 | "types": "./dist/typings/foo.d.cts", 30 | "default": "./dist/cjs/foo.js" 31 | }, 32 | "import": { 33 | "types": "./dist/typings/foo.d.ts", 34 | "default": "./dist/esm/foo.js" 35 | }, 36 | "default": { 37 | "types": "./dist/typings/foo.d.ts", 38 | "default": "./dist/esm/foo.js" 39 | } 40 | }, 41 | "./package.json": "./package.json" 42 | }, 43 | "publishConfig": { 44 | "directory": "dist", 45 | "access": "public" 46 | }, 47 | "buildOptions": { 48 | "input": "./src/index.ts" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/packages/b/src/foo.ts: -------------------------------------------------------------------------------- 1 | export const b = 'SUP'; 2 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/packages/b/src/index.ts: -------------------------------------------------------------------------------- 1 | import { b as a } from './foo.js'; 2 | 3 | export * from './foo.js'; 4 | export const b = 'SUP' + a; 5 | 6 | export function foo() { 7 | return import('./foo.js'); 8 | } 9 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/packages/b/src/log-the-world.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as foo from './foo.js'; 3 | import * as index from './index.js'; 4 | 5 | console.log(foo, index); 6 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/packages/c/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "c", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 14.0.0" 6 | }, 7 | "main": "dist/esm/index.js", 8 | "typings": "dist/typings/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "require": { 12 | "types": "./dist/typings/index.d.cts", 13 | "default": "./dist/cjs/index.js" 14 | }, 15 | "import": { 16 | "types": "./dist/typings/index.d.ts", 17 | "default": "./dist/esm/index.js" 18 | }, 19 | "default": { 20 | "types": "./dist/typings/index.d.ts", 21 | "default": "./dist/esm/index.js" 22 | } 23 | }, 24 | "./package.json": "./package.json" 25 | }, 26 | "publishConfig": { 27 | "directory": "dist", 28 | "access": "public" 29 | }, 30 | "buildOptions": { 31 | "input": "./src/index.ts" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/packages/c/src/index.ts: -------------------------------------------------------------------------------- 1 | export type SomeType = 'type'; 2 | 3 | export interface SomeInterface {} 4 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo-pnpm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "dist", 5 | "target": "esnext", 6 | "module": "node16", 7 | "moduleResolution": "node16", 8 | "lib": ["es6", "esnext", "es2015", "dom", "webworker"], 9 | "declaration": true, 10 | "strict": true, 11 | "skipLibCheck": true, 12 | "paths": {}, 13 | "sourceMap": false 14 | }, 15 | "include": ["packages"], 16 | "exclude": ["**/dist", "**/temp"] 17 | } 18 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monorepo", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo/packages/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 14.0.0" 6 | }, 7 | "main": "dist/esm/index.js", 8 | "typings": "dist/typings/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "require": { 12 | "types": "./dist/typings/index.d.cts", 13 | "default": "./dist/cjs/index.js" 14 | }, 15 | "import": { 16 | "types": "./dist/typings/index.d.ts", 17 | "default": "./dist/esm/index.js" 18 | }, 19 | "default": { 20 | "types": "./dist/typings/index.d.ts", 21 | "default": "./dist/esm/index.js" 22 | } 23 | }, 24 | "./*": { 25 | "require": { 26 | "types": "./dist/typings/*.d.cts", 27 | "default": "./dist/cjs/*.js" 28 | }, 29 | "import": { 30 | "types": "./dist/typings/*.d.ts", 31 | "default": "./dist/esm/*.js" 32 | }, 33 | "default": { 34 | "types": "./dist/typings/*.d.ts", 35 | "default": "./dist/esm/*.js" 36 | } 37 | }, 38 | "./package.json": "./package.json" 39 | }, 40 | "publishConfig": { 41 | "directory": "dist", 42 | "access": "public" 43 | }, 44 | "buildOptions": { 45 | "input": "./src/index.ts" 46 | }, 47 | "typescript": { 48 | "definition": "dist/typings/index.d.ts" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo/packages/a/src/index.ts: -------------------------------------------------------------------------------- 1 | export const a = 'WUP'; 2 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo/packages/b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "b", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 14.0.0" 6 | }, 7 | "bin": { 8 | "bbb": "dist/cjs/log-the-world.js" 9 | }, 10 | "main": "dist/esm/index.js", 11 | "typings": "dist/typings/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": { 15 | "types": "./dist/typings/index.d.cts", 16 | "default": "./dist/cjs/index.js" 17 | }, 18 | "import": { 19 | "types": "./dist/typings/index.d.ts", 20 | "default": "./dist/esm/index.js" 21 | }, 22 | "default": { 23 | "types": "./dist/typings/index.d.ts", 24 | "default": "./dist/esm/index.js" 25 | } 26 | }, 27 | "./foo": { 28 | "require": { 29 | "types": "./dist/typings/foo.d.cts", 30 | "default": "./dist/cjs/foo.js" 31 | }, 32 | "import": { 33 | "types": "./dist/typings/foo.d.ts", 34 | "default": "./dist/esm/foo.js" 35 | }, 36 | "default": { 37 | "types": "./dist/typings/foo.d.ts", 38 | "default": "./dist/esm/foo.js" 39 | } 40 | }, 41 | "./package.json": "./package.json" 42 | }, 43 | "publishConfig": { 44 | "directory": "dist", 45 | "access": "public" 46 | }, 47 | "buildOptions": { 48 | "input": "./src/index.ts" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo/packages/b/src/foo.ts: -------------------------------------------------------------------------------- 1 | export const b = 'SUP'; 2 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo/packages/b/src/index.ts: -------------------------------------------------------------------------------- 1 | import { b as a } from './foo.js'; 2 | 3 | export * from './foo.js'; 4 | export const b = 'SUP' + a; 5 | 6 | export function foo() { 7 | return import('./foo.js'); 8 | } 9 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo/packages/b/src/log-the-world.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as foo from './foo.js'; 3 | import * as index from './index.js'; 4 | 5 | console.log(foo, index); 6 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo/packages/c/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "c", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 14.0.0" 6 | }, 7 | "main": "dist/esm/index.js", 8 | "typings": "dist/typings/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "require": { 12 | "types": "./dist/typings/index.d.cts", 13 | "default": "./dist/cjs/index.js" 14 | }, 15 | "import": { 16 | "types": "./dist/typings/index.d.ts", 17 | "default": "./dist/esm/index.js" 18 | }, 19 | "default": { 20 | "types": "./dist/typings/index.d.ts", 21 | "default": "./dist/esm/index.js" 22 | } 23 | }, 24 | "./package.json": "./package.json" 25 | }, 26 | "publishConfig": { 27 | "directory": "dist", 28 | "access": "public" 29 | }, 30 | "buildOptions": { 31 | "input": "./src/index.ts" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo/packages/c/src/index.ts: -------------------------------------------------------------------------------- 1 | export type SomeType = 'type'; 2 | 3 | export interface SomeInterface {} 4 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-monorepo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "dist", 5 | "target": "esnext", 6 | "module": "node16", 7 | "moduleResolution": "node16", 8 | "lib": ["es6", "esnext", "es2015", "dom", "webworker"], 9 | "declaration": true, 10 | "strict": true, 11 | "skipLibCheck": true, 12 | "paths": {}, 13 | "sourceMap": false 14 | }, 15 | "include": ["packages"], 16 | "exclude": ["**/dist", "**/temp"] 17 | } 18 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-types-only/README.md: -------------------------------------------------------------------------------- 1 | Hi types! 2 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-types-only/foo.json: -------------------------------------------------------------------------------- 1 | { "hi": 1 } 2 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-types-only/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-types-only", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 14.0.0" 6 | }, 7 | "main": "dist/esm/index.js", 8 | "typings": "dist/typings/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "require": { 12 | "types": "./dist/typings/index.d.cts", 13 | "default": "./dist/cjs/index.js" 14 | }, 15 | "import": { 16 | "types": "./dist/typings/index.d.ts", 17 | "default": "./dist/esm/index.js" 18 | }, 19 | "default": { 20 | "types": "./dist/typings/index.d.ts", 21 | "default": "./dist/esm/index.js" 22 | } 23 | }, 24 | "./package.json": "./package.json" 25 | }, 26 | "publishConfig": { 27 | "directory": "dist", 28 | "access": "public" 29 | }, 30 | "bob": { 31 | "build": { 32 | "copy": [ 33 | "foo.json", 34 | "src/style.css" 35 | ] 36 | }, 37 | "check": { 38 | "skip": [ 39 | "./file-that-throws" 40 | ] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-types-only/src/index.ts: -------------------------------------------------------------------------------- 1 | export type SomeType = 'type'; 2 | 3 | export interface SomeInterface {} 4 | -------------------------------------------------------------------------------- /test/__fixtures__/simple-types-only/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "skipLibCheck": true, 5 | "declaration": true, 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/__fixtures__/simple/README.md: -------------------------------------------------------------------------------- 1 | Hello! 2 | -------------------------------------------------------------------------------- /test/__fixtures__/simple/foo.json: -------------------------------------------------------------------------------- 1 | { "hi": 1 } 2 | -------------------------------------------------------------------------------- /test/__fixtures__/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 12.0.0", 6 | "pnpm": ">= 8.0.0" 7 | }, 8 | "main": "dist/esm/index.js", 9 | "typings": "dist/typings/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "require": { 13 | "types": "./dist/typings/index.d.cts", 14 | "default": "./dist/cjs/index.js" 15 | }, 16 | "import": { 17 | "types": "./dist/typings/index.d.ts", 18 | "default": "./dist/esm/index.js" 19 | }, 20 | "default": { 21 | "types": "./dist/typings/index.d.ts", 22 | "default": "./dist/esm/index.js" 23 | } 24 | }, 25 | "./*": { 26 | "require": { 27 | "types": "./dist/typings/*.d.cts", 28 | "default": "./dist/cjs/*.js" 29 | }, 30 | "import": { 31 | "types": "./dist/typings/*.d.ts", 32 | "default": "./dist/esm/*.js" 33 | }, 34 | "default": { 35 | "types": "./dist/typings/*.d.ts", 36 | "default": "./dist/esm/*.js" 37 | } 38 | }, 39 | "./package.json": "./package.json", 40 | "./style.css": "./dist/esm/style.css" 41 | }, 42 | "publishConfig": { 43 | "directory": "dist", 44 | "access": "public" 45 | }, 46 | "bob": { 47 | "build": { 48 | "copy": [ 49 | "foo.json", 50 | "src/style.css" 51 | ] 52 | }, 53 | "check": { 54 | "skip": [ 55 | "./file-that-throws" 56 | ] 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/__fixtures__/simple/src/file-that-throws.ts: -------------------------------------------------------------------------------- 1 | throw new Error('KEK'); 2 | -------------------------------------------------------------------------------- /test/__fixtures__/simple/src/index.ts: -------------------------------------------------------------------------------- 1 | export const someNumber = 1; 2 | 3 | export default 'kek'; 4 | -------------------------------------------------------------------------------- /test/__fixtures__/simple/src/style.css: -------------------------------------------------------------------------------- 1 | .foo { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/__fixtures__/simple/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "skipLibCheck": true, 5 | "declaration": true, 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/__fixtures__/tsconfig-build-json/README.md: -------------------------------------------------------------------------------- 1 | Hello! 2 | -------------------------------------------------------------------------------- /test/__fixtures__/tsconfig-build-json/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig-build-json", 3 | "type": "module", 4 | "engines": { 5 | "node": ">= 14.0.0" 6 | }, 7 | "main": "dist/esm/index.js", 8 | "typings": "dist/typings/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "require": { 12 | "types": "./dist/typings/index.d.cts", 13 | "default": "./dist/cjs/index.js" 14 | }, 15 | "import": { 16 | "types": "./dist/typings/index.d.ts", 17 | "default": "./dist/esm/index.js" 18 | }, 19 | "default": { 20 | "types": "./dist/typings/index.d.ts", 21 | "default": "./dist/esm/index.js" 22 | } 23 | }, 24 | "./*": { 25 | "require": { 26 | "types": "./dist/typings/*.d.cts", 27 | "default": "./dist/cjs/*.js" 28 | }, 29 | "import": { 30 | "types": "./dist/typings/*.d.ts", 31 | "default": "./dist/esm/*.js" 32 | }, 33 | "default": { 34 | "types": "./dist/typings/*.d.ts", 35 | "default": "./dist/esm/*.js" 36 | } 37 | }, 38 | "./package.json": "./package.json", 39 | "./style.css": "./dist/esm/style.css" 40 | }, 41 | "publishConfig": { 42 | "directory": "dist", 43 | "access": "public" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/__fixtures__/tsconfig-build-json/src/index.ts: -------------------------------------------------------------------------------- 1 | export const hello = 1; 2 | 3 | export default 'there'; 4 | -------------------------------------------------------------------------------- /test/__fixtures__/tsconfig-build-json/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "declaration": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/__fixtures__/tsconfig-build-json/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "declaration": true, 5 | "skipLibCheck": true, 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/exports.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest'; 2 | import { rewriteExports } from '../src/utils/rewrite-exports'; 3 | 4 | test('basic exports', () => { 5 | expect( 6 | rewriteExports( 7 | { 8 | '.': { 9 | require: { 10 | default: './dist/index.js', 11 | types: './dist/index.d.ts', 12 | }, 13 | import: { 14 | default: './dist/index.mjs', 15 | types: './dist/index.d.ts', 16 | }, 17 | }, 18 | './*': { 19 | require: { 20 | default: './dist/*.js', 21 | types: './dist/*.d.ts', 22 | }, 23 | import: { 24 | default: './dist/*.mjs', 25 | types: './dist/*.d.ts', 26 | }, 27 | }, 28 | }, 29 | 'dist', 30 | false, 31 | ), 32 | ).toStrictEqual({ 33 | '.': { 34 | require: { 35 | default: './index.js', 36 | types: './index.d.ts', 37 | }, 38 | import: { 39 | default: './index.mjs', 40 | types: './index.d.ts', 41 | }, 42 | default: { 43 | default: './index.mjs', 44 | types: './index.d.ts', 45 | }, 46 | }, 47 | './*': { 48 | require: { 49 | default: './*.js', 50 | types: './*.d.ts', 51 | }, 52 | import: { 53 | default: './*.mjs', 54 | types: './*.d.ts', 55 | }, 56 | default: { 57 | default: './*.mjs', 58 | types: './*.d.ts', 59 | }, 60 | }, 61 | }); 62 | }); 63 | 64 | test('with custom exports', () => { 65 | expect( 66 | rewriteExports( 67 | { 68 | '.': { 69 | require: { 70 | default: './dist/index.js', 71 | types: './dist/index.d.ts', 72 | }, 73 | import: { 74 | default: './dist/index.mjs', 75 | types: './dist/index.d.ts', 76 | }, 77 | }, 78 | './*': { 79 | require: { 80 | default: './dist/*.js', 81 | types: './dist/*.d.ts', 82 | }, 83 | import: { 84 | default: './dist/*.mjs', 85 | types: './dist/*.d.ts', 86 | }, 87 | }, 88 | './utils': { 89 | require: { 90 | default: './dist/utils/index.js', 91 | types: './dist/utils/index.d.ts', 92 | }, 93 | import: { 94 | default: './dist/utils/index.mjs', 95 | types: './dist/utils/index.d.ts', 96 | }, 97 | }, 98 | }, 99 | 'dist', 100 | false, 101 | ), 102 | ).toStrictEqual({ 103 | '.': { 104 | require: { 105 | default: './index.js', 106 | types: './index.d.ts', 107 | }, 108 | import: { 109 | default: './index.mjs', 110 | types: './index.d.ts', 111 | }, 112 | default: { 113 | default: './index.mjs', 114 | types: './index.d.ts', 115 | }, 116 | }, 117 | './*': { 118 | require: { 119 | default: './*.js', 120 | types: './*.d.ts', 121 | }, 122 | import: { 123 | default: './*.mjs', 124 | types: './*.d.ts', 125 | }, 126 | default: { 127 | default: './*.mjs', 128 | types: './*.d.ts', 129 | }, 130 | }, 131 | './utils': { 132 | require: { 133 | default: './utils/index.js', 134 | types: './utils/index.d.ts', 135 | }, 136 | import: { 137 | default: './utils/index.mjs', 138 | types: './utils/index.d.ts', 139 | }, 140 | default: { 141 | default: './utils/index.mjs', 142 | types: './utils/index.d.ts', 143 | }, 144 | }, 145 | }); 146 | }); 147 | 148 | test.todo('with types-only exports'); 149 | -------------------------------------------------------------------------------- /test/integration.spec.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { execa } from 'execa'; 3 | import * as fse from 'fs-extra'; 4 | import { expect, it } from 'vitest'; 5 | 6 | const fixturesFolder = path.join(__dirname, '__fixtures__'); 7 | const binaryFolder = path.join(__dirname, '..', 'dist', 'index.js'); 8 | 9 | it('can bundle a simple project', async () => { 10 | await fse.remove(path.resolve(fixturesFolder, 'simple', 'dist')); 11 | const result = await execa('node', [binaryFolder, 'build'], { 12 | cwd: path.resolve(fixturesFolder, 'simple'), 13 | }); 14 | expect(result.exitCode).toEqual(0); 15 | const baseDistPath = path.resolve(fixturesFolder, 'simple', 'dist'); 16 | const indexJsFilePath = path.resolve(baseDistPath, 'cjs', 'index.js'); 17 | const indexDtsFilePath = path.resolve(baseDistPath, 'typings', 'index.d.ts'); 18 | const indexMjsFilePath = path.resolve(baseDistPath, 'esm', 'index.js'); 19 | const packageJsonFilePath = path.resolve(baseDistPath, 'package.json'); 20 | const readmeFilePath = path.resolve(baseDistPath, 'README.md'); 21 | const fooFilePath = path.resolve(baseDistPath, 'foo.json'); 22 | 23 | expect(await fse.readFile(indexJsFilePath, 'utf8')).toMatchInlineSnapshot(` 24 | "use strict"; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | exports.someNumber = void 0; 27 | exports.someNumber = 1; 28 | exports.default = 'kek'; 29 | `); 30 | expect(await fse.readFile(indexDtsFilePath, 'utf8')).toMatchInlineSnapshot(` 31 | export declare const someNumber = 1; 32 | declare const _default: "kek"; 33 | export default _default; 34 | `); 35 | expect(await fse.readFile(indexMjsFilePath, 'utf8')).toMatchInlineSnapshot(` 36 | export var someNumber = 1; 37 | export default 'kek'; 38 | `); 39 | expect(await fse.readFile(readmeFilePath, 'utf8')).toMatchInlineSnapshot('Hello!'); 40 | expect(await fse.readFile(fooFilePath, 'utf8')).toMatchInlineSnapshot('{ "hi": 1 }'); 41 | expect(await fse.readFile(packageJsonFilePath, 'utf8')).toMatchInlineSnapshot(` 42 | { 43 | "name": "simple", 44 | "engines": { 45 | "node": ">= 12.0.0" 46 | }, 47 | "main": "esm/index.js", 48 | "typings": "typings/index.d.ts", 49 | "type": "module", 50 | "exports": { 51 | ".": { 52 | "require": { 53 | "types": "./typings/index.d.cts", 54 | "default": "./cjs/index.js" 55 | }, 56 | "import": { 57 | "types": "./typings/index.d.ts", 58 | "default": "./esm/index.js" 59 | }, 60 | "default": { 61 | "types": "./typings/index.d.ts", 62 | "default": "./esm/index.js" 63 | } 64 | }, 65 | "./*": { 66 | "require": { 67 | "types": "./typings/*.d.cts", 68 | "default": "./cjs/*.js" 69 | }, 70 | "import": { 71 | "types": "./typings/*.d.ts", 72 | "default": "./esm/*.js" 73 | }, 74 | "default": { 75 | "types": "./typings/*.d.ts", 76 | "default": "./esm/*.js" 77 | } 78 | }, 79 | "./package.json": "./package.json", 80 | "./style.css": "./esm/style.css" 81 | } 82 | } 83 | `); 84 | await execa('node', [binaryFolder, 'check'], { 85 | cwd: path.resolve(fixturesFolder, 'simple'), 86 | }); 87 | }); 88 | 89 | it('can build a monorepo project', async () => { 90 | await fse.remove(path.resolve(fixturesFolder, 'simple-monorepo', 'a', 'dist')); 91 | await fse.remove(path.resolve(fixturesFolder, 'simple-monorepo', 'b', 'dist')); 92 | const result = await execa('node', [binaryFolder, 'build'], { 93 | cwd: path.resolve(fixturesFolder, 'simple-monorepo'), 94 | }); 95 | expect(result.exitCode).toEqual(0); 96 | const baseDistAPath = path.resolve(fixturesFolder, 'simple-monorepo', 'packages', 'a', 'dist'); 97 | const baseDistBPath = path.resolve(fixturesFolder, 'simple-monorepo', 'packages', 'b', 'dist'); 98 | const baseDistCPath = path.resolve(fixturesFolder, 'simple-monorepo', 'packages', 'c', 'dist'); 99 | // prettier-ignore 100 | const files = { 101 | a: { 102 | "cjs/index.js": path.resolve(baseDistAPath, "cjs", "index.js"), 103 | "typings/index.d.ts": path.resolve(baseDistAPath, "typings", "index.d.ts"), 104 | "esm/index.js": path.resolve(baseDistAPath, "esm", "index.js"), 105 | "package.json": path.resolve(baseDistAPath, "package.json"), 106 | }, 107 | b: { 108 | "cjs/index.js": path.resolve(baseDistBPath, "cjs", "index.js"), 109 | "typings/index.d.ts": path.resolve(baseDistBPath, "typings", "index.d.ts"), 110 | "esm/index.js": path.resolve(baseDistBPath, "esm", "index.js"), 111 | "package.json": path.resolve(baseDistBPath, "package.json"), 112 | }, 113 | c: { 114 | "cjs/index.js": path.resolve(baseDistCPath, "cjs", "index.js"), 115 | "esm/index.js": path.resolve(baseDistCPath, "esm", "index.js"), 116 | "typings/index.d.ts": path.resolve(baseDistCPath, "typings", "index.d.ts"), 117 | "package.json": path.resolve(baseDistCPath, "package.json"), 118 | }, 119 | } as const; 120 | 121 | expect(await fse.readFile(files.a['cjs/index.js'], 'utf8')).toMatchInlineSnapshot(` 122 | "use strict"; 123 | Object.defineProperty(exports, "__esModule", { value: true }); 124 | exports.a = void 0; 125 | exports.a = 'WUP'; 126 | `); 127 | expect(await fse.readFile(files.a['typings/index.d.ts'], 'utf8')).toMatchInlineSnapshot( 128 | 'export declare const a = "WUP";', 129 | ); 130 | expect(await fse.readFile(files.a['esm/index.js'], 'utf8')).toMatchInlineSnapshot( 131 | "export const a = 'WUP';", 132 | ); 133 | expect(await fse.readFile(files.a['package.json'], 'utf8')).toMatchInlineSnapshot(` 134 | { 135 | "name": "a", 136 | "engines": { 137 | "node": ">= 14.0.0" 138 | }, 139 | "main": "esm/index.js", 140 | "typings": "typings/index.d.ts", 141 | "type": "module", 142 | "exports": { 143 | ".": { 144 | "require": { 145 | "types": "./typings/index.d.cts", 146 | "default": "./cjs/index.js" 147 | }, 148 | "import": { 149 | "types": "./typings/index.d.ts", 150 | "default": "./esm/index.js" 151 | }, 152 | "default": { 153 | "types": "./typings/index.d.ts", 154 | "default": "./esm/index.js" 155 | } 156 | }, 157 | "./*": { 158 | "require": { 159 | "types": "./typings/*.d.cts", 160 | "default": "./cjs/*.js" 161 | }, 162 | "import": { 163 | "types": "./typings/*.d.ts", 164 | "default": "./esm/*.js" 165 | }, 166 | "default": { 167 | "types": "./typings/*.d.ts", 168 | "default": "./esm/*.js" 169 | } 170 | }, 171 | "./package.json": "./package.json" 172 | } 173 | } 174 | `); 175 | 176 | expect(await fse.readFile(files.b['cjs/index.js'], 'utf8')).toMatchInlineSnapshot(` 177 | "use strict"; 178 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 179 | if (k2 === undefined) k2 = k; 180 | var desc = Object.getOwnPropertyDescriptor(m, k); 181 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 182 | desc = { enumerable: true, get: function() { return m[k]; } }; 183 | } 184 | Object.defineProperty(o, k2, desc); 185 | }) : (function(o, m, k, k2) { 186 | if (k2 === undefined) k2 = k; 187 | o[k2] = m[k]; 188 | })); 189 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 190 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 191 | }; 192 | Object.defineProperty(exports, "__esModule", { value: true }); 193 | exports.b = void 0; 194 | exports.foo = foo; 195 | const foo_js_1 = require("./foo.js"); 196 | __exportStar(require("./foo.js"), exports); 197 | exports.b = 'SUP' + foo_js_1.b; 198 | function foo() { 199 | return import('./foo.js'); 200 | } 201 | `); 202 | expect(await fse.readFile(files.b['typings/index.d.ts'], 'utf8')).toMatchInlineSnapshot(` 203 | export * from './foo.js'; 204 | export declare const b: string; 205 | export declare function foo(): Promise; 206 | `); 207 | expect(await fse.readFile(files.b['esm/index.js'], 'utf8')).toMatchInlineSnapshot(` 208 | import { b as a } from './foo.js'; 209 | export * from './foo.js'; 210 | export const b = 'SUP' + a; 211 | export function foo() { 212 | return import('./foo.js'); 213 | } 214 | `); 215 | expect(await fse.readFile(files.b['package.json'], 'utf8')).toMatchInlineSnapshot(` 216 | { 217 | "name": "b", 218 | "engines": { 219 | "node": ">= 14.0.0" 220 | }, 221 | "main": "esm/index.js", 222 | "typings": "typings/index.d.ts", 223 | "type": "module", 224 | "exports": { 225 | ".": { 226 | "require": { 227 | "types": "./typings/index.d.cts", 228 | "default": "./cjs/index.js" 229 | }, 230 | "import": { 231 | "types": "./typings/index.d.ts", 232 | "default": "./esm/index.js" 233 | }, 234 | "default": { 235 | "types": "./typings/index.d.ts", 236 | "default": "./esm/index.js" 237 | } 238 | }, 239 | "./foo": { 240 | "require": { 241 | "types": "./typings/foo.d.cts", 242 | "default": "./cjs/foo.js" 243 | }, 244 | "import": { 245 | "types": "./typings/foo.d.ts", 246 | "default": "./esm/foo.js" 247 | }, 248 | "default": { 249 | "types": "./typings/foo.d.ts", 250 | "default": "./esm/foo.js" 251 | } 252 | }, 253 | "./package.json": "./package.json" 254 | }, 255 | "bin": { 256 | "bbb": "cjs/log-the-world.js" 257 | } 258 | } 259 | `); 260 | 261 | expect(await fse.readFile(files.c['cjs/index.js'], 'utf8')).toMatchInlineSnapshot(''); 262 | expect(await fse.readFile(files.c['esm/index.js'], 'utf8')).toMatchInlineSnapshot(''); 263 | expect(await fse.readFile(files.c['typings/index.d.ts'], 'utf8')).toMatchInlineSnapshot(` 264 | export type SomeType = 'type'; 265 | export interface SomeInterface { 266 | } 267 | `); 268 | expect(await fse.readFile(files.c['package.json'], 'utf8')).toMatchInlineSnapshot(` 269 | { 270 | "name": "c", 271 | "engines": { 272 | "node": ">= 14.0.0" 273 | }, 274 | "main": "esm/index.js", 275 | "typings": "typings/index.d.ts", 276 | "type": "module", 277 | "exports": { 278 | ".": { 279 | "require": { 280 | "types": "./typings/index.d.cts", 281 | "default": "./cjs/index.js" 282 | }, 283 | "import": { 284 | "types": "./typings/index.d.ts", 285 | "default": "./esm/index.js" 286 | }, 287 | "default": { 288 | "types": "./typings/index.d.ts", 289 | "default": "./esm/index.js" 290 | } 291 | }, 292 | "./package.json": "./package.json" 293 | } 294 | } 295 | `); 296 | 297 | await execa('node', [binaryFolder, 'check'], { 298 | cwd: path.resolve(fixturesFolder, 'simple-monorepo'), 299 | }); 300 | }); 301 | 302 | it('can build an esm only project', async () => { 303 | await fse.remove(path.resolve(fixturesFolder, 'simple-esm-only', 'dist')); 304 | const result = await execa('node', [binaryFolder, 'build'], { 305 | cwd: path.resolve(fixturesFolder, 'simple-esm-only'), 306 | }); 307 | expect(result.exitCode).toEqual(0); 308 | 309 | const baseDistPath = path.resolve(fixturesFolder, 'simple-esm-only', 'dist'); 310 | const packageJsonFilePath = path.resolve(baseDistPath, 'package.json'); 311 | const indexJsFilePath = path.resolve(baseDistPath, 'esm', 'index.js'); 312 | const indexDtsFilePath = path.resolve(baseDistPath, 'typings', 'index.d.ts'); 313 | expect(await fse.readFile(packageJsonFilePath, 'utf8')).toMatchInlineSnapshot(` 314 | { 315 | "name": "simple-esm-only", 316 | "engines": { 317 | "node": ">= 14.0.0" 318 | }, 319 | "main": "esm/index.js", 320 | "typings": "typings/index.d.ts", 321 | "type": "module", 322 | "exports": { 323 | ".": { 324 | "import": { 325 | "types": "./typings/index.d.ts", 326 | "default": "./esm/index.js" 327 | }, 328 | "default": { 329 | "types": "./typings/index.d.ts", 330 | "default": "./esm/index.js" 331 | } 332 | }, 333 | "./package.json": "./package.json" 334 | } 335 | } 336 | `); 337 | 338 | expect(await fse.readFile(indexJsFilePath, 'utf8')).toMatchInlineSnapshot( 339 | `export var someNumber = 1;`, 340 | ); 341 | expect(await fse.readFile(indexDtsFilePath, 'utf8')).toMatchInlineSnapshot( 342 | 'export declare const someNumber = 1;', 343 | ); 344 | }); 345 | 346 | it('can build a types only project', async () => { 347 | await fse.remove(path.resolve(fixturesFolder, 'simple-types-only', 'dist')); 348 | const result = await execa('node', [binaryFolder, 'build'], { 349 | cwd: path.resolve(fixturesFolder, 'simple-types-only'), 350 | }); 351 | expect(result.exitCode).toEqual(0); 352 | 353 | const baseDistPath = path.resolve(fixturesFolder, 'simple-types-only', 'dist'); 354 | 355 | // types-only adjusted package.json 356 | const packageJsonFilePath = path.resolve(baseDistPath, 'package.json'); 357 | expect(await fse.readFile(packageJsonFilePath, 'utf8')).toMatchInlineSnapshot(` 358 | { 359 | "name": "simple-types-only", 360 | "engines": { 361 | "node": ">= 14.0.0" 362 | }, 363 | "main": "esm/index.js", 364 | "typings": "typings/index.d.ts", 365 | "type": "module", 366 | "exports": { 367 | ".": { 368 | "require": { 369 | "types": "./typings/index.d.cts", 370 | "default": "./cjs/index.js" 371 | }, 372 | "import": { 373 | "types": "./typings/index.d.ts", 374 | "default": "./esm/index.js" 375 | }, 376 | "default": { 377 | "types": "./typings/index.d.ts", 378 | "default": "./esm/index.js" 379 | } 380 | }, 381 | "./package.json": "./package.json" 382 | } 383 | } 384 | `); 385 | 386 | await expect( 387 | fse.readFile(path.resolve(baseDistPath, 'cjs', 'index.js'), 'utf8'), 388 | ).resolves.toMatchInlineSnapshot(''); 389 | await expect( 390 | fse.readFile(path.resolve(baseDistPath, 'esm', 'index.js'), 'utf8'), 391 | ).resolves.toMatchInlineSnapshot(''); 392 | const indexDtsFilePath = path.resolve(baseDistPath, 'typings', 'index.d.ts'); 393 | expect(await fse.readFile(indexDtsFilePath, 'utf8')).toMatchInlineSnapshot(` 394 | export type SomeType = 'type'; 395 | export interface SomeInterface { 396 | } 397 | `); 398 | }); 399 | 400 | it('can build a monorepo pnpm project', async () => { 401 | await fse.remove(path.resolve(fixturesFolder, 'simple-monorepo-pnpm', 'a', 'dist')); 402 | await fse.remove(path.resolve(fixturesFolder, 'simple-monorepo-pnpm', 'b', 'dist')); 403 | const result = await execa('node', [binaryFolder, 'build'], { 404 | cwd: path.resolve(fixturesFolder, 'simple-monorepo-pnpm'), 405 | }); 406 | expect(result.exitCode).toEqual(0); 407 | const baseDistAPath = path.resolve( 408 | fixturesFolder, 409 | 'simple-monorepo-pnpm', 410 | 'packages', 411 | 'a', 412 | 'dist', 413 | ); 414 | const baseDistBPath = path.resolve( 415 | fixturesFolder, 416 | 'simple-monorepo-pnpm', 417 | 'packages', 418 | 'b', 419 | 'dist', 420 | ); 421 | const baseDistCPath = path.resolve( 422 | fixturesFolder, 423 | 'simple-monorepo-pnpm', 424 | 'packages', 425 | 'c', 426 | 'dist', 427 | ); 428 | // prettier-ignore 429 | const files = { 430 | a: { 431 | "cjs/index.js": path.resolve(baseDistAPath, "cjs", "index.js"), 432 | "typings/index.d.ts": path.resolve(baseDistAPath, "typings", "index.d.ts"), 433 | "esm/index.js": path.resolve(baseDistAPath, "esm", "index.js"), 434 | "package.json": path.resolve(baseDistAPath, "package.json"), 435 | }, 436 | b: { 437 | "cjs/index.js": path.resolve(baseDistBPath, "cjs", "index.js"), 438 | "typings/index.d.ts": path.resolve(baseDistBPath, "typings", "index.d.ts"), 439 | "esm/index.js": path.resolve(baseDistBPath, "esm", "index.js"), 440 | "package.json": path.resolve(baseDistBPath, "package.json"), 441 | }, 442 | c: { 443 | "cjs/index.js": path.resolve(baseDistCPath, "cjs", "index.js"), 444 | "esm/index.js": path.resolve(baseDistCPath, "esm", "index.js"), 445 | "typings/index.d.ts": path.resolve(baseDistCPath, "typings", "index.d.ts"), 446 | "package.json": path.resolve(baseDistCPath, "package.json"), 447 | }, 448 | } as const; 449 | 450 | expect(await fse.readFile(files.a['cjs/index.js'], 'utf8')).toMatchInlineSnapshot(` 451 | "use strict"; 452 | Object.defineProperty(exports, "__esModule", { value: true }); 453 | exports.a = void 0; 454 | exports.a = 'WUP'; 455 | `); 456 | expect(await fse.readFile(files.a['typings/index.d.ts'], 'utf8')).toMatchInlineSnapshot( 457 | 'export declare const a = "WUP";', 458 | ); 459 | expect(await fse.readFile(files.a['esm/index.js'], 'utf8')).toMatchInlineSnapshot( 460 | "export const a = 'WUP';", 461 | ); 462 | expect(await fse.readFile(files.a['package.json'], 'utf8')).toMatchInlineSnapshot(` 463 | { 464 | "name": "a", 465 | "engines": { 466 | "node": ">= 14.0.0" 467 | }, 468 | "main": "esm/index.js", 469 | "typings": "typings/index.d.ts", 470 | "type": "module", 471 | "exports": { 472 | ".": { 473 | "require": { 474 | "types": "./typings/index.d.cts", 475 | "default": "./cjs/index.js" 476 | }, 477 | "import": { 478 | "types": "./typings/index.d.ts", 479 | "default": "./esm/index.js" 480 | }, 481 | "default": { 482 | "types": "./typings/index.d.ts", 483 | "default": "./esm/index.js" 484 | } 485 | }, 486 | "./*": { 487 | "require": { 488 | "types": "./typings/*.d.cts", 489 | "default": "./cjs/*.js" 490 | }, 491 | "import": { 492 | "types": "./typings/*.d.ts", 493 | "default": "./esm/*.js" 494 | }, 495 | "default": { 496 | "types": "./typings/*.d.ts", 497 | "default": "./esm/*.js" 498 | } 499 | }, 500 | "./package.json": "./package.json" 501 | } 502 | } 503 | `); 504 | 505 | expect(await fse.readFile(files.b['cjs/index.js'], 'utf8')).toMatchInlineSnapshot(` 506 | "use strict"; 507 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 508 | if (k2 === undefined) k2 = k; 509 | var desc = Object.getOwnPropertyDescriptor(m, k); 510 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 511 | desc = { enumerable: true, get: function() { return m[k]; } }; 512 | } 513 | Object.defineProperty(o, k2, desc); 514 | }) : (function(o, m, k, k2) { 515 | if (k2 === undefined) k2 = k; 516 | o[k2] = m[k]; 517 | })); 518 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 519 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 520 | }; 521 | Object.defineProperty(exports, "__esModule", { value: true }); 522 | exports.b = void 0; 523 | exports.foo = foo; 524 | const foo_js_1 = require("./foo.js"); 525 | __exportStar(require("./foo.js"), exports); 526 | exports.b = 'SUP' + foo_js_1.b; 527 | function foo() { 528 | return import('./foo.js'); 529 | } 530 | `); 531 | expect(await fse.readFile(files.b['typings/index.d.ts'], 'utf8')).toMatchInlineSnapshot(` 532 | export * from './foo.js'; 533 | export declare const b: string; 534 | export declare function foo(): Promise; 535 | `); 536 | expect(await fse.readFile(files.b['esm/index.js'], 'utf8')).toMatchInlineSnapshot(` 537 | import { b as a } from './foo.js'; 538 | export * from './foo.js'; 539 | export const b = 'SUP' + a; 540 | export function foo() { 541 | return import('./foo.js'); 542 | } 543 | `); 544 | expect(await fse.readFile(files.b['package.json'], 'utf8')).toMatchInlineSnapshot(` 545 | { 546 | "name": "b", 547 | "engines": { 548 | "node": ">= 14.0.0" 549 | }, 550 | "main": "esm/index.js", 551 | "typings": "typings/index.d.ts", 552 | "type": "module", 553 | "exports": { 554 | ".": { 555 | "require": { 556 | "types": "./typings/index.d.cts", 557 | "default": "./cjs/index.js" 558 | }, 559 | "import": { 560 | "types": "./typings/index.d.ts", 561 | "default": "./esm/index.js" 562 | }, 563 | "default": { 564 | "types": "./typings/index.d.ts", 565 | "default": "./esm/index.js" 566 | } 567 | }, 568 | "./foo": { 569 | "require": { 570 | "types": "./typings/foo.d.cts", 571 | "default": "./cjs/foo.js" 572 | }, 573 | "import": { 574 | "types": "./typings/foo.d.ts", 575 | "default": "./esm/foo.js" 576 | }, 577 | "default": { 578 | "types": "./typings/foo.d.ts", 579 | "default": "./esm/foo.js" 580 | } 581 | }, 582 | "./package.json": "./package.json" 583 | }, 584 | "bin": { 585 | "bbb": "cjs/log-the-world.js" 586 | } 587 | } 588 | `); 589 | 590 | expect(await fse.readFile(files.c['cjs/index.js'], 'utf8')).toMatchInlineSnapshot(''); 591 | expect(await fse.readFile(files.c['esm/index.js'], 'utf8')).toMatchInlineSnapshot(''); 592 | expect(await fse.readFile(files.c['typings/index.d.ts'], 'utf8')).toMatchInlineSnapshot(` 593 | export type SomeType = 'type'; 594 | export interface SomeInterface { 595 | } 596 | `); 597 | expect(await fse.readFile(files.c['package.json'], 'utf8')).toMatchInlineSnapshot(` 598 | { 599 | "name": "c", 600 | "engines": { 601 | "node": ">= 14.0.0" 602 | }, 603 | "main": "esm/index.js", 604 | "typings": "typings/index.d.ts", 605 | "type": "module", 606 | "exports": { 607 | ".": { 608 | "require": { 609 | "types": "./typings/index.d.cts", 610 | "default": "./cjs/index.js" 611 | }, 612 | "import": { 613 | "types": "./typings/index.d.ts", 614 | "default": "./esm/index.js" 615 | }, 616 | "default": { 617 | "types": "./typings/index.d.ts", 618 | "default": "./esm/index.js" 619 | } 620 | }, 621 | "./package.json": "./package.json" 622 | } 623 | } 624 | `); 625 | 626 | await execa('node', [binaryFolder, 'check'], { 627 | cwd: path.resolve(fixturesFolder, 'simple-monorepo-pnpm'), 628 | }); 629 | }); 630 | 631 | it('can bundle a tsconfig-build-json project', async () => { 632 | await fse.remove(path.resolve(fixturesFolder, 'tsconfig-build-json', 'dist')); 633 | 634 | const result = await execa('node', [binaryFolder, 'build'], { 635 | cwd: path.resolve(fixturesFolder, 'tsconfig-build-json'), 636 | }); 637 | expect(result.exitCode).toEqual(0); 638 | 639 | const baseDistPath = path.resolve(fixturesFolder, 'tsconfig-build-json', 'dist'); 640 | await expect(fse.readFile(path.resolve(baseDistPath, 'package.json'), 'utf8')).resolves 641 | .toMatchInlineSnapshot(` 642 | { 643 | "name": "tsconfig-build-json", 644 | "engines": { 645 | "node": ">= 14.0.0" 646 | }, 647 | "main": "esm/index.js", 648 | "typings": "typings/index.d.ts", 649 | "type": "module", 650 | "exports": { 651 | ".": { 652 | "require": { 653 | "types": "./typings/index.d.cts", 654 | "default": "./cjs/index.js" 655 | }, 656 | "import": { 657 | "types": "./typings/index.d.ts", 658 | "default": "./esm/index.js" 659 | }, 660 | "default": { 661 | "types": "./typings/index.d.ts", 662 | "default": "./esm/index.js" 663 | } 664 | }, 665 | "./*": { 666 | "require": { 667 | "types": "./typings/*.d.cts", 668 | "default": "./cjs/*.js" 669 | }, 670 | "import": { 671 | "types": "./typings/*.d.ts", 672 | "default": "./esm/*.js" 673 | }, 674 | "default": { 675 | "types": "./typings/*.d.ts", 676 | "default": "./esm/*.js" 677 | } 678 | }, 679 | "./package.json": "./package.json", 680 | "./style.css": "./esm/style.css" 681 | } 682 | } 683 | `); 684 | await expect( 685 | fse.readFile(path.resolve(baseDistPath, 'README.md'), 'utf8'), 686 | ).resolves.toMatchInlineSnapshot('Hello!'); 687 | await expect(fse.readFile(path.resolve(baseDistPath, 'cjs', 'index.js'), 'utf8')).resolves 688 | .toMatchInlineSnapshot(` 689 | "use strict"; 690 | Object.defineProperty(exports, "__esModule", { value: true }); 691 | exports.hello = void 0; 692 | exports.hello = 1; 693 | exports.default = 'there'; 694 | `); 695 | await expect(fse.readFile(path.resolve(baseDistPath, 'esm', 'index.js'), 'utf8')).resolves 696 | .toMatchInlineSnapshot(` 697 | export var hello = 1; 698 | export default 'there'; 699 | `); 700 | 701 | // because the tsconfig.build.json has `declaration: false` 702 | await expect(fse.stat(path.resolve(baseDistPath, 'typings', 'index.d.ts'))).rejects.toThrowError( 703 | 'ENOENT: no such file or directory', 704 | ); 705 | 706 | await execa('node', [binaryFolder, 'check'], { 707 | cwd: path.resolve(fixturesFolder, 'simple'), 708 | }); 709 | }); 710 | 711 | it('can bundle a simple project with additional exports', async () => { 712 | const proj = path.join(fixturesFolder, 'simple-exports'); 713 | const dist = path.join(proj, 'dist'); 714 | 715 | await fse.remove(dist); 716 | 717 | await expect(execa('node', [binaryFolder, 'build'], { cwd: proj })).resolves.toEqual( 718 | expect.objectContaining({ 719 | exitCode: 0, 720 | }), 721 | ); 722 | 723 | await expect(fse.readFile(path.join(dist, 'cjs', 'index.js'), 'utf8')).resolves 724 | .toMatchInlineSnapshot(` 725 | "use strict"; 726 | Object.defineProperty(exports, "__esModule", { value: true }); 727 | exports.someLetter = void 0; 728 | exports.someLetter = 'a'; 729 | exports.default = { b: 'c' }; 730 | `); 731 | await expect(fse.readFile(path.join(dist, 'typings', 'index.d.ts'), 'utf8')).resolves 732 | .toMatchInlineSnapshot(` 733 | export declare const someLetter = "a"; 734 | declare const _default: { 735 | b: string; 736 | }; 737 | export default _default; 738 | `); 739 | await expect(fse.readFile(path.join(dist, 'esm', 'index.js'), 'utf8')).resolves 740 | .toMatchInlineSnapshot(` 741 | export var someLetter = 'a'; 742 | export default { b: 'c' }; 743 | `); 744 | 745 | await expect(fse.readFile(path.join(dist, 'cjs', 'sub', 'index.js'), 'utf8')).resolves 746 | .toMatchInlineSnapshot(` 747 | "use strict"; 748 | Object.defineProperty(exports, "__esModule", { value: true }); 749 | exports.someOtherLetter = void 0; 750 | exports.someOtherLetter = 'd'; 751 | exports.default = { e: 'f' }; 752 | `); 753 | await expect(fse.readFile(path.join(dist, 'typings', 'sub', 'index.d.ts'), 'utf8')).resolves 754 | .toMatchInlineSnapshot(` 755 | export declare const someOtherLetter = "d"; 756 | declare const _default: { 757 | e: string; 758 | }; 759 | export default _default; 760 | `); 761 | await expect(fse.readFile(path.join(dist, 'esm', 'sub', 'index.js'), 'utf8')).resolves 762 | .toMatchInlineSnapshot(` 763 | export var someOtherLetter = 'd'; 764 | export default { e: 'f' }; 765 | `); 766 | 767 | await expect(fse.readFile(path.join(dist, 'package.json'), 'utf8')).resolves 768 | .toMatchInlineSnapshot(` 769 | { 770 | "name": "simple-exports", 771 | "engines": { 772 | "node": ">= 12.0.0" 773 | }, 774 | "main": "esm/index.js", 775 | "typings": "typings/index.d.ts", 776 | "type": "module", 777 | "exports": { 778 | ".": { 779 | "require": { 780 | "types": "./typings/index.d.cts", 781 | "default": "./cjs/index.js" 782 | }, 783 | "import": { 784 | "types": "./typings/index.d.ts", 785 | "default": "./esm/index.js" 786 | }, 787 | "default": { 788 | "types": "./typings/index.d.ts", 789 | "default": "./esm/index.js" 790 | } 791 | }, 792 | "./sub": { 793 | "require": { 794 | "types": "./typings/sub/index.d.cts", 795 | "default": "./cjs/sub/index.js" 796 | }, 797 | "import": { 798 | "types": "./typings/sub/index.d.ts", 799 | "default": "./esm/sub/index.js" 800 | }, 801 | "default": { 802 | "types": "./typings/sub/index.d.ts", 803 | "default": "./esm/sub/index.js" 804 | } 805 | }, 806 | "./package.json": "./package.json" 807 | } 808 | } 809 | `); 810 | 811 | await execa('node', [binaryFolder, 'check'], { cwd: proj }); 812 | }); 813 | -------------------------------------------------------------------------------- /test/jest-resolver.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import jestResolver from '../jest-resolver.cjs'; 3 | 4 | describe('jest-resolver', () => { 5 | it('removes .js extension from relative paths', () => { 6 | const result = jestResolver('./foo.js', { 7 | basedir: __dirname, 8 | defaultResolver: path => path, 9 | }); 10 | expect(result).toBe('./foo'); 11 | }); 12 | it('ignores absolute paths', () => { 13 | const result = jestResolver('/Users/username/project/foo', { 14 | basedir: __dirname, 15 | defaultResolver: path => path, 16 | }); 17 | expect(result).toBe('/Users/username/project/foo'); 18 | }); 19 | it('ignores relative paths from node_modules', () => { 20 | const result = jestResolver('./foo', { 21 | basedir: '/Users/username/project/node_modules/foo', 22 | defaultResolver: path => path, 23 | }); 24 | expect(result).toBe('./foo'); 25 | }); 26 | it('ignores non-js imports', () => { 27 | const result = jestResolver('./foo.graphql', { 28 | basedir: __dirname, 29 | defaultResolver: path => path, 30 | }); 31 | expect(result).toBe('./foo.graphql'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/ts-tests/.gitignore: -------------------------------------------------------------------------------- 1 | fixture.js 2 | -------------------------------------------------------------------------------- /test/ts-tests/fixture.ts: -------------------------------------------------------------------------------- 1 | import kek, { someNumber } from 'simple'; 2 | 3 | console.log(`${kek} ${someNumber}`); 4 | -------------------------------------------------------------------------------- /test/ts-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /test/ts-tests/run-tests.mjs: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { execa } from 'execa'; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = path.dirname(__filename); 7 | 8 | async function run(commandStr) { 9 | const [command, ...args] = commandStr.split(' '); 10 | await execa(command, args); 11 | } 12 | 13 | async function main() { 14 | await run(`rm -rf ${__dirname}/node_modules`); 15 | await run(`mkdir -p ${__dirname}/node_modules`); 16 | await run(`ln -s ${__dirname}/../__fixtures__/simple/dist ${__dirname}/node_modules/simple`); 17 | 18 | await run(`pnpm tsc --project ${__dirname}/tsconfig.esnext-node.json`); 19 | await run(`pnpm tsc --project ${__dirname}/tsconfig.commonjs-node.json`); 20 | // await run(`pnpm tsc --project ${__dirname}/tsconfig.commonjs-node16.json`); 21 | // await run(`pnpm tsc --project ${__dirname}/tsconfig.commonjs-nodenext.json`); 22 | await run(`pnpm tsc --project ${__dirname}/tsconfig.node16-node16.json`); 23 | await run(`pnpm tsc --project ${__dirname}/tsconfig.nodenext-nodenext.json`); 24 | } 25 | 26 | main(); 27 | -------------------------------------------------------------------------------- /test/ts-tests/tsconfig.commonjs-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["fixture.ts"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "module": "CommonJS", 6 | "moduleResolution": "Node" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/ts-tests/tsconfig.commonjs-node16.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["fixture.ts"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "module": "CommonJS", 6 | "moduleResolution": "Node16" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/ts-tests/tsconfig.commonjs-nodenext.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["fixture.ts"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "module": "CommonJS", 6 | "moduleResolution": "NodeNext" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/ts-tests/tsconfig.esnext-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["fixture.ts"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/ts-tests/tsconfig.node16-node16.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["fixture.ts"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "module": "Node16", 6 | "moduleResolution": "Node16" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/ts-tests/tsconfig.nodenext-nodenext.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["fixture.ts"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "module": "NodeNext", 6 | "moduleResolution": "NodeNext" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2020", 4 | "strictNullChecks": true, 5 | "target": "es2018", 6 | "lib": ["esnext"], 7 | "outDir": "dist", 8 | "declaration": true, 9 | "declarationMap": true, 10 | "importHelpers": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "strict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "skipLibCheck": true 17 | }, 18 | "exclude": ["node_modules", "dist"], 19 | "include": ["src/index.ts", "src/typings.d.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | testTimeout: 15_000, 6 | setupFiles: './serializer.ts', 7 | }, 8 | }); 9 | --------------------------------------------------------------------------------