Skip to content

Commit 26d7234

Browse files
authored
feat(packaging): native-Node-resolvable ESM (extensions, type:module, exports) + CI (#2773)
1 parent 357796f commit 26d7234

19 files changed

Lines changed: 5676 additions & 12267 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Validate ESM Packaging
2+
3+
# Guards the ESM packaging contract from https://github.com/cornerstonejs/cornerstone3D/issues/2763
4+
# from regressing: every published @cornerstonejs/* package must ship ESM that
5+
# resolves in bundlers AND native Node (correct `.js`/`.d.ts` extensions,
6+
# `type:module`, clean publint + @arethetypeswrong). Builds once on Node 24,
7+
# then validates on Node 24 and 25 — Node >=25 hard-fails on missing extensions,
8+
# so it is the canary for this class of bug.
9+
10+
on:
11+
pull_request:
12+
workflow_dispatch:
13+
14+
jobs:
15+
build-packages:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v6.0.3
22+
with:
23+
persist-credentials: false
24+
25+
- name: Setup pnpm
26+
uses: pnpm/action-setup@v6.0.8
27+
28+
- name: Setup Node.js 24
29+
uses: actions/setup-node@v6.4.0
30+
with:
31+
node-version: '24'
32+
cache: 'pnpm'
33+
34+
- name: Install dependencies
35+
run: pnpm install --frozen-lockfile
36+
37+
- name: Build packages
38+
run: pnpm run build
39+
40+
- name: Archive built ESM output
41+
run: tar -czf built-esm-packages.tgz packages/*/dist/esm
42+
43+
- name: Upload built ESM output
44+
uses: actions/upload-artifact@v7.0.1
45+
with:
46+
name: built-esm-packages
47+
path: built-esm-packages.tgz
48+
if-no-files-found: error
49+
50+
validate-packaging:
51+
needs: build-packages
52+
runs-on: ubuntu-latest
53+
permissions:
54+
contents: read
55+
strategy:
56+
fail-fast: false
57+
matrix:
58+
node-version: ['24', '25']
59+
steps:
60+
- name: Checkout code
61+
uses: actions/checkout@v6.0.3
62+
with:
63+
persist-credentials: false
64+
65+
- name: Setup pnpm
66+
uses: pnpm/action-setup@v6.0.8
67+
68+
- name: Setup Node.js ${{ matrix.node-version }}
69+
uses: actions/setup-node@v6.4.0
70+
with:
71+
node-version: ${{ matrix.node-version }}
72+
cache: 'pnpm'
73+
74+
- name: Install dependencies
75+
run: pnpm install --frozen-lockfile
76+
77+
- name: Download built ESM output
78+
uses: actions/download-artifact@v8.0.1
79+
with:
80+
name: built-esm-packages
81+
path: .
82+
83+
- name: Restore built ESM output
84+
run: tar -xzf built-esm-packages.tgz
85+
86+
- name: Validate ESM packaging (extensions, publint, attw, native-Node import)
87+
run: pnpm run validate:packaging

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"serve-static-examples": "pnpm dlx serve .static-examples --listen 3333",
2525
"build-and-serve-static-examples": "cross-env NODE_OPTIONS=--max_old_space_size=32896 pnpm run build-all-examples && pnpm run serve-static-examples",
2626
"build:all": "pnpm -r --filter=!docs run build:all",
27+
"validate:packaging": "node scripts/validate-esm-packaging.mjs",
2728
"dev": "pnpm run all-examples",
2829
"docs": "pnpm -r run docs",
2930
"docs:watch": "pnpm -r run docs:watch",
@@ -58,6 +59,7 @@
5859
"webpack:watch": "pnpm -r --filter=!docs run webpack:watch"
5960
},
6061
"devDependencies": {
62+
"@arethetypeswrong/cli": "0.18.4",
6163
"@babel/core": "7.26.10",
6264
"@babel/plugin-external-helpers": "7.25.9",
6365
"@babel/plugin-proposal-class-properties": "7.18.6",
@@ -144,6 +146,7 @@
144146
"postcss-loader": "7.3.4",
145147
"postcss-preset-env": "8.5.1",
146148
"prettier": "3.6.2",
149+
"publint": "0.3.21",
147150
"puppeteer": "13.7.0",
148151
"resemblejs": "5.0.0",
149152
"rollup": "3.30.0",
@@ -158,6 +161,7 @@
158161
"stylelint-config-recommended": "12.0.0",
159162
"ts-loader": "9.5.1",
160163
"ts-node": "10.9.2",
164+
"tsc-alias": "1.8.17",
161165
"typescript": "5.5.4",
162166
"url-loader": "4.1.1",
163167
"vitest": "4.1.8",

packages/adapters/package.json

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,55 +12,65 @@
1212
},
1313
"exports": {
1414
".": {
15+
"types": "./dist/esm/index.d.ts",
1516
"import": "./dist/esm/index.js",
16-
"types": "./dist/esm/index.d.ts"
17+
"default": "./dist/esm/index.js"
1718
},
1819
"./cornerstone": {
20+
"types": "./dist/esm/adapters/Cornerstone/index.d.ts",
1921
"import": "./dist/esm/adapters/Cornerstone/index.js",
20-
"types": "./dist/esm/adapters/Cornerstone/index.d.ts"
22+
"default": "./dist/esm/adapters/Cornerstone/index.js"
2123
},
2224
"./cornerstone/*": {
25+
"types": "./dist/esm/adapters/Cornerstone/*.d.ts",
2326
"import": "./dist/esm/adapters/Cornerstone/*.js",
24-
"types": "./dist/esm/adapters/Cornerstone/*.d.ts"
27+
"default": "./dist/esm/adapters/Cornerstone/*.js"
2528
},
2629
"./cornerstone3D": {
30+
"types": "./dist/esm/adapters/Cornerstone3D/index.d.ts",
2731
"import": "./dist/esm/adapters/Cornerstone3D/index.js",
28-
"types": "./dist/esm/adapters/Cornerstone3D/index.d.ts"
32+
"default": "./dist/esm/adapters/Cornerstone3D/index.js"
2933
},
3034
"./cornerstone3D/*": {
35+
"types": "./dist/esm/adapters/Cornerstone3D/*.d.ts",
3136
"import": "./dist/esm/adapters/Cornerstone3D/*.js",
32-
"types": "./dist/esm/adapters/Cornerstone3D/*.d.ts"
37+
"default": "./dist/esm/adapters/Cornerstone3D/*.js"
3338
},
3439
"./enums": {
40+
"types": "./dist/esm/adapters/enums/index.d.ts",
3541
"import": "./dist/esm/adapters/enums/index.js",
36-
"types": "./dist/esm/adapters/enums/index.d.ts"
42+
"default": "./dist/esm/adapters/enums/index.js"
3743
},
3844
"./enums/*": {
45+
"types": "./dist/esm/adapters/enums/*.d.ts",
3946
"import": "./dist/esm/adapters/enums/*.js",
40-
"types": "./dist/esm/adapters/enums/*.d.ts"
47+
"default": "./dist/esm/adapters/enums/*.js"
4148
},
4249
"./helpers": {
50+
"types": "./dist/esm/adapters/helpers/index.d.ts",
4351
"import": "./dist/esm/adapters/helpers/index.js",
44-
"types": "./dist/esm/adapters/helpers/index.d.ts"
52+
"default": "./dist/esm/adapters/helpers/index.js"
4553
},
4654
"./helpers/*": {
55+
"types": "./dist/esm/adapters/helpers/*.d.ts",
4756
"import": "./dist/esm/adapters/helpers/*.js",
48-
"types": "./dist/esm/adapters/helpers/*.d.ts"
57+
"default": "./dist/esm/adapters/helpers/*.js"
4958
},
5059
"./version": {
51-
"node": "./dist/esm/version.js",
60+
"types": "./dist/esm/version.d.ts",
5261
"import": "./dist/esm/version.js",
53-
"types": "./dist/esm/version.d.ts"
54-
}
62+
"default": "./dist/esm/version.js"
63+
},
64+
"./package.json": "./package.json"
5565
},
5666
"publishConfig": {
5767
"access": "public"
5868
},
5969
"scripts": {
6070
"test": "jest --testTimeout 60000",
61-
"build": "rollup -c rollup.config.mjs",
62-
"build:esm": "pnpm run prebuild && rollup -c rollup.config.mjs",
63-
"build:esm:watch": "rollup --watch -c rollup.config.mjs",
71+
"build": "rollup -c rollup.config.mjs && tsc-alias -p ./tsconfig.json --resolve-full-paths && node ../../scripts/write-dist-esm-package-json.js ./",
72+
"build:esm": "pnpm run prebuild && rollup -c rollup.config.mjs && tsc-alias -p ./tsconfig.json --resolve-full-paths && node ../../scripts/write-dist-esm-package-json.js ./",
73+
"build:esm:watch": "sh -c 'tsc-alias -p ./tsconfig.json --resolve-full-paths --watch & alias_pid=$!; node ../../scripts/write-dist-esm-package-json.js ./ --watch & writer_pid=$!; trap \"kill $alias_pid $writer_pid\" EXIT; rollup --watch -c rollup.config.mjs'",
6474
"clean": "rm -rf node_modules/.cache/storybook && shx rm -rf dist",
6575
"clean:deep": "pnpm run clean && shx rm -rf node_modules",
6676
"dev": "rollup --watch -c rollup.config.mjs",

packages/adapters/rollup.config.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ export default [
3030
// ESM configuration
3131
{
3232
external: isExternal,
33-
input: pkg.src || 'src/index.ts',
33+
// version.ts is not reachable from index.ts, but the package exposes a
34+
// "./version" subpath export. With preserveModules, rollup only emits modules
35+
// in the graph, so list it as an explicit input to emit dist/esm/version.js.
36+
input: [pkg.src || 'src/index.ts', 'src/version.ts'],
3437
output: [
3538
{
3639
dir: 'dist/esm',

packages/ai/package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@
1212
},
1313
"exports": {
1414
".": {
15+
"types": "./dist/esm/index.d.ts",
1516
"import": "./dist/esm/index.js",
16-
"types": "./dist/esm/index.d.ts"
17+
"default": "./dist/esm/index.js"
1718
},
1819
"./version": {
19-
"node": "./dist/esm/version.js",
20+
"types": "./dist/esm/version.d.ts",
2021
"import": "./dist/esm/version.js",
21-
"types": "./dist/esm/version.d.ts"
22-
}
22+
"default": "./dist/esm/version.js"
23+
},
24+
"./package.json": "./package.json"
2325
},
2426
"publishConfig": {
2527
"access": "public"
@@ -30,8 +32,8 @@
3032
"clean": "rm -rf node_modules/.cache/storybook && shx rm -rf dist",
3133
"clean:deep": "pnpm run clean && shx rm -rf node_modules",
3234
"build": "pnpm run build:esm",
33-
"build:esm": "pnpm run prebuild && tsc --project ./tsconfig.json",
34-
"build:esm:watch": "tsc --project ./tsconfig.json --watch",
35+
"build:esm": "pnpm run prebuild && tsc --project ./tsconfig.json && tsc-alias -p ./tsconfig.json --resolve-full-paths && node ../../scripts/write-dist-esm-package-json.js ./",
36+
"build:esm:watch": "sh -c 'tsc-alias -p ./tsconfig.json --resolve-full-paths --watch & alias_pid=$!; node ../../scripts/write-dist-esm-package-json.js ./ --watch & writer_pid=$!; trap \"kill $alias_pid $writer_pid\" EXIT; tsc --project ./tsconfig.json --watch'",
3537
"dev": "tsc --project ./tsconfig.json --watch",
3638
"build:all": "pnpm run build:esm",
3739
"start": "tsc --project ./tsconfig.json --watch",

packages/core/package.json

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,49 +11,49 @@
1111
"sideEffects": false,
1212
"exports": {
1313
".": {
14+
"types": "./dist/esm/index.d.ts",
1415
"import": "./dist/esm/index.js",
15-
"node": "./dist/esm/index.js",
16-
"types": "./dist/esm/index.d.ts"
16+
"default": "./dist/esm/index.js"
1717
},
1818
"./utilities": {
19+
"types": "./dist/esm/utilities/index.d.ts",
1920
"import": "./dist/esm/utilities/index.js",
20-
"node": "./dist/esm/utilities/index.js",
21-
"types": "./dist/esm/utilities/index.d.ts"
21+
"default": "./dist/esm/utilities/index.js"
2222
},
2323
"./utilities/*": {
24+
"types": "./dist/esm/utilities/*.d.ts",
2425
"import": "./dist/esm/utilities/*.js",
25-
"node": "./dist/esm/utilities/*.js",
26-
"types": "./dist/esm/utilities/*.d.ts"
26+
"default": "./dist/esm/utilities/*.js"
2727
},
2828
"./constants": {
29+
"types": "./dist/esm/constants/index.d.ts",
2930
"import": "./dist/esm/constants/index.js",
30-
"node": "./dist/esm/constants/index.js",
31-
"types": "./dist/esm/constants/index.d.ts"
31+
"default": "./dist/esm/constants/index.js"
3232
},
3333
"./constants/*": {
34+
"types": "./dist/esm/constants/*.d.ts",
3435
"import": "./dist/esm/constants/*.js",
35-
"node": "./dist/esm/constants/*.js",
36-
"types": "./dist/esm/constants/*.d.ts"
36+
"default": "./dist/esm/constants/*.js"
3737
},
3838
"./enums": {
39+
"types": "./dist/esm/enums/index.d.ts",
3940
"import": "./dist/esm/enums/index.js",
40-
"node": "./dist/esm/enums/index.js",
41-
"types": "./dist/esm/enums/index.d.ts"
41+
"default": "./dist/esm/enums/index.js"
4242
},
4343
"./enums/*": {
44+
"types": "./dist/esm/enums/*.d.ts",
4445
"import": "./dist/esm/enums/*.js",
45-
"node": "./dist/esm/enums/*.js",
46-
"types": "./dist/esm/enums/*.d.ts"
46+
"default": "./dist/esm/enums/*.js"
4747
},
4848
"./loaders": {
49+
"types": "./dist/esm/loaders/index.d.ts",
4950
"import": "./dist/esm/loaders/index.js",
50-
"node": "./dist/esm/loaders/index.js",
51-
"types": "./dist/esm/loaders/index.d.ts"
51+
"default": "./dist/esm/loaders/index.js"
5252
},
5353
"./loaders/*": {
54+
"types": "./dist/esm/loaders/*.d.ts",
5455
"import": "./dist/esm/loaders/*.js",
55-
"node": "./dist/esm/loaders/*.js",
56-
"types": "./dist/esm/loaders/*.d.ts"
56+
"default": "./dist/esm/loaders/*.js"
5757
},
5858
"./types": {
5959
"types": "./dist/esm/types/index.d.ts"
@@ -62,18 +62,19 @@
6262
"types": "./dist/esm/types/*.d.ts"
6363
},
6464
"./version": {
65+
"types": "./dist/esm/version.d.ts",
6566
"import": "./dist/esm/version.js",
66-
"node": "./dist/esm/version.js",
67-
"types": "./dist/esm/version.d.ts"
68-
}
67+
"default": "./dist/esm/version.js"
68+
},
69+
"./package.json": "./package.json"
6970
},
7071
"publishConfig": {
7172
"access": "public"
7273
},
7374
"scripts": {
7475
"prebuild": "node ../../scripts/generate-version.js ./",
75-
"build:esm": "pnpm run prebuild && tsc --project ./tsconfig.json",
76-
"build:esm:watch": "tsc --project ./tsconfig.json --watch",
76+
"build:esm": "pnpm run prebuild && tsc --project ./tsconfig.json && tsc-alias -p ./tsconfig.json --resolve-full-paths && node ../../scripts/write-dist-esm-package-json.js ./",
77+
"build:esm:watch": "sh -c 'tsc-alias -p ./tsconfig.json --resolve-full-paths --watch & alias_pid=$!; node ../../scripts/write-dist-esm-package-json.js ./ --watch & writer_pid=$!; trap \"kill $alias_pid $writer_pid\" EXIT; tsc --project ./tsconfig.json --watch'",
7778
"clean": "rm -rf node_modules/.cache/storybook && shx rm -rf dist",
7879
"clean:deep": "pnpm run clean && shx rm -rf node_modules",
7980
"build": "pnpm run build:esm",

0 commit comments

Comments
 (0)