From 401d4cdd6557a69f9cfaf564f2b68f8589e526e2 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Mon, 14 Sep 2026 10:46:25 +0800 Subject: [PATCH] refactor(tooling): [MOD-02] split library build and development into typed modules --- package.json | 10 +- .../baselines/library-tooling-validation.json | 736 ++++++++++++++++++ refactor/build-development.md | 17 +- .../2026-09-14-MOD-02-library-tooling.md | 73 ++ refactor/ci-setup.md | 1 + refactor/plan.md | 10 +- refactor/progress.md | 18 + refactor/risk-table.md | 1 + refactor/risks.json | 17 + refactor/scripts/build.test.mjs | 4 + refactor/scripts/library-build-comparison.mjs | 50 ++ refactor/scripts/servor-port-probe.mjs | 35 + refactor/tasks.json | 24 +- refactor/typechecking.md | 4 + scripts/build-analysis.mjs | 45 +- scripts/build.js | 98 +-- scripts/dev.js | 73 +- scripts/library/README.md | 98 +++ scripts/library/analysis.ts | 45 ++ scripts/library/banner.ts | 53 ++ scripts/library/config.ts | 41 + scripts/library/development.ts | 73 ++ scripts/library/names.ts | 11 + scripts/library/production.ts | 88 +++ scripts/library/projects.ts | 72 ++ scripts/library/rebuild.ts | 16 + scripts/library/vendor.d.ts | 22 + scripts/performance-artifacts.mjs | 4 + scripts/projects.js | 69 +- scripts/rebuild.js | 17 +- scripts/tsconfig.library.json | 10 + scripts/utils.js | 117 +-- test/README.md | 5 + test/browser/README.md | 6 + test/browser/library-development.spec.js | 101 +++ test/performance-report.test.js | 7 + 36 files changed, 1644 insertions(+), 427 deletions(-) create mode 100644 refactor/baselines/library-tooling-validation.json create mode 100644 refactor/changes/2026-09-14-MOD-02-library-tooling.md create mode 100644 refactor/scripts/library-build-comparison.mjs create mode 100644 refactor/scripts/servor-port-probe.mjs create mode 100644 scripts/library/README.md create mode 100644 scripts/library/analysis.ts create mode 100644 scripts/library/banner.ts create mode 100644 scripts/library/config.ts create mode 100644 scripts/library/development.ts create mode 100644 scripts/library/names.ts create mode 100644 scripts/library/production.ts create mode 100644 scripts/library/projects.ts create mode 100644 scripts/library/rebuild.ts create mode 100644 scripts/library/vendor.d.ts create mode 100644 scripts/tsconfig.library.json create mode 100644 test/browser/library-development.spec.js diff --git a/package.json b/package.json index 68e3c928e..aeb08fa6d 100644 --- a/package.json +++ b/package.json @@ -40,14 +40,14 @@ "test:dash-control": "node --test test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js", "dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js", "build": "npx cross-env NODE_ENV=production node ./scripts/build.js", - "lint": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build}/**/*.ts\" \"scripts/plugin/*.{js,ts}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --no-fix", + "lint": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build,library}/**/*.ts\" \"scripts/plugin/*.{js,ts}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --no-fix", "build:all": "yarn ci:build && yarn lint", "check:toolchain": "node scripts/check-toolchain.mjs", - "lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build}/**/*.ts\" \"scripts/plugin/*.{js,ts}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --fix", + "lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build,library}/**/*.ts\" \"scripts/plugin/*.{js,ts}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --fix", "check:plan": "node refactor/scripts/plan.mjs --check", "test:node": "yarn test:unit && node --test test/toolchain.test.js test/build-docs.test.js test/package-check.test.js test/declarations.test.js test/editor-types.test.js test/coverage.test.js test/performance-report.test.js test/media-gate.test.js test/ci-summary.test.js test/package-runtime.test.js test/library-build.test.js test/site-loading.test.js test/documentation-pipeline.test.js test/site-build.test.js test/site-markdown.test.js test/site-editor.test.js test/plugin-scaffold.test.js", "test:baseline": "node --test refactor/scripts/*.test.mjs", - "ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn typecheck:scaffold && yarn typecheck:docs-tools && yarn check:docs-smoke && yarn check:editor-types && yarn check:llm && yarn typecheck:site-assets && yarn check:site-assets && yarn check:types && yarn typecheck && yarn typecheck:react && yarn lint:react && yarn typecheck:vue && yarn lint:vue && yarn test", + "ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn typecheck:library && yarn typecheck:scaffold && yarn typecheck:docs-tools && yarn check:docs-smoke && yarn check:editor-types && yarn check:llm && yarn typecheck:site-assets && yarn check:site-assets && yarn check:types && yarn typecheck && yarn typecheck:react && yarn lint:react && yarn typecheck:vue && yarn lint:vue && yarn test", "ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:llm && yarn build:test && yarn build:docs && yarn test:imports", "test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js", "typecheck": "node scripts/typecheck.mjs", @@ -129,7 +129,9 @@ "test:site-build": "node --test test/site-build.test.js test/build-docs.test.js test/site-markdown.test.js", "test:site-editor": "node --test test/site-editor.test.js", "typecheck:scaffold": "node node_modules/typescript/bin/tsc -p scripts/tsconfig.scaffold.json --noEmit", - "test:scaffold": "node --test test/plugin-scaffold.test.js" + "test:scaffold": "node --test test/plugin-scaffold.test.js", + "typecheck:library": "node node_modules/typescript/bin/tsc -p scripts/tsconfig.library.json --noEmit", + "test:library": "node --test refactor/scripts/build.test.mjs test/library-build.test.js test/performance-report.test.js test/plugin-scaffold.test.js" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/refactor/baselines/library-tooling-validation.json b/refactor/baselines/library-tooling-validation.json new file mode 100644 index 000000000..7b39969be --- /dev/null +++ b/refactor/baselines/library-tooling-validation.json @@ -0,0 +1,736 @@ +{ + "task": "MOD-02", + "date": "2026-09-14", + "environment": { + "os": "Windows", + "node": "24.21.0", + "yarn": "1.22.22", + "typescript": "5.9.3", + "browsers": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + } + }, + "results": { + "library": { + "passed": 18, + "failed": 0 + }, + "ci": { + "passed": 50, + "failed": 0 + }, + "browser": { + "passed": 3, + "failed": 0, + "skipped": 0, + "report": "refactor/.cache/library-browser-final/report.json" + }, + "strictTypes": "passed", + "docsTypes": "passed", + "toolchain": "passed", + "lint": { + "errors": 0, + "existingWarnings": 1 + }, + "baseline": { + "passed": 522, + "failed": 0, + "skipped": 0 + } + }, + "wholeBuildComparison": { + "task": "MOD-02", + "baseline": "28c7abb2bb8dfb7c84acfc7c5ec05c5882623069", + "node": "v24.21.0", + "names": [ + "artplayer", + "artplayer-plugin-ads", + "artplayer-plugin-ambilight", + "artplayer-plugin-asr", + "artplayer-plugin-audio-track", + "artplayer-plugin-auto-thumbnail", + "artplayer-plugin-chapter", + "artplayer-plugin-chromecast", + "artplayer-plugin-danmuku", + "artplayer-plugin-danmuku-mask", + "artplayer-plugin-dash-control", + "artplayer-plugin-document-pip", + "artplayer-plugin-hls-control", + "artplayer-plugin-jassub", + "artplayer-plugin-multiple-subtitles", + "artplayer-plugin-vast", + "artplayer-plugin-vtt-thumbnail", + "artplayer-proxy-canvas", + "artplayer-proxy-mediabunny", + "artplayer-tool-iframe", + "artplayer-tool-thumbnail" + ], + "before": [ + { + "path": "packages/artplayer/dist/artplayer.js", + "sha256": "6dc8ca6d6eb34aa8a056cbc31328efd634530b3c5c60f0014cb7ce719fabc335" + }, + { + "path": "packages/artplayer/dist/artplayer.legacy.js", + "sha256": "80f74a8a39df4baf64cc00c65d21cd57c011bfcdead0da38eea077fbc67c5c04" + }, + { + "path": "packages/artplayer/dist/artplayer.mjs", + "sha256": "432e2b0ad92009a09743e020bf7c928c8b8f18a52496622b16259ec9ab20059f" + }, + { + "path": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.js", + "sha256": "1429541242f49c4d558eda242a63b6e57a876c2dba06ce9f631d71d315028023" + }, + { + "path": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.legacy.js", + "sha256": "4f64ac015e0850b71f51cf01d9fc6f238c95414730c71e91d1240c40e28499d8" + }, + { + "path": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.mjs", + "sha256": "8ad7df8145b2514467cac0f733151619c36fa965e620df575c0779604372503f" + }, + { + "path": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.js", + "sha256": "21ee5227f26cfff6e82ee6932f6eafc9741fa3be0ec2f5252a47b53ceea14599" + }, + { + "path": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.legacy.js", + "sha256": "c3ed1a54e44a0202811d70378cb19edf87b1ebd010d05fe96ebe64d0b4301d5d" + }, + { + "path": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.mjs", + "sha256": "ef126959819c4973da27dbb1e60ccccc54386c23f8c7750ce1ba4d38f306aaaa" + }, + { + "path": "packages/artplayer-plugin-asr/dist/artplayer-plugin-asr.js", + "sha256": "06b386f6a4744801f4153944ed6e2bd37822a85e177aa2a0d800c30002ef87bf" + }, + { + "path": "packages/artplayer-plugin-asr/dist/artplayer-plugin-asr.legacy.js", + "sha256": "639ae9bdede0e26c8af0251b9c1ed12e7fba9e0b6b79cb0556290741040f9697" + }, + { + "path": "packages/artplayer-plugin-asr/dist/artplayer-plugin-asr.mjs", + "sha256": "1b7bac676d2c4976046b08fd7c1ec25724ff24952f5d93fc546e219466d975d8" + }, + { + "path": "packages/artplayer-plugin-audio-track/dist/artplayer-plugin-audio-track.js", + "sha256": "23b10856628c5568f1aa7ad88e926c7da8d96edfadf6df872777062a78a4b4be" + }, + { + "path": "packages/artplayer-plugin-audio-track/dist/artplayer-plugin-audio-track.legacy.js", + "sha256": "41b94e84e3d3729b9373cb13de2f64d5ea30b4c36627f38583bf4c3c9bc2160c" + }, + { + "path": "packages/artplayer-plugin-audio-track/dist/artplayer-plugin-audio-track.mjs", + "sha256": "9caa5a6e11a2e777760dd2835426842a87d04fb081ea2e7d48c7229035fd89a9" + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "430ac17e9025d3cc6bf37bd83316db5067bf438b30c2727b43c3e5fc4910e1d0" + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "efb30986ffc6a28859ca37126af82824601f25f0a42f7ea2c75a41f3da2c50ef" + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs", + "sha256": "6080dd90bde239515646899bdd8e71d17de24119523e4883025fbf39b1a6a302" + }, + { + "path": "packages/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js", + "sha256": "179c3ea90ddc85da27b46f000110e32d464dfa5c8c394e98316dc1b75d6f2a72" + }, + { + "path": "packages/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.legacy.js", + "sha256": "dade919ed044cbf46ac16e505f538f86a86edccf177da2e14d79d111559d4bef" + }, + { + "path": "packages/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.mjs", + "sha256": "754fbcfc60add74d6b6ad3bc8db253df3eb14ace13ac8a5439be0592bd5512d0" + }, + { + "path": "packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.js", + "sha256": "80b12b3081086488c9d80f03979c336d4a6fdb44dca298e218dfeaaadb295d00" + }, + { + "path": "packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.legacy.js", + "sha256": "b21a30852403914a3bf3defc89aff11f2cc23b044ed68ccf667164b477f04172" + }, + { + "path": "packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.mjs", + "sha256": "68d81fcab469f74fe9d9740842d989e10144f7ff28c7ade91ed22adee90f44d7" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "133b7412e1a468b34f4f7ab3779958259f13899e037833029931f02b0833d088" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "9f0cb9969419a7d0c02d54790c423353b83f775acd95fbdbaeeb29a7fc42c816" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs", + "sha256": "356406cda2e3e47376a87c939b76fb7c55f86d7c3829127da625b01241c67a2f" + }, + { + "path": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.js", + "sha256": "406a8c28207d386cffe1e61dc79afd8a6a9d5cbd1dc8e1661b4febbf48182f55" + }, + { + "path": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.legacy.js", + "sha256": "62d61dcedd3e90fa480589c2f1e3168c97635232c98ffe8c25c7918a2f3c66db" + }, + { + "path": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.mjs", + "sha256": "51936576383129170125b33d2983858e0841ea507368970f6d154bc9d78583b1" + }, + { + "path": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js", + "sha256": "a594009be5c17406844f817c5a79930c6ed60add4a5ef0578783f163a1bae012" + }, + { + "path": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js", + "sha256": "b00a837a99f7a7cccd415af335cbf0c68f1e4267fa7cfd8d91e945e3dc634151" + }, + { + "path": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs", + "sha256": "0c5a7e4d84e9641778e0a22ff9c78db9bd4ac2f08f51358519443046a528fb93" + }, + { + "path": "packages/artplayer-plugin-document-pip/dist/artplayer-plugin-document-pip.js", + "sha256": "56440aad3ed06be55ec3a57d781f7de239e9aecf40a067e88f2cffcd01a86d6a" + }, + { + "path": "packages/artplayer-plugin-document-pip/dist/artplayer-plugin-document-pip.legacy.js", + "sha256": "67dd14de3cc16a04ae6c1938dcba61b1355f4a15e5fa06f62a41b18d14d4d356" + }, + { + "path": "packages/artplayer-plugin-document-pip/dist/artplayer-plugin-document-pip.mjs", + "sha256": "541b4ad6e1cbadd9156ee1f317559c97b9fddae37668ef32046f1fa47c392ec2" + }, + { + "path": "packages/artplayer-plugin-hls-control/dist/artplayer-plugin-hls-control.js", + "sha256": "8e163d4ab6734cbc45e7f539de7db644c7faf36d0318469a86d300bf6365cca6" + }, + { + "path": "packages/artplayer-plugin-hls-control/dist/artplayer-plugin-hls-control.legacy.js", + "sha256": "fd7be1c5bf8f994b61fc040ff96eb628fc442568d99f4b5fee4f611bac1d28c0" + }, + { + "path": "packages/artplayer-plugin-hls-control/dist/artplayer-plugin-hls-control.mjs", + "sha256": "64286498c1e90c3927236318383db193698a9dcbf39f91e93fc2b4ea2239e786" + }, + { + "path": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "fb8cd8b7b6fb57a7866f929cfe20f7f582c86af96101f23c572dd79b4912dc9f" + }, + { + "path": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "e19fea01316f78245e4508999bc441c483cfb47abfa6b5afbba529af3d05bfd8" + }, + { + "path": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs", + "sha256": "d5f9df0621de4b5a9a5a8a232e290f99a1d8a2eda1bc0f8a34a2cb530c95653e" + }, + { + "path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.js", + "sha256": "14f4de91201f6b2cd55ea22a042d456c7149e4a38c22bc2ee0107f8a5e417446" + }, + { + "path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.legacy.js", + "sha256": "2059a43e4634b6c1a924a1cb451c2348356d6c20d26628b975b1d3545394d323" + }, + { + "path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.mjs", + "sha256": "07e4f628b4038d7c250f6242c410ddb188148b4ed58bb592975532666412e3e2" + }, + { + "path": "packages/artplayer-plugin-vast/dist/artplayer-plugin-vast.js", + "sha256": "874803ec726f04649bb636c1376de9e038754310ab0016a22da44f189b7a5ce0" + }, + { + "path": "packages/artplayer-plugin-vast/dist/artplayer-plugin-vast.legacy.js", + "sha256": "8a4166838aae5d150dc1a18cc594fb038df425df42eff1944e0d095df060dbb9" + }, + { + "path": "packages/artplayer-plugin-vast/dist/artplayer-plugin-vast.mjs", + "sha256": "ffbab3a7cd6c1852c42a726acd0e0a5dbbb636dc9e8c2e8a084df962ff6ef860" + }, + { + "path": "packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.js", + "sha256": "df70ccb5261fd988ba8c2c2d689ca58653bb5426242ffd1076569a1d225997a7" + }, + { + "path": "packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.legacy.js", + "sha256": "732ade2c647281cf26446d6c7433e916f8189e2fdf2687127a56556c415544b1" + }, + { + "path": "packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.mjs", + "sha256": "d35e28015df65e27da0fc01d06db07bad58e08d498b2d11fedfea35e6c4339c1" + }, + { + "path": "packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.js", + "sha256": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73" + }, + { + "path": "packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.legacy.js", + "sha256": "052263fdd7ab657a0183d874a706c2cf82527233c5e60b58158caa0677718b97" + }, + { + "path": "packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.mjs", + "sha256": "66ca7271f52545a8796f563f2d5ca04581bed8d60d218463923325acf5b73ccf" + }, + { + "path": "packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.js", + "sha256": "23ce59f86e7159eedd4c371219c5ed58db83341e1f3fa3c67dcb8f1259e243cb" + }, + { + "path": "packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.legacy.js", + "sha256": "2c523a4535ae71213d279b54abf3b1d164c81e3d8abe3d9c9906e9a9ac840c26" + }, + { + "path": "packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.mjs", + "sha256": "3126446aa6461b78e56855ac851a2abf5a12359c7354e5564af6efaa0903f3fa" + }, + { + "path": "packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.js", + "sha256": "7408fceccb000e1b304abe30ac9bc364da7860c3896ad5aaa6af427804c237e6" + }, + { + "path": "packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.legacy.js", + "sha256": "4e8bd39cdc37e98bff9cd19b5e0c9453ebe5231264242fc1c573aa96edb5ea8a" + }, + { + "path": "packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.mjs", + "sha256": "ac4c5efa670c0429fc95bdab7902df60f5b5540f0dad9c7e65c2bd660796dc83" + }, + { + "path": "packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.js", + "sha256": "ef72d277e817c2f7312c10815ee91a74dbb9334285476d14fae7f4449b3d2758" + }, + { + "path": "packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.legacy.js", + "sha256": "f1d62e3cee7e060e7ecff4a21e7589e2befb2e405976efcdee73719857d0be03" + }, + { + "path": "packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.mjs", + "sha256": "abdbc7c28bcccc1a8dee543f1bd784f850c147856fc148c3bf4b8ea863f3d6ea" + } + ], + "candidate": [ + { + "path": "packages/artplayer/dist/artplayer.js", + "sha256": "6dc8ca6d6eb34aa8a056cbc31328efd634530b3c5c60f0014cb7ce719fabc335" + }, + { + "path": "packages/artplayer/dist/artplayer.legacy.js", + "sha256": "80f74a8a39df4baf64cc00c65d21cd57c011bfcdead0da38eea077fbc67c5c04" + }, + { + "path": "packages/artplayer/dist/artplayer.mjs", + "sha256": "432e2b0ad92009a09743e020bf7c928c8b8f18a52496622b16259ec9ab20059f" + }, + { + "path": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.js", + "sha256": "1429541242f49c4d558eda242a63b6e57a876c2dba06ce9f631d71d315028023" + }, + { + "path": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.legacy.js", + "sha256": "4f64ac015e0850b71f51cf01d9fc6f238c95414730c71e91d1240c40e28499d8" + }, + { + "path": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.mjs", + "sha256": "8ad7df8145b2514467cac0f733151619c36fa965e620df575c0779604372503f" + }, + { + "path": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.js", + "sha256": "21ee5227f26cfff6e82ee6932f6eafc9741fa3be0ec2f5252a47b53ceea14599" + }, + { + "path": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.legacy.js", + "sha256": "c3ed1a54e44a0202811d70378cb19edf87b1ebd010d05fe96ebe64d0b4301d5d" + }, + { + "path": "packages/artplayer-plugin-ambilight/dist/artplayer-plugin-ambilight.mjs", + "sha256": "ef126959819c4973da27dbb1e60ccccc54386c23f8c7750ce1ba4d38f306aaaa" + }, + { + "path": "packages/artplayer-plugin-asr/dist/artplayer-plugin-asr.js", + "sha256": "06b386f6a4744801f4153944ed6e2bd37822a85e177aa2a0d800c30002ef87bf" + }, + { + "path": "packages/artplayer-plugin-asr/dist/artplayer-plugin-asr.legacy.js", + "sha256": "639ae9bdede0e26c8af0251b9c1ed12e7fba9e0b6b79cb0556290741040f9697" + }, + { + "path": "packages/artplayer-plugin-asr/dist/artplayer-plugin-asr.mjs", + "sha256": "1b7bac676d2c4976046b08fd7c1ec25724ff24952f5d93fc546e219466d975d8" + }, + { + "path": "packages/artplayer-plugin-audio-track/dist/artplayer-plugin-audio-track.js", + "sha256": "23b10856628c5568f1aa7ad88e926c7da8d96edfadf6df872777062a78a4b4be" + }, + { + "path": "packages/artplayer-plugin-audio-track/dist/artplayer-plugin-audio-track.legacy.js", + "sha256": "41b94e84e3d3729b9373cb13de2f64d5ea30b4c36627f38583bf4c3c9bc2160c" + }, + { + "path": "packages/artplayer-plugin-audio-track/dist/artplayer-plugin-audio-track.mjs", + "sha256": "9caa5a6e11a2e777760dd2835426842a87d04fb081ea2e7d48c7229035fd89a9" + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.js", + "sha256": "430ac17e9025d3cc6bf37bd83316db5067bf438b30c2727b43c3e5fc4910e1d0" + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.legacy.js", + "sha256": "efb30986ffc6a28859ca37126af82824601f25f0a42f7ea2c75a41f3da2c50ef" + }, + { + "path": "packages/artplayer-plugin-auto-thumbnail/dist/artplayer-plugin-auto-thumbnail.mjs", + "sha256": "6080dd90bde239515646899bdd8e71d17de24119523e4883025fbf39b1a6a302" + }, + { + "path": "packages/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js", + "sha256": "179c3ea90ddc85da27b46f000110e32d464dfa5c8c394e98316dc1b75d6f2a72" + }, + { + "path": "packages/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.legacy.js", + "sha256": "dade919ed044cbf46ac16e505f538f86a86edccf177da2e14d79d111559d4bef" + }, + { + "path": "packages/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.mjs", + "sha256": "754fbcfc60add74d6b6ad3bc8db253df3eb14ace13ac8a5439be0592bd5512d0" + }, + { + "path": "packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.js", + "sha256": "80b12b3081086488c9d80f03979c336d4a6fdb44dca298e218dfeaaadb295d00" + }, + { + "path": "packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.legacy.js", + "sha256": "b21a30852403914a3bf3defc89aff11f2cc23b044ed68ccf667164b477f04172" + }, + { + "path": "packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.mjs", + "sha256": "68d81fcab469f74fe9d9740842d989e10144f7ff28c7ade91ed22adee90f44d7" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "133b7412e1a468b34f4f7ab3779958259f13899e037833029931f02b0833d088" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "9f0cb9969419a7d0c02d54790c423353b83f775acd95fbdbaeeb29a7fc42c816" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs", + "sha256": "356406cda2e3e47376a87c939b76fb7c55f86d7c3829127da625b01241c67a2f" + }, + { + "path": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.js", + "sha256": "406a8c28207d386cffe1e61dc79afd8a6a9d5cbd1dc8e1661b4febbf48182f55" + }, + { + "path": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.legacy.js", + "sha256": "62d61dcedd3e90fa480589c2f1e3168c97635232c98ffe8c25c7918a2f3c66db" + }, + { + "path": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.mjs", + "sha256": "51936576383129170125b33d2983858e0841ea507368970f6d154bc9d78583b1" + }, + { + "path": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js", + "sha256": "a594009be5c17406844f817c5a79930c6ed60add4a5ef0578783f163a1bae012" + }, + { + "path": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js", + "sha256": "b00a837a99f7a7cccd415af335cbf0c68f1e4267fa7cfd8d91e945e3dc634151" + }, + { + "path": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs", + "sha256": "0c5a7e4d84e9641778e0a22ff9c78db9bd4ac2f08f51358519443046a528fb93" + }, + { + "path": "packages/artplayer-plugin-document-pip/dist/artplayer-plugin-document-pip.js", + "sha256": "56440aad3ed06be55ec3a57d781f7de239e9aecf40a067e88f2cffcd01a86d6a" + }, + { + "path": "packages/artplayer-plugin-document-pip/dist/artplayer-plugin-document-pip.legacy.js", + "sha256": "67dd14de3cc16a04ae6c1938dcba61b1355f4a15e5fa06f62a41b18d14d4d356" + }, + { + "path": "packages/artplayer-plugin-document-pip/dist/artplayer-plugin-document-pip.mjs", + "sha256": "541b4ad6e1cbadd9156ee1f317559c97b9fddae37668ef32046f1fa47c392ec2" + }, + { + "path": "packages/artplayer-plugin-hls-control/dist/artplayer-plugin-hls-control.js", + "sha256": "8e163d4ab6734cbc45e7f539de7db644c7faf36d0318469a86d300bf6365cca6" + }, + { + "path": "packages/artplayer-plugin-hls-control/dist/artplayer-plugin-hls-control.legacy.js", + "sha256": "fd7be1c5bf8f994b61fc040ff96eb628fc442568d99f4b5fee4f611bac1d28c0" + }, + { + "path": "packages/artplayer-plugin-hls-control/dist/artplayer-plugin-hls-control.mjs", + "sha256": "64286498c1e90c3927236318383db193698a9dcbf39f91e93fc2b4ea2239e786" + }, + { + "path": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.js", + "sha256": "fb8cd8b7b6fb57a7866f929cfe20f7f582c86af96101f23c572dd79b4912dc9f" + }, + { + "path": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.legacy.js", + "sha256": "e19fea01316f78245e4508999bc441c483cfb47abfa6b5afbba529af3d05bfd8" + }, + { + "path": "packages/artplayer-plugin-jassub/dist/artplayer-plugin-jassub.mjs", + "sha256": "d5f9df0621de4b5a9a5a8a232e290f99a1d8a2eda1bc0f8a34a2cb530c95653e" + }, + { + "path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.js", + "sha256": "14f4de91201f6b2cd55ea22a042d456c7149e4a38c22bc2ee0107f8a5e417446" + }, + { + "path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.legacy.js", + "sha256": "2059a43e4634b6c1a924a1cb451c2348356d6c20d26628b975b1d3545394d323" + }, + { + "path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.mjs", + "sha256": "07e4f628b4038d7c250f6242c410ddb188148b4ed58bb592975532666412e3e2" + }, + { + "path": "packages/artplayer-plugin-vast/dist/artplayer-plugin-vast.js", + "sha256": "874803ec726f04649bb636c1376de9e038754310ab0016a22da44f189b7a5ce0" + }, + { + "path": "packages/artplayer-plugin-vast/dist/artplayer-plugin-vast.legacy.js", + "sha256": "8a4166838aae5d150dc1a18cc594fb038df425df42eff1944e0d095df060dbb9" + }, + { + "path": "packages/artplayer-plugin-vast/dist/artplayer-plugin-vast.mjs", + "sha256": "ffbab3a7cd6c1852c42a726acd0e0a5dbbb636dc9e8c2e8a084df962ff6ef860" + }, + { + "path": "packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.js", + "sha256": "df70ccb5261fd988ba8c2c2d689ca58653bb5426242ffd1076569a1d225997a7" + }, + { + "path": "packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.legacy.js", + "sha256": "732ade2c647281cf26446d6c7433e916f8189e2fdf2687127a56556c415544b1" + }, + { + "path": "packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.mjs", + "sha256": "d35e28015df65e27da0fc01d06db07bad58e08d498b2d11fedfea35e6c4339c1" + }, + { + "path": "packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.js", + "sha256": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73" + }, + { + "path": "packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.legacy.js", + "sha256": "052263fdd7ab657a0183d874a706c2cf82527233c5e60b58158caa0677718b97" + }, + { + "path": "packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.mjs", + "sha256": "66ca7271f52545a8796f563f2d5ca04581bed8d60d218463923325acf5b73ccf" + }, + { + "path": "packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.js", + "sha256": "23ce59f86e7159eedd4c371219c5ed58db83341e1f3fa3c67dcb8f1259e243cb" + }, + { + "path": "packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.legacy.js", + "sha256": "2c523a4535ae71213d279b54abf3b1d164c81e3d8abe3d9c9906e9a9ac840c26" + }, + { + "path": "packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.mjs", + "sha256": "3126446aa6461b78e56855ac851a2abf5a12359c7354e5564af6efaa0903f3fa" + }, + { + "path": "packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.js", + "sha256": "7408fceccb000e1b304abe30ac9bc364da7860c3896ad5aaa6af427804c237e6" + }, + { + "path": "packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.legacy.js", + "sha256": "4e8bd39cdc37e98bff9cd19b5e0c9453ebe5231264242fc1c573aa96edb5ea8a" + }, + { + "path": "packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.mjs", + "sha256": "ac4c5efa670c0429fc95bdab7902df60f5b5540f0dad9c7e65c2bd660796dc83" + }, + { + "path": "packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.js", + "sha256": "ef72d277e817c2f7312c10815ee91a74dbb9334285476d14fae7f4449b3d2758" + }, + { + "path": "packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.legacy.js", + "sha256": "f1d62e3cee7e060e7ecff4a21e7589e2befb2e405976efcdee73719857d0be03" + }, + { + "path": "packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.mjs", + "sha256": "abdbc7c28bcccc1a8dee543f1bd784f850c147856fc148c3bf4b8ea863f3d6ea" + } + ], + "differences": [] + }, + "servorPortDefect": { + "task": "MOD-DEV-01", + "node": "v24.21.0", + "servor": "4.0.2", + "occupiedPort": 12671, + "code": 0, + "signal": null, + "output": "[ERR] The port you have specified is already in use!\n", + "interpretation": "Historical upstream defect reproduction, not candidate success: an occupied requested port exits the entire process with status zero." + }, + "sources": [ + { + "path": "scripts/build.js", + "sha256": "ec716c545d6bd0a06fdf7b616a487e0cc7605212de5c413bc247efa6737d5870" + }, + { + "path": "scripts/dev.js", + "sha256": "86b13818172c2ab9ed6792f32e14af1b194783292264ff4476bcbe0f54697176" + }, + { + "path": "scripts/utils.js", + "sha256": "b42450026f5e8a621b59cd84f91d5c1d6915f4ec026ad2ee0980b06f16f6ae79" + }, + { + "path": "scripts/projects.js", + "sha256": "3956faff0032ab5b91daa45f390a664ca246559621f95fd2f53cef8bb8b58bdf" + }, + { + "path": "scripts/rebuild.js", + "sha256": "de8d73c482ac22c7c197910a755eb38cf9f3e4719c2daa9d994dd9122668fddf" + }, + { + "path": "scripts/build-analysis.mjs", + "sha256": "b5e62d1fcd9fd9216f8849a15b5fc70bf0280d978b9992c0ff5302927884c15f" + }, + { + "path": "scripts/tsconfig.library.json", + "sha256": "3f47bba61969a74be0285c67d19293ffb736ac9831a9ff78abac4789dcaf5e50" + }, + { + "path": "scripts/performance-artifacts.mjs", + "sha256": "5749bc5d75d9d12fe73c281cfa12741cec29e3fd7032324eaa63c6670774e9c4" + }, + { + "path": "test/performance-report.test.js", + "sha256": "3092854bea819b4436e00a9cc26615119ccaffef5f745d6c1459e1ed9a33422d" + }, + { + "path": "test/browser/library-development.spec.js", + "sha256": "d2f606432618319815d588edda2bf01513db4154b5ddef0f4813ed5cdd80ff27" + }, + { + "path": "refactor/scripts/build.test.mjs", + "sha256": "41942c147d63880169c9c7b032b395b511ca0573dc4ac09ba54e01338013b521" + }, + { + "path": "refactor/scripts/library-build-comparison.mjs", + "sha256": "956e697d4d67a0417c71398f345d44144f8c3a9997f262c2b014c7a3ca5c0725" + }, + { + "path": "refactor/scripts/servor-port-probe.mjs", + "sha256": "c3ae47466a575755b63c74fafd25a8fd8df160b36c2fe119cb7f5dfadfd26e2b" + }, + { + "path": "scripts/library/analysis.ts", + "sha256": "68ce6b33d0257ccd34a2abc5a75a7a6e06420cd93eaa00501cc04e38d48ad78b" + }, + { + "path": "scripts/library/banner.ts", + "sha256": "c07b9b033cf1e3ad68e9ce80c0f9e7955d24abe8ad291e01ad22b5303c9f9ec5" + }, + { + "path": "scripts/library/config.ts", + "sha256": "57ce344e05e5373a9c243a37a223e2403c67b25541b4c77f5289a83a008b6a23" + }, + { + "path": "scripts/library/development.ts", + "sha256": "7b6af5ef9bc985def5d604ae0dbc75faa57be907edfb8a06a5420f18462ba5b4" + }, + { + "path": "scripts/library/names.ts", + "sha256": "3749777e71f2f471e0edb4a1104d2c650d09643f57149a42f1c135dc79b7b1b5" + }, + { + "path": "scripts/library/production.ts", + "sha256": "f309ec71319d7d99f703bba3d11f3db53c1c240d6af9d0d5e6a5e36eb3b1bfeb" + }, + { + "path": "scripts/library/projects.ts", + "sha256": "fc658271482a4f293c21d606ac87b2668dbc88e3028b345a622587ebc0d151ca" + }, + { + "path": "scripts/library/rebuild.ts", + "sha256": "01cfc0f5f58263baa891e7c783349f1ff212a423a03a6805aecea568a482aec0" + }, + { + "path": "scripts/library/vendor.d.ts", + "sha256": "c64b7a80155c6ffc95683a208de01ba5d8d4c7b8e673b2dc5a51479a467e9f6a" + } + ], + "logs": [ + { + "path": "refactor/.cache/library-types.log", + "sha256": "258b10b22b662a74ceb797d7522c1fada9d3eb274e0b5d399297124213a6bb60" + }, + { + "path": "refactor/.cache/library-docs-types.log", + "sha256": "a81eda6ec03482b64e1f2e445981950400f44c198f4a2807b4d8d0ce8a40e0c5" + }, + { + "path": "refactor/.cache/library-final.log", + "sha256": "57ea9a6ad9a9dbe51021ed4e931a01eda245bf6075d2f96aa51361c7959d706f" + }, + { + "path": "refactor/.cache/library-lint.log", + "sha256": "582d5ca5dd5743cc6c4ee0d79209e83ae092192fc08a4459108c9378da5977ca" + }, + { + "path": "refactor/.cache/library-ci.log", + "sha256": "4f3da506e18e73d591b3639a45d58ff8d6d2dd85b10cdd6b9e6ea83e264e450d" + }, + { + "path": "refactor/.cache/library-toolchain.log", + "sha256": "26a73433d38e21611f5fac5a5ee69f4fccbe6504ff8911e14804263ff971bcb2" + }, + { + "path": "refactor/.cache/library-browser-initial.log", + "sha256": "5d89458936d534add82759a5d5c7c619573ca4926808b5ca9daf2862d4bfee16" + }, + { + "path": "refactor/.cache/library-browser-reload-initial.log", + "sha256": "327d7313fff811981422a6b1006ec059571d3e2aabea1396076070657a4bd922" + }, + { + "path": "refactor/.cache/library-browser-final.log", + "sha256": "f1ca8c33db2481eacfb8769dc2fbc304979b09d502042820aef6240752d4c795" + }, + { + "path": "refactor/.cache/library-comparison.log", + "sha256": "3e3b74b295a44218b6266e0593af346a3686e3108db4b44822a25658faf44135" + }, + { + "path": "refactor/.cache/servor-port-probe.log", + "sha256": "69d774eb3a5ce6a0b17e398d4fecd228054f2a42465703e9ddc7c1aefe261682" + }, + { + "path": "refactor/.cache/library-baseline.log", + "sha256": "0379a619c3b1323b32ae74a95da757318a6dd9538fef586de987fb5d94644d78" + } + ], + "limitations": [ + "Windows browser evidence; remote OS matrices remain pending", + "No library media playback or npm publication claims", + "Servor occupied-port exit0 remains open in MOD-DEV-01", + "No new dependencies, Lerna changes or real dist/docs rebuild" + ] +} diff --git a/refactor/build-development.md b/refactor/build-development.md index af0432e33..4cf0fd187 100644 --- a/refactor/build-development.md +++ b/refactor/build-development.md @@ -4,8 +4,10 @@ MOD-PLUGIN-01 将 `yarn create:plugin` 的实现拆为严格 TS 渲染/文件写 保留旧 JS 命令入口。生成 TS 工厂、CJS/ESM 声明、分发消费测试与维护文档; 拒绝覆盖既有包/示例,失败时回退自己的写入并保留外部修改。 运行 `yarn typecheck:scaffold` 与 `yarn test:scaffold`,具体限制和模块地图见 -[生成器维护指南](../scripts/plugin/README.md)。MOD-02 继续负责 dev/build/utils, -本子任务不代表全部工程脚本已经迁移。 +[生成器维护指南](../scripts/plugin/README.md)。MOD-02 将剩余库构建/开发实现移到 +scripts/library 的严格 TS 模块,保留旧命令/模块入口;其模块地图与生命周期限制见 +[库工具维护指南](../scripts/library/README.md)。Servor 端口失败状态和关闭责任由 +MOD-DEV-01 继续处理,不因 TS 迁移而豁免。 ENG-06 保留原来的 Vite 7.3.6、Terser、三种库产物及路径,增加选包参数和 TS 入口;未切换 bundler 或运行时依赖。固定工具链仍为 Node 24.21.0 / Yarn 1.22.22。 @@ -32,11 +34,12 @@ JS 包使用 src/index.js,TS 包使用 src/index.ts;必须恰有一个入口 | 文件 | 职责与维护点 | | --- | --- | -| scripts/projects.js | 库包清单、CLI 选包、旧 prompts 入口、JS/TS 入口唯一性;utils.js 继续转出 getProjects,保留原脚本调用 | -| scripts/build.js | 三格式顺序构建、受限 dist 清理、banner、复制 docs/compiled;main=.js/es2020、legacy=.legacy.js/es2015、ESM=.mjs/es2020 | -| scripts/dev.js | 8082 服务、docs/uncompiled/包名/index.js、首次成功打开浏览器、源目录监听及错误后的继续开发 | -| scripts/rebuild.js | 合并构建期间的通知并串行重建,避免多个构建同时清空同一输出目录;失败后下一次请求仍可运行 | -| scripts/utils.js | 共享 Vite 资源/worker/output 配置与历史全局名称;AMD 补丁依据实际 UMD 全局参数,不假定压缩变量名 | +| scripts/projects.js | 兼容导出 library/projects.ts:库清单、CLI 选择和 JS/TS 唯一入口;保留原 prompts 交互 | +| scripts/build.js | 命令门面调用 library/production.ts;顺序构建、dist 清理、docs/compiled 复制和分析;banner 独立为 library/banner.ts | +| scripts/dev.js | 命令门面调用 library/development.ts;默认 8082、docs/uncompiled/包名/index.js、首次成功打开浏览器、源码监听和失败恢复 | +| scripts/rebuild.js | 兼容导出 library/rebuild.ts;合并通知、串行构建、失败后的下一次请求 | +| scripts/utils.js | 兼容导出 library/config.ts、names.ts 和 projects.ts;Vite/worker 配置与命名分离,AMD 补丁由 banner.ts 维护 | +| scripts/build-analysis.mjs | 兼容导出 library/analysis.ts;分析文件位于 cache,不进入 dist | | types/assets.d.ts | Less inline、SVG URL 与 SVG raw 的字符串类型;特定 worker 类型边界随拥有该 worker 的包配置 | | refactor/fixtures/build/ | 同时使用 TS、相邻 JS、Less、SVG raw、TS inline worker 的独立构建夹具;不属于发布包 | | refactor/scripts/build.test.mjs | 真实 CLI 产物、错误不误清理、CJS/global/AMD/ESM、队列错误恢复;浏览器 worker/全库 AMD 使用 browser.html | diff --git a/refactor/changes/2026-09-14-MOD-02-library-tooling.md b/refactor/changes/2026-09-14-MOD-02-library-tooling.md new file mode 100644 index 000000000..3c30969e6 --- /dev/null +++ b/refactor/changes/2026-09-14-MOD-02-library-tooling.md @@ -0,0 +1,73 @@ +# MOD-02: Typed library build and development tooling + +## Scope, structure and compatibility + +Preserve the historical build/dev commands, selectors, global names, resource +handling, banner/notices, distribution filenames and analysis output. JS/MJS +entrypoints remain as checked shims. The implementation moves to strict TS modules +for production/development, config, banner handling, names, project selection, +rebuild queue and analysis; responsibilities and dependencies are documented in +[scripts/library/README.md](../../scripts/library/README.md). The scaffold part was +already completed independently as MOD-PLUGIN-01. + +Installed Vite declarations constrain plugin hooks and config mutation. Narrow +ambient declarations cover only the consumed prompts2.4.2/Servor4.0.2 APIs, inspected +in their installed source. No dependencies, lockfile, Lerna or installation hooks +changed. Tool execution uses canonical Node24.21.0; built package consumption retains +its separate Node/browser compatibility requirements. + +Manifest parsing now starts from unknown and rejects a missing/non-string version +before cleaning that package's dist. Performance evidence fingerprints every TS/JS +and JSON module under scripts/library as well as the previous shims/lock; changes +and additions/removals must invalidate old installed-artifact measurements. Markdown +is not a build input. The tests cover the previously unnoticed migration hazard +where unchanged shims could hide a changed TS config. + +Development keeps port8082 and --no-open. Tests use the explicit internal run function +with a fixture port, leaving the existing8082 process untouched. Output directories +are created before Servor enumerates Linux watch directories; actual Linux execution +remains part of remote CI, not inferred from the Windows run. Build errors keep the +source watcher alive; queued changes remain serial. + +## Validation + +- `library-build-comparison.mjs`: 21 libraries / 63 artifacts built from the same + current sources and fixture paths with frozen scripts at + `28c7abb2bb8dfb7c84acfc7c5ec05c5882623069`, then current scripts. Every SHA-256 + is identical. Full report and logs retained in .cache/library-comparison-gjDNPX. + Existing real dist/docs artifacts were not rebuilt or manually edited. +- `yarn test:library`: 18 passed, including CLI invalid inputs, malformed version + protection, TS/JS/Less/SVG/inline-worker builds, CJS/global/AMD/ESM, analysis output + identity, private-public exclusion, performance evidence and generated plugins. +- `yarn typecheck:library` and `yarn typecheck:docs-tools`: strict checks passed; + the old JS/MJS shims are checked too. +- `library-development.spec.js`: final 3 passed on Windows Chromium, Firefox and + WebKit. Real server, worker postMessage result, TS edits, automatic page reload + and syntax-error recovery were checked. No mocked dev server or playback claim. +- First browser run failed because --input-type=module leaked into Vite's Less + worker and broke CommonJS require; using a normal .mjs fixture launcher fixes the + test environment. A second run had Chromium navigation cancellation when manual + reload raced the real automatic reload; tests now assert the automatic update + itself. Those reports are retained separately; neither is called a production fix. +- `yarn lint`: passed with one existing docs/assets/ts/artplayer.d.ts warning. + `yarn test:ci`: 50 passed. Remote workflow runs are not claimed. +- `yarn test:baseline`: 522 passed, zero skipped. Strict toolchain and final + plan/risk checks also pass; dependencies and the root lock remain unchanged. + +Source/log hashes, artifact comparison, browser results and final gate results are +recorded in [library-tooling-validation.json](../baselines/library-tooling-validation.json). + +## Remaining server defect and next step + +`servor-port-probe.mjs` binds its own random port, calls installed Servor4.0.2 in an +isolated child, and reproduces its error message followed by exit status0. This is +old upstream behavior, not a successful candidate check. DEV-SERVOR-PORT-01 remains +open; MOD-DEV-01 is added before the final tooling decision to fix failure status +and define HTTP/watch/reload/timeout shutdown ownership. Servor exposes no close +handle and currently owns these resources for process lifetime. No unrelated server +was terminated and no vendor patch or dependency replacement was hidden in this task. + +MOD-02 completes the TS/structure and matching-output contract, not every dev-server +lifecycle issue, remote OS check or the entire refactor. Revert this dedicated task +commit to restore its old scripts and validation mapping together; do not revert +only the shims without their TS targets. Local commit only, no push or publication. diff --git a/refactor/ci-setup.md b/refactor/ci-setup.md index 97f5e4a6f..7d4003e63 100644 --- a/refactor/ci-setup.md +++ b/refactor/ci-setup.md @@ -12,6 +12,7 @@ | `yarn typecheck:react` / `yarn typecheck:vue` | 原 React TSX / Vue SFC 示例严格检查,ci:check 同时执行对应 lint | | `yarn typecheck:docs-tools` / `yarn check:docs-smoke` | 严格检查 TS 示例/声明生成器及 JS/MJS 门面;只读核对确定性生成的 readiness smoke,ci:check 执行 | | `yarn typecheck:scaffold` / `yarn test:scaffold` | 严格检查插件生成器与旧 JS 入口;生成包真实构建/类型/分发消费和写入失败回归;分别接入 ci:check 与 test:node | +| `yarn typecheck:library` / `yarn test:library` | 严格检查库构建/开发 TS 与旧门面;复用真实资源构建、分发、性能证据和生成器回归;类型检查接入 ci:check | | `yarn check:editor-types` | 只读核对全部编辑器声明、SDK notices 和实际 libUris;主 TS 5.9.3/历史 4.3.5 整组语义检查,ci:check 执行 | | `yarn build:site-assets` / `yarn check:site-assets` | 从站点 browser/ TS 生成三个经典脚本;check 只读,root build:docs 先生成;完整 VitePress 构建另行验收 | | `yarn typecheck:site-assets` | 严格检查站点 browser/ 模块;生成脚本由 typecheck:docs-tools 的 checkJs 覆盖;均进入 ci:check | diff --git a/refactor/plan.md b/refactor/plan.md index 57e7b0941..a0bfc4927 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -2,9 +2,9 @@ > 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。 -基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 244 项,范围 22 个包及工作区/示例。 +基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 245 项,范围 22 个包及工作区/示例。 -状态:todo 57 / doing 16 / blocked 0 / done 171 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 57 / doing 16 / blocked 0 / done 172 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -401,10 +401,11 @@ | --- | --- | --- | --- | --- | --- | --- | | MOD-01 | workspace
Bun 固定版本干净安装试点 | ENG-09 | 独立目录的 Bun 安装与 Yarn 冻结基线对比,默认仍为用户选定的 Yarn | Node 测试仍通过;安装与资源一致才决定采用;不改 bundler;不自动替换默认 packageManager 或维护锁文件 | H | todo | | MOD-PLUGIN-01 | workspace
迁移插件生成器与类型化模板 | ENG-06, SITE-03 | 保留 create:plugin CLI;纯渲染/文件发布边界、TS 工厂/声明/测试/示例及维护指南 | 防止覆盖现有包或示例;失败回退;生成包可构建并通过严格类型、旧模块消费及测试;不自动修改既有包或运行安装发布 | M | done | -| MOD-02 | workspace
整理剩余开发/构建脚本与插件模板 | ENG-06, SITE-03, MOD-PLUGIN-01 | dev/build/utils/create-plugin 的 TS 与可测 CLI,模板同时提供旧 API | 旧脚本入口保留、新插件类型/测试/示例齐全,Lerna 改动单独取证 | M | todo | +| MOD-02 | workspace
整理剩余开发/构建脚本与插件模板 | ENG-06, SITE-03, MOD-PLUGIN-01 | dev/build/utils/create-plugin 的 TS 与可测 CLI,模板同时提供旧 API | 旧脚本入口保留、新插件类型/测试/示例齐全,Lerna 改动单独取证 | M | done | +| MOD-DEV-01 | workspace
修复开发服务器失败状态并明确资源生命周期 | MOD-02 | 修复 Servor 占用端口却退出 0 的已复现问题;明确 HTTP/监听/热刷新连接与定时器的启动、失败和关闭责任 | 保持默认 8082、docs 页面、自动刷新和旧 dev 命令;端口冲突退出非零且不终止其他服务,连续启动/停止及编译错误恢复有实际验证;跨平台监听差异单独留证 | M | todo | | MOD-03 | workspace
测量并优化核心热路径 | CORE-22, ENG-08 | DOM 读写、进度更新、持久化、初始化的测量与改进 | 相同设备媒体多次比较,契约不变,收益及无效尝试记录;复用 BASE-06 的原始样本与测量限制,至少三组同环境旧新配对,不以单次变快宣称收益 | M | todo | | MOD-04 | workspace
测量并优化重型插件/proxy | PKG-DANMUKU-09, PKG-MASK-06, PKG-MB-10, ENG-08 | 帧/队列/推理/音画同步与资源长期运行比较 | 不改默认算法/阈值,性能改善有证据;无收益则保留旧实现 | M | todo | -| MOD-05 | workspace
完成工具链与性能采用决策 | MOD-01, MOD-02, MOD-03, MOD-04 | 最终 runtime/packageManager/构建配置及性能台账 | 干净安装和全包检查通过;Bun 未采用有理由,不为状态强行切换 | M | todo | +| MOD-05 | workspace
完成工具链与性能采用决策 | MOD-01, MOD-02, MOD-03, MOD-04, MOD-DEV-01 | 最终 runtime/packageManager/构建配置及性能台账 | 干净安装和全包检查通过;Bun 未采用有理由,不为状态强行切换 | M | todo | ## 8 发布验收 @@ -622,6 +623,7 @@ - EX-01: [记录](changes/2026-09-14-EX-01-react-consumer.md) [记录](baselines/react-consumer-validation.json) [记录](scripts/react-consumer.mjs) - EX-02: [记录](changes/2026-09-14-EX-02-vue-consumer.md) [记录](baselines/vue-consumer-validation.json) [记录](scripts/vue-consumer.mjs) - MOD-PLUGIN-01: [记录](changes/2026-09-14-MOD-PLUGIN-01-scaffold.md) [记录](baselines/scaffold-validation.json) +- MOD-02: [记录](changes/2026-09-14-MOD-02-library-tooling.md) [记录](baselines/library-tooling-validation.json) - PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md) [记录](type-compatibility-policy.md) [记录](baselines/factory-compatibility-validation.json) [记录](changes/2026-09-13-PKG-FACTORY-01-compatible-types.md) - CORE-25: [记录](changes/2026-09-13-CORE-25-defaults-ssr.md) [记录](baselines/defaults-ssr-validation.json) - ENG-12: [记录](changes/2026-09-13-ENG-12-library-public.md) [记录](baselines/library-public-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index 18168d73f..b5c3a2333 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,23 @@ # 进度与证据 +## MOD-02 库构建与开发脚本 TS 迁移完成 + +旧 build/dev/utils/projects/rebuild/build-analysis 入口保留,实现拆为严格 TS +模块,Vite 配置、banner/AMD、命名、包选择、队列和分析责任分离。63 个产物 +在同一来源/路径下用旧脚本与新脚本构建,SHA-256 全部相同;真实 dist/docs 未改。 +性能证据校验同步覆盖新的 TS 构建输入。默认8082保留,测试使用自有端口,未 +终止原有服务。输出目录提前创建,避免漏入 Servor 初始 Linux 目录枚举;远端 +Linux/macOS 实测仍待 CI。Windows 三浏览器验证真实 Worker、自动刷新、修改和 +编译失败恢复最终3项通过,测试启动参数/手动刷新竞态的失败报告另存。 +18项专项、522项baseline、50项CI回归通过;严格库/文档工具类型和工具链检查 +通过,lint 保留1条既有warning。详情见[记录](changes/2026-09-14-MOD-02-library-tooling.md) +和[证据](baselines/library-tooling-validation.json)。没有新增依赖、Lerna变更或发布。 +另用隔离子进程复现 Servor4.0.2 端口占用却退出0,新增开放风险及 MOD-DEV-01 +作为下一任务处理启动失败和资源关闭责任,已接入最终工具链任务依赖。 +245项:172 done、16 doing、57 todo。MOD-02 完成不代表该服务器缺陷或完整 +重构完成。本地独立提交;VAST 默认行为选择仍待用户答复。 + + ## MOD-PLUGIN-01 插件生成器与 TS 模板完成 生成器拆为严格 TS 的模板渲染、文件写入和 CLI,保留 create.js 旧命令入口及 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index 9a2ac9fdc..12de9bb9d 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -262,3 +262,4 @@ | SITE-EDITOR-AMD-01 | resolved / 已复现 | Late Monaco language AMD initialization races with example dependency loading | SITE-03 | | VTT-CORE-NAME-01 | open / 已复现 | Published VTT 1.0.1 control name conflicts with the core placeholder introduced in 5.1.7 | PKG-VTT-THUMB-05, REL-08 | | SCAFFOLD-OUTPUT-01 | resolved / 已复现 | Plugin generation overwrites existing examples and links to a different example name | MOD-PLUGIN-01 | +| DEV-SERVOR-PORT-01 | open / 已复现 | Servor exits the entire dev process with status zero when the requested port is occupied | MOD-DEV-01 | diff --git a/refactor/risks.json b/refactor/risks.json index 921d5c644..0331058b2 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -5914,6 +5914,23 @@ "test/plugin-scaffold.test.js" ], "resolutionRationale": "Frozen old generator reproduces overwrite and mismatched URL. Candidate refuses old or concurrent output, preserves external edits on rollback, generates matching dotted URLs and passes actual three-format package consumers and strict types." + }, + { + "id": "DEV-SERVOR-PORT-01", + "title": "Servor exits the entire dev process with status zero when the requested port is occupied", + "confirmation": "reproduced", + "status": "open", + "owners": [ + "MOD-DEV-01" + ], + "evidence": [ + "refactor/changes/2026-09-14-MOD-02-library-tooling.md", + "refactor/scripts/servor-port-probe.mjs", + "refactor/baselines/library-tooling-validation.json" + ], + "compatibleResolution": "Preserve the default dev URL and CLI while reporting startup failures with nonzero status, without stopping another service. Define ownership of HTTP, watch and reload resources.", + "closureCriteria": "Real occupied-port and repeated startup/shutdown checks pass for the candidate; build, reload and error recovery remain compatible. Separate source-observed resource ownership limitations from reproduced leaks.", + "workspaceState": "Pinned Servor4.0.2 probe prints the occupied-port error and exits0. TS migration retains that upstream behavior; MOD-DEV-01 must resolve it before the final tooling decision. Servor has no exposed close handle; this is a source observation, not a measured leak." } ] } diff --git a/refactor/scripts/build.test.mjs b/refactor/scripts/build.test.mjs index 379cadc96..c7ac22d1c 100644 --- a/refactor/scripts/build.test.mjs +++ b/refactor/scripts/build.test.mjs @@ -75,6 +75,10 @@ test('Normal build CLI compiles TypeScript, JS, Less, SVG and inline worker into assert.equal(run([name]).status, 1) assert(fs.existsSync(path.join(dist, 'preserve.txt'))) fs.unlinkSync(path.join(project, 'src/index.js')) + fs.writeFileSync(path.join(project, 'package.json'), JSON.stringify({ name, version: 1 })) + assert.equal(run([name]).status, 1, 'Malformed version must fail before dist is cleaned') + assert(fs.existsSync(path.join(dist, 'preserve.txt'))) + fs.writeFileSync(path.join(project, 'package.json'), JSON.stringify({ name, version: '1.0.0' })) execFileSync(process.execPath, [path.join(root, 'scripts/build.js'), name], { cwd: directory, env: { ...process.env, NODE_ENV: 'production' }, encoding: 'utf8', timeout: 60000 }) assert.deepEqual(fs.readdirSync(dist).sort(), [`${name}.js`, `${name}.legacy.js`, `${name}.mjs`]) function check(factory) { diff --git a/refactor/scripts/library-build-comparison.mjs b/refactor/scripts/library-build-comparison.mjs new file mode 100644 index 000000000..0504412fe --- /dev/null +++ b/refactor/scripts/library-build-comparison.mjs @@ -0,0 +1,50 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { createHash } from 'node:crypto' +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' + +const root = fileURLToPath(new URL('../../', import.meta.url)) +const baseline = '28c7abb2bb8dfb7c84acfc7c5ec05c5882623069' +const directory = fs.mkdtempSync(path.join(root, 'refactor/.cache/library-comparison-')) +const names = fs.readdirSync(path.join(root, 'packages')).filter(name => name !== 'artplayer-vitepress' && fs.existsSync(path.join(root, 'packages', name, 'package.json'))).sort() +const sha = file => createHash('sha256').update(fs.readFileSync(file)).digest('hex') +fs.mkdirSync(path.join(directory, 'scripts')) +fs.writeFileSync(path.join(directory, 'package.json'), '{"type":"module"}\n') +for (const file of ['build.js', 'utils.js', 'projects.js', 'build-analysis.mjs']) + fs.writeFileSync(path.join(directory, 'scripts', file), execFileSync('git', ['show', `${baseline}:scripts/${file}`], { cwd: root })) +for (const name of names) { + const target = path.join(directory, 'packages', name) + fs.mkdirSync(target, { recursive: true }) + for (const file of ['src', 'public', 'package.json', 'THIRD_PARTY_NOTICES']) { + const source = path.join(root, 'packages', name, file) + if (fs.existsSync(source)) + fs.cpSync(source, path.join(target, file), { recursive: true }) + } +} +const artifacts = () => names.flatMap(name => ['.js', '.legacy.js', '.mjs'].map(extension => { + const relative = `packages/${name}/dist/${name}${extension}` + return { path: relative, sha256: sha(path.join(directory, relative)) } +})) +function build(script, label) { + const log = fs.openSync(path.join(directory, `${label}.log`), 'w') + try { + execFileSync(process.execPath, [script, 'all'], { cwd: directory, stdio: ['ignore', log, log], timeout: 300000 }) + } + finally { + fs.closeSync(log) + } +} +console.log(`Library comparison fixture: ${directory}`) +build(path.join(directory, 'scripts/build.js'), 'before') +const before = artifacts() +fs.writeFileSync(path.join(directory, 'before.json'), `${JSON.stringify(before, null, 2)}\n`) +build(path.join(root, 'scripts/build.js'), 'candidate') +const candidate = artifacts() +const differences = candidate.filter((item, index) => item.sha256 !== before[index].sha256) +const report = { task: 'MOD-02', baseline, node: process.version, names, before, candidate, differences } +fs.writeFileSync(path.join(directory, 'report.json'), `${JSON.stringify(report, null, 2)}\n`) +assert.deepEqual(differences, [], 'Production bytes changed across the tooling migration') +console.log(`Compared ${names.length} libraries / ${candidate.length} artifacts: identical SHA-256. Report: ${directory}/report.json`) diff --git a/refactor/scripts/servor-port-probe.mjs b/refactor/scripts/servor-port-probe.mjs new file mode 100644 index 000000000..6a7049187 --- /dev/null +++ b/refactor/scripts/servor-port-probe.mjs @@ -0,0 +1,35 @@ +import assert from 'node:assert/strict' +import { spawn } from 'node:child_process' +import { once } from 'node:events' +import fs from 'node:fs' +import net from 'node:net' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' + +const root = fileURLToPath(new URL('../../', import.meta.url)) +const server = net.createServer() +server.listen(0) +await once(server, 'listening') +const { port } = server.address() +try { + const child = spawn(process.execPath, ['--input-type=module', '--eval', "import servor from 'servor'; await servor({port:Number(process.env.ARTPLAYER_PROBE_PORT),reload:false}); process.exit(7)"], { + cwd: root, + env: { ...process.env, ARTPLAYER_PROBE_PORT: String(port) }, + windowsHide: true, + timeout: 10000, + }) + let output = '' + child.stdout.on('data', (data) => { output += data }) + child.stderr.on('data', (data) => { output += data }) + const [code, signal] = await once(child, 'exit') + const version = JSON.parse(fs.readFileSync(path.join(root, 'node_modules/servor/package.json'), 'utf8')).version + const result = { task: 'MOD-DEV-01', node: process.version, servor: version, occupiedPort: port, code, signal, output, interpretation: 'Historical upstream defect reproduction, not candidate success: an occupied requested port exits the entire process with status zero.' } + fs.writeFileSync(path.join(root, 'refactor/.cache/servor-port-probe.json'), `${JSON.stringify(result, null, 2)}\n`) + assert.match(output, /port.*already in use/) + assert.equal(code, 0, 'Pinned Servor no longer reproduces its historical successful failure exit') + console.log(JSON.stringify(result, null, 2)) +} +finally { + await new Promise(resolve => server.close(resolve)) +} diff --git a/refactor/tasks.json b/refactor/tasks.json index 09216bb76..941d3b444 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -4459,10 +4459,29 @@ "SITE-03", "MOD-PLUGIN-01" ], - "status": "todo", + "status": "done", "risk": "M", "deliverable": "dev/build/utils/create-plugin 的 TS 与可测 CLI,模板同时提供旧 API", "acceptance": "旧脚本入口保留、新插件类型/测试/示例齐全,Lerna 改动单独取证", + "evidence": [ + "changes/2026-09-14-MOD-02-library-tooling.md", + "baselines/library-tooling-validation.json" + ] + }, + { + "id": "MOD-DEV-01", + "phase": "7 工具链与性能", + "title": "修复开发服务器失败状态并明确资源生命周期", + "scope": [ + "workspace" + ], + "dependsOn": [ + "MOD-02" + ], + "status": "todo", + "risk": "M", + "deliverable": "修复 Servor 占用端口却退出 0 的已复现问题;明确 HTTP/监听/热刷新连接与定时器的启动、失败和关闭责任", + "acceptance": "保持默认 8082、docs 页面、自动刷新和旧 dev 命令;端口冲突退出非零且不终止其他服务,连续启动/停止及编译错误恢复有实际验证;跨平台监听差异单独留证", "evidence": [] }, { @@ -4512,7 +4531,8 @@ "MOD-01", "MOD-02", "MOD-03", - "MOD-04" + "MOD-04", + "MOD-DEV-01" ], "status": "todo", "risk": "M", diff --git a/refactor/typechecking.md b/refactor/typechecking.md index b99aeeb64..54e374f9a 100644 --- a/refactor/typechecking.md +++ b/refactor/typechecking.md @@ -1,5 +1,9 @@ # 已实现的类型检查与迁移入口 +`yarn typecheck:library` 检查 scripts/library 的生产构建、开发、配置、banner、 +分析、包选择和重建队列,同时检查旧 JS/MJS 门面。Vite 配置使用已安装的官方 +类型;vendor.d.ts 只描述实际消费的 prompts2.4.2/Servor4.0.2 调用,不声明全 SDK。 + `yarn typecheck:scaffold` 检查 scripts/plugin 的严格 TS 模块及旧 JS 命令入口。 模板本身保留占位符,`yarn test:scaffold` 对实际渲染包执行源码检查和 TS 5.9.3 NodeNext / TS 4.3.5 classic 消费者正反例;生成声明地图见 diff --git a/scripts/build-analysis.mjs b/scripts/build-analysis.mjs index 9fe380408..deb9a8cdb 100644 --- a/scripts/build-analysis.mjs +++ b/scripts/build-analysis.mjs @@ -1,44 +1 @@ -import { Buffer } from 'node:buffer' -import { createHash } from 'node:crypto' -import fs from 'node:fs' -import path from 'node:path' -import process from 'node:process' -import { gzipSync } from 'node:zlib' - -export function createBuildAnalysis() { - const root = process.cwd() - const parent = path.join(root, 'refactor/.cache/build-analysis') - fs.mkdirSync(parent, { recursive: true }) - const directory = fs.mkdtempSync(path.join(parent, 'run-')) - fs.writeFileSync(path.join(parent, 'latest.json'), `${JSON.stringify({ output: path.relative(root, directory).replaceAll('\\', '/') })}\n`) - return { root, directory } -} - -export function buildAnalysisPlugin({ root, directory }, name, format) { - return { - name: 'artplayer-build-analysis', - generateBundle(_options, bundle) { - for (const chunk of Object.values(bundle).filter(item => item.type === 'chunk')) { - const modules = Object.entries(chunk.modules).map(([id, info]) => { - const normalized = id.replaceAll('\\', '/') - const dependency = normalized.lastIndexOf('/node_modules/') - const file = id.startsWith('\0') - ? `virtual:${id.slice(1)}` - : dependency === -1 ? path.relative(root, id).replaceAll('\\', '/') : normalized.slice(dependency + 1) - return { file, renderedBytes: Buffer.byteLength(info.code || ''), renderedCodeUnits: info.renderedLength, originalCodeUnits: info.originalLength, renderedExports: info.renderedExports, removedExports: info.removedExports } - }).sort((a, b) => b.renderedBytes - a.renderedBytes || a.file.localeCompare(b.file)) - const report = { - schemaVersion: 1, - name, - format, - node: process.version, - file: chunk.fileName, - artifact: { bytes: Buffer.byteLength(chunk.code), gzip9: gzipSync(chunk.code, { level: 9 }).length, sha256: createHash('sha256').update(chunk.code).digest('hex') }, - note: 'Rendered UTF-8 bytes precede final chunk minification; they are not additive compressed sizes or download measurements. Rollup lengths are separately recorded as UTF-16 code units. Original code units describe Rollup input, not raw TypeScript.', - modules, - } - fs.writeFileSync(path.join(directory, `${name}-${format}.json`), `${JSON.stringify(report, null, 2)}\n`) - } - }, - } -} +export { buildAnalysisPlugin, createBuildAnalysis } from './library/analysis.ts' diff --git a/scripts/build.js b/scripts/build.js index 9ada5d221..b38ae97c3 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,101 +1,5 @@ -import fs from 'node:fs' -import path from 'node:path' import process from 'node:process' -import cpy from 'cpy' -import { build as viteBuild } from 'vite' -import { buildAnalysisPlugin, createBuildAnalysis } from './build-analysis.mjs' -import { getEntryFile, selectProjects } from './projects.js' -import { getGlobalName, getProjects, getViteBuildConfig } from './utils.js' - -const projects = getProjects() -const compiledPath = path.resolve('docs/compiled') - -function getBanner(name, version) { - const noticeFile = path.join(projects[name], 'THIRD_PARTY_NOTICES') - const notices = fs.existsSync(noticeFile) ? fs.readFileSync(noticeFile, 'utf8').trim() : '' - const noticeBlock = notices ? `\n *\n${notices.split(/\r?\n/).map(line => line.trimEnd() ? ` * ${line.trimEnd()}` : ' *').join('\n')}` : '' - return `/*! - * ${name}.js v${version} - * Github: https://github.com/zhw2590582/ArtPlayer - * (c) 2017-${new Date().getFullYear()} Harvey Zhao - * Released under the MIT License.${noticeBlock} - */` -} - -const BUILD_FORMATS = { - main: { format: 'umd', ext: '.js', target: 'es2020', minify: 'terser' }, - legacy: { format: 'umd', ext: '.legacy.js', target: 'es2015', minify: 'terser' }, - esm: { format: 'es', ext: '.mjs', target: 'es2020', minify: false }, -} - -async function build(name, targetName, clean = false, analysis) { - const projectDir = projects[name] - const { version } = JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), 'utf-8')) - const distDir = path.join(projectDir, 'dist') - const entryFile = getEntryFile(projectDir) - - if (clean && fs.existsSync(distDir)) { - if (fs.realpathSync(distDir) !== path.join(fs.realpathSync(projectDir), 'dist')) - throw new Error(`Refusing to clean redirected dist directory: ${distDir}`) - fs.rmSync(distDir, { recursive: true, force: true }) - } - - const startTime = Date.now() - const { format, ext, target, minify } = BUILD_FORMATS[targetName] - const fileName = `${name}${ext}` - - const config = getViteBuildConfig({ - entry: entryFile, - outDir: distDir, - name: getGlobalName(name), - format, - fileName, - minify, - target, - banner: getBanner(name, version), - }) - - // Terser options for all builds (preserve banner comments starting with /*!) - const isLegacy = targetName === 'legacy' - config.build.terserOptions = { - ecma: isLegacy ? 5 : 2020, - compress: { ecma: isLegacy ? 5 : 2020, comparisons: false, inline: 2 }, - mangle: { safari10: true }, - format: { - ecma: isLegacy ? 5 : 2020, - comments: /^!/, - ascii_only: true, - max_line_len: false, // No line length limit - }, - } - if (analysis) - config.build.rollupOptions.plugins.push(buildAnalysisPlugin(analysis, name, targetName)) - - await viteBuild({ root: projectDir, ...config }) - - const outFile = path.join(distDir, fileName) - const size = (fs.statSync(outFile).size / 1024).toFixed(2) - await cpy(outFile, compiledPath, { flat: true }) - - console.log(`✨ Built@${targetName} ${name}@${version} Time@${Date.now() - startTime}ms Size@${size}kb`) -} - -async function buildProject(name, analysis) { - await build(name, 'main', true, analysis) - await build(name, 'legacy', false, analysis) - await build(name, 'esm', false, analysis) -} - -async function runBuild() { - const { names, analyze } = await selectProjects(projects, 'build') - const analysis = analyze ? createBuildAnalysis() : undefined - for (const name of names) - await buildProject(name, analysis) - if (analysis) - console.log(`Build analysis: ${analysis.directory}`) - if (names.length) - console.log(`✅ Finished building ${names.length} package(s)!`) -} +import { runBuild } from './library/production.ts' runBuild().catch((error) => { console.error('❌ Build failed:', error) diff --git a/scripts/dev.js b/scripts/dev.js index 2494f4b93..cb8744749 100644 --- a/scripts/dev.js +++ b/scripts/dev.js @@ -1,76 +1,7 @@ -import fs from 'node:fs' -import path from 'node:path' import process from 'node:process' -import servor from 'servor' -import openBrowser from 'servor/utils/openBrowser.js' -import { build as viteBuild } from 'vite' -import { getEntryFile, selectProjects } from './projects.js' -import { createRebuildQueue } from './rebuild.js' -import { getGlobalName, getProjects, getViteBuildConfig } from './utils.js' +import { runDevelop } from './library/development.ts' -const projects = getProjects() - -async function develop(name, open) { - const projectPath = projects[name] - const uncompiledPath = path.resolve(`docs/uncompiled/${name}`) - let browserOpened = false - - const { url } = await servor({ - root: 'docs', - fallback: 'index.html', - reload: true, - port: 8082, - }) - - fs.mkdirSync(uncompiledPath, { recursive: true }) - - async function buildBundle() { - const startTime = Date.now() - try { - const config = getViteBuildConfig({ - entry: getEntryFile(projectPath), - outDir: uncompiledPath, - name: getGlobalName(name), - format: 'iife', - fileName: 'index.js', - minify: false, - emptyOutDir: true, - }) - config.define['process.env.NODE_ENV'] = JSON.stringify('development') - - await viteBuild({ root: projectPath, ...config }) - console.log(`[${name}] ✅ Built in ${Date.now() - startTime}ms`) - - if (open && !browserOpened) { - browserOpened = true - openBrowser(url) - } - } - catch (error) { - console.error(`[${name}] ❌ Build error:`, error) - } - } - - const rebuild = createRebuildQueue(buildBundle) - await rebuild() - console.log(`[${name}] Demo: ${url}/?libs=./uncompiled/${name}/index.js`) - - const srcPath = path.join(projectPath, 'src') - console.log(`[${name}] 👀 Watching ${srcPath}...`) - - fs.watch(srcPath, { recursive: true }, async (_, filename) => { - if (filename) { - console.log(`[${name}] 📝 Changed: ${filename}`) - await rebuild() - } - }) -} - -(async () => { - const { names, open } = await selectProjects(projects, 'dev') - if (names.length) - await develop(names[0], open) -})().catch((error) => { +runDevelop().catch((error) => { console.error('❌ Development server failed:', error) process.exitCode = 1 }) diff --git a/scripts/library/README.md b/scripts/library/README.md new file mode 100644 index 000000000..e8aecb25f --- /dev/null +++ b/scripts/library/README.md @@ -0,0 +1,98 @@ +# Library build and development tooling + +Use Node from `.node-version` and Yarn Classic 1.22.22. The historical JS/MJS +entrypoints remain checked compatibility shims; implementation lives in strict TS. +No runtime/build dependency or bundler was replaced by this migration. + +| Module | Responsibility | +| --- | --- | +| `projects.ts` | Deterministic workspace discovery, CLI selection, non-TTY failure, one JS/TS entry | +| `names.ts` | Historical camelCase/PascalCase library global names | +| `config.ts` | Vite configuration and resource/worker/output targets; typed with installed Vite declarations | +| `banner.ts` | Version/license/notices, UMD AMD/global identity, inline-worker banner removal | +| `production.ts` | Sequential package/format builds, checked dist cleanup, docs copy and optional analysis | +| `analysis.ts` | Per-build module and artifact size/hash reports outside dist | +| `development.ts` | Servor demo startup, initial IIFE build, source watcher, first-success browser opening | +| `rebuild.ts` | One active build Promise, coalesced pending changes and later retry after rejection | +| `vendor.d.ts` | Only the consumed prompts 2.4.2 / Servor 4.0.2 APIs; verified against installed sources | + +`build.js` and `dev.js` own command execution/error reporting. `utils.js`, +`projects.js`, `rebuild.js` and `build-analysis.mjs` keep their previous named +exports. Importing implementation modules does not start a server/build or read +the current workspace; the explicit run functions do that work. TypeScript +annotation removal uses the canonical Node version; Node 20 consumer validation +concerns the built libraries, not executing these repository TS tools. + +## Stable commands and build behavior + +```sh +yarn build artplayer artplayer-plugin-chapter +yarn build all --analyze +yarn dev artplayer --no-open +yarn build --help +yarn dev --help +yarn typecheck:library +yarn test:library +``` + +No-argument TTY selection uses the same prompts UI. Missing names in non-TTY mode, +unknown/inherited project names and ambiguous JS/TS entries fail before cleaning +dist or starting the server. Build format order remains modern UMD `.js` es2020, +legacy UMD `.legacy.js` es2015, then ESM `.mjs` es2020. Main/legacy use the same +Terser options; ESM remains unminified. Notice text, worker handling, AMD globals, +default exports, copied docs paths and `--analyze` output remain intact. A missing +or non-string manifest version now fails before that package's dist cleanup. +Multi-package builds are sequential, not an all-packages transaction. + +Vite types constrain configuration but do not replace source typechecking. Avoid +casts that hide incompatible plugin/output types. `config.ts` deliberately sets +`publicDir: false` so core declaration sources cannot leak into distribution files. +Keep unknown UMD wrappers as explicit failures when upgrading Vite/Terser. + +Performance artifact verification fingerprints the JS entry shims and all code/JSON +under `scripts/library/`, including additions/removals. Do not reduce that coverage +to `config.ts` alone: banners, name mapping, project discovery or analysis may also +affect artifacts. Markdown maintenance changes are not build inputs. + +## Development lifetime and remaining server work + +CLI defaults remain port 8082 and docs/index.html, with output at +docs/uncompiled//index.js. The output directory is created before Servor +enumerates directories for its Linux file watchers. The internal `runDevelop` +function accepts a port only for isolated callers/tests; the CLI does not add a +new flag. Do not stop an unrelated server occupying 8082 to run a test. + +Build errors are reported while keeping the source watcher alive. Changes during +a build schedule one subsequent build rather than concurrent writes. Browser +opening happens once after the first successful build; `--no-open` suppresses it. +Only the package src tree is watched; external dependencies/configuration changes +still require restart. Tests use real automatic reload rather than racing it with +a second manual navigation. + +Servor still owns its HTTP server, docs watchers, reload sockets/timers and SIGINT +handler until process exit; its API exposes no close handle. MOD-DEV-01 tracks +replacing/adapting that lifetime and fixing its reproduced occupied-port exit code +zero. `node refactor/scripts/servor-port-probe.mjs` reproduces that upstream defect +in an isolated child without touching 8082. It is not a candidate-success test. +This migration does not claim those server limitations are resolved. + +## Evidence and regression ownership + +- `refactor/scripts/build.test.mjs`: CLI validation, serialization/error recovery, + real TS/JS/Less/SVG/inline-worker builds, CJS/global/AMD/ESM and analysis identity. +- `test/library-build.test.js`: public files never copied into library dist. +- `test/performance-report.test.js`: changed/new/removed TS build inputs cannot + reuse stale installed performance artifacts. +- `test/plugin-scaffold.test.js`: newly generated plugins use these real tools. +- `test/browser/library-development.spec.js`: real dev server in Chromium, Firefox + and WebKit, worker messaging, automatic reload, source edits and error recovery. + Uses fixture ports and disposes its own child; it does not test media playback. +- `node refactor/scripts/library-build-comparison.mjs`: copy current package sources + into a retained fixture, build all libraries with frozen pre-migration scripts + and current scripts at the same paths, compare all artifact hashes. This is an + explicit whole-build validation, not part of every quick unit test. + +Remote Linux/macOS/Windows CI remains a separate release gate. The local browser +evidence for MOD-02 is Windows; source inspection of Linux watcher registration +does not substitute for a remote Linux run. Lerna and root installation hooks were +not changed. The scaffold's separate guide remains in `scripts/plugin/README.md`. diff --git a/scripts/library/analysis.ts b/scripts/library/analysis.ts new file mode 100644 index 000000000..a701f24f1 --- /dev/null +++ b/scripts/library/analysis.ts @@ -0,0 +1,45 @@ +import type { Plugin } from 'vite' +import { Buffer } from 'node:buffer' +import { createHash } from 'node:crypto' +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { gzipSync } from 'node:zlib' + +export function createBuildAnalysis() { + const root = process.cwd() + const parent = path.join(root, 'refactor/.cache/build-analysis') + fs.mkdirSync(parent, { recursive: true }) + const directory = fs.mkdtempSync(path.join(parent, 'run-')) + fs.writeFileSync(path.join(parent, 'latest.json'), `${JSON.stringify({ output: path.relative(root, directory).replaceAll('\\', '/') })}\n`) + return { root, directory } +} + +export function buildAnalysisPlugin({ root, directory }: ReturnType, name: string, format: string): Plugin { + return { + name: 'artplayer-build-analysis', + generateBundle(_options, bundle) { + for (const chunk of Object.values(bundle).filter(item => item.type === 'chunk')) { + const modules = Object.entries(chunk.modules).map(([id, info]) => { + const normalized = id.replaceAll('\\', '/') + const dependency = normalized.lastIndexOf('/node_modules/') + const file = id.startsWith('\0') + ? `virtual:${id.slice(1)}` + : dependency === -1 ? path.relative(root, id).replaceAll('\\', '/') : normalized.slice(dependency + 1) + return { file, renderedBytes: Buffer.byteLength(info.code || ''), renderedCodeUnits: info.renderedLength, originalCodeUnits: info.originalLength, renderedExports: info.renderedExports, removedExports: info.removedExports } + }).sort((a, b) => b.renderedBytes - a.renderedBytes || a.file.localeCompare(b.file)) + const report = { + schemaVersion: 1, + name, + format, + node: process.version, + file: chunk.fileName, + artifact: { bytes: Buffer.byteLength(chunk.code), gzip9: gzipSync(chunk.code, { level: 9 }).length, sha256: createHash('sha256').update(chunk.code).digest('hex') }, + note: 'Rendered UTF-8 bytes precede final chunk minification; they are not additive compressed sizes or download measurements. Rollup lengths are separately recorded as UTF-16 code units. Original code units describe Rollup input, not raw TypeScript.', + modules, + } + fs.writeFileSync(path.join(directory, `${name}-${format}.json`), `${JSON.stringify(report, null, 2)}\n`) + } + }, + } +} diff --git a/scripts/library/banner.ts b/scripts/library/banner.ts new file mode 100644 index 000000000..c0ef62c7f --- /dev/null +++ b/scripts/library/banner.ts @@ -0,0 +1,53 @@ +import type { Plugin } from 'vite' +import fs from 'node:fs' +import path from 'node:path' + +export function getBanner(project: string, name: string, version: string): string { + const noticeFile = path.join(project, 'THIRD_PARTY_NOTICES') + const notices = fs.existsSync(noticeFile) ? fs.readFileSync(noticeFile, 'utf8').trim() : '' + const noticeBlock = notices ? `\n *\n${notices.split(/\r?\n/).map(line => line.trimEnd() ? ` * ${line.trimEnd()}` : ' *').join('\n')}` : '' + return `/*! + * ${name}.js v${version} + * Github: https://github.com/zhw2590582/ArtPlayer + * (c) 2017-${new Date().getFullYear()} Harvey Zhao + * Released under the MIT License.${noticeBlock} + */` +} + +export function bannerPlugin(name: string, banner: string): Plugin { + return { + name: 'add-banner-and-global', + generateBundle(outputOptions, bundle) { + for (const chunk of Object.values(bundle)) { + if (chunk.type !== 'chunk') + continue + let code = chunk.code.replace(/`\/\*![\s\S]*?\*\/\n/g, '`') + if (!code.startsWith('/*!')) + code = `${banner}\n${code}` + if (outputOptions.format === 'umd') { + const wrapper = code.replace(/^\/\*![\s\S]*?\*\/\s*/, '').match(/^[!(\s]*function\s*\(\s*([\w$]+)\s*,\s*([\w$]+)\s*\)/) + if (!wrapper) + throw new Error(`Unrecognized UMD wrapper for ${name}`) + const global = wrapper[1] + const amd = /["']function["']\s*===?\s*typeof define\s*&&\s*define\.amd\s*\?\s*define\(([\w$]+)\)\s*:/ + if (!amd.test(code)) + throw new Error(`Unrecognized AMD branch for ${name}`) + code = code.replace(amd, `"function"==typeof define&&define.amd?(${global}.${name}=$1(),define(function(){return ${global}.${name}})):`) + } + chunk.code = code + } + }, + } +} + +export function workerBannerPlugin(): Plugin { + return { + name: 'remove-worker-banner', + generateBundle(_, bundle) { + for (const chunk of Object.values(bundle)) { + if (chunk.type === 'chunk' && chunk.code.startsWith('/*!')) + chunk.code = chunk.code.replace(/^\/\*![\s\S]*?\*\/\s*/, '') + } + }, + } +} diff --git a/scripts/library/config.ts b/scripts/library/config.ts new file mode 100644 index 000000000..10be0a335 --- /dev/null +++ b/scripts/library/config.ts @@ -0,0 +1,41 @@ +import type { BuildOptions, InlineConfig, LibraryFormats, Plugin } from 'vite' +import { bannerPlugin, workerBannerPlugin } from './banner.ts' + +export interface LibraryConfigOptions { + entry: string + outDir: string + name: string + format: LibraryFormats + fileName: string + minify?: boolean | 'esbuild' | 'terser' + target?: string | string[] + banner?: string + emptyOutDir?: boolean +} + +type LibraryConfig = InlineConfig & { + build: BuildOptions & { rollupOptions: NonNullable & { plugins: Plugin[] } } + define: Record +} + +export function getViteBuildConfig(options: LibraryConfigOptions): LibraryConfig { + const { entry, outDir, name, format, fileName, minify = 'esbuild', target = 'es2020', banner, emptyOutDir = false } = options + return { + configFile: false, + publicDir: false, + logLevel: 'warn', + build: { + outDir, + emptyOutDir, + minify, + target, + lib: { entry, name, formats: [format], fileName: () => fileName }, + rollupOptions: { + output: { exports: 'default' }, + plugins: banner ? [bannerPlugin(name, banner)] : [], + }, + }, + worker: { format: 'es', plugins: () => [workerBannerPlugin()] }, + define: { 'process.env.NODE_ENV': JSON.stringify(minify ? 'production' : 'development') }, + } +} diff --git a/scripts/library/development.ts b/scripts/library/development.ts new file mode 100644 index 000000000..bee1399e9 --- /dev/null +++ b/scripts/library/development.ts @@ -0,0 +1,73 @@ +import fs from 'node:fs' +import path from 'node:path' +import servor from 'servor' +import openBrowser from 'servor/utils/openBrowser.js' +import { build as viteBuild } from 'vite' +import { getViteBuildConfig } from './config.ts' +import { getGlobalName } from './names.ts' +import { getEntryFile, getProjects, selectProjects } from './projects.ts' +import { createRebuildQueue } from './rebuild.ts' + +async function develop(projectPath: string, name: string, open: boolean, port: number) { + const uncompiledPath = path.resolve(`docs/uncompiled/${name}`) + let browserOpened = false + + // Servor enumerates existing directories when installing its Linux watchers. + fs.mkdirSync(uncompiledPath, { recursive: true }) + + const { url } = await servor({ + root: 'docs', + fallback: 'index.html', + reload: true, + port, + }) + + async function buildBundle() { + const startTime = Date.now() + try { + const config = getViteBuildConfig({ + entry: getEntryFile(projectPath), + outDir: uncompiledPath, + name: getGlobalName(name), + format: 'iife', + fileName: 'index.js', + minify: false, + emptyOutDir: true, + }) + config.define['process.env.NODE_ENV'] = JSON.stringify('development') + + await viteBuild({ root: projectPath, ...config }) + console.log(`[${name}] ✅ Built in ${Date.now() - startTime}ms`) + + if (open && !browserOpened) { + browserOpened = true + openBrowser(url) + } + } + catch (error) { + console.error(`[${name}] ❌ Build error:`, error) + } + } + + const rebuild = createRebuildQueue(buildBundle) + await rebuild() + console.log(`[${name}] Demo: ${url}/?libs=./uncompiled/${name}/index.js`) + + const srcPath = path.join(projectPath, 'src') + console.log(`[${name}] 👀 Watching ${srcPath}...`) + + fs.watch(srcPath, { recursive: true }, async (_, filename) => { + if (filename) { + console.log(`[${name}] 📝 Changed: ${filename}`) + await rebuild() + } + }) +} + +export async function runDevelop({ port = 8082 }: { port?: number } = {}): Promise { + const projects = getProjects() + const { names, open } = await selectProjects(projects, 'dev') + const name = names[0] + if (name) + await develop(projects[name]!, name, open, port) +} diff --git a/scripts/library/names.ts b/scripts/library/names.ts new file mode 100644 index 000000000..8e7975a04 --- /dev/null +++ b/scripts/library/names.ts @@ -0,0 +1,11 @@ +export function toPascalCase(name: string): string { + return name.replace(/(^|-)([a-z])/g, (_, _prefix: string, letter: string) => letter.toUpperCase()) +} + +export function toCamelCase(name: string): string { + return name.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase()) +} + +export function getGlobalName(name: string): string { + return name === 'artplayer' || name.startsWith('artplayer-tool-') ? toPascalCase(name) : toCamelCase(name) +} diff --git a/scripts/library/production.ts b/scripts/library/production.ts new file mode 100644 index 000000000..cf51df71c --- /dev/null +++ b/scripts/library/production.ts @@ -0,0 +1,88 @@ +import fs from 'node:fs' +import path from 'node:path' +import cpy from 'cpy' +import { build as viteBuild } from 'vite' +import { buildAnalysisPlugin, createBuildAnalysis } from './analysis.ts' +import { getBanner } from './banner.ts' +import { getViteBuildConfig } from './config.ts' +import { getGlobalName } from './names.ts' +import { getEntryFile, getProjects, selectProjects } from './projects.ts' + +const BUILD_FORMATS = { + main: { format: 'umd', ext: '.js', target: 'es2020', minify: 'terser' }, + legacy: { format: 'umd', ext: '.legacy.js', target: 'es2015', minify: 'terser' }, + esm: { format: 'es', ext: '.mjs', target: 'es2020', minify: false }, +} as const + +async function build(projectDir: string, compiledPath: string, name: string, targetName: keyof typeof BUILD_FORMATS, clean = false, analysis?: ReturnType) { + const manifest: unknown = JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), 'utf-8')) + if (!manifest || typeof manifest !== 'object' || !('version' in manifest) || typeof manifest.version !== 'string') + throw new Error(`Missing package version: ${name}`) + const { version } = manifest + const distDir = path.join(projectDir, 'dist') + const entryFile = getEntryFile(projectDir) + + if (clean && fs.existsSync(distDir)) { + if (fs.realpathSync(distDir) !== path.join(fs.realpathSync(projectDir), 'dist')) + throw new Error(`Refusing to clean redirected dist directory: ${distDir}`) + fs.rmSync(distDir, { recursive: true, force: true }) + } + + const startTime = Date.now() + const { format, ext, target, minify } = BUILD_FORMATS[targetName] + const fileName = `${name}${ext}` + + const config = getViteBuildConfig({ + entry: entryFile, + outDir: distDir, + name: getGlobalName(name), + format, + fileName, + minify, + target, + banner: getBanner(projectDir, name, version), + }) + + // Terser options for all builds (preserve banner comments starting with /*!) + const isLegacy = targetName === 'legacy' + config.build.terserOptions = { + ecma: isLegacy ? 5 : 2020, + compress: { ecma: isLegacy ? 5 : 2020, comparisons: false, inline: 2 }, + mangle: { safari10: true }, + format: { + ecma: isLegacy ? 5 : 2020, + comments: /^!/, + ascii_only: true, + max_line_len: false, // No line length limit + }, + } + if (analysis) + config.build.rollupOptions.plugins.push(buildAnalysisPlugin(analysis, name, targetName)) + + await viteBuild({ root: projectDir, ...config }) + + const outFile = path.join(distDir, fileName) + const size = (fs.statSync(outFile).size / 1024).toFixed(2) + await cpy(outFile, compiledPath, { flat: true }) + + console.log(`✨ Built@${targetName} ${name}@${version} Time@${Date.now() - startTime}ms Size@${size}kb`) +} + +async function buildProject(projectDir: string, compiledPath: string, name: string, analysis?: ReturnType) { + await build(projectDir, compiledPath, name, 'main', true, analysis) + await build(projectDir, compiledPath, name, 'legacy', false, analysis) + await build(projectDir, compiledPath, name, 'esm', false, analysis) +} + +export async function runBuild(): Promise { + const projects = getProjects() + const compiledPath = path.resolve('docs/compiled') + const { names, analyze } = await selectProjects(projects, 'build') + const analysis = analyze ? createBuildAnalysis() : undefined + for (const name of names) + await buildProject(projects[name]!, compiledPath, name, analysis) + if (analysis) + console.log(`Build analysis: ${analysis.directory}`) + if (names.length) + console.log(`✅ Finished building ${names.length} package(s)!`) +} diff --git a/scripts/library/projects.ts b/scripts/library/projects.ts new file mode 100644 index 000000000..294aeed52 --- /dev/null +++ b/scripts/library/projects.ts @@ -0,0 +1,72 @@ +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import prompts from 'prompts' + +export type Projects = Record +export type Command = 'build' | 'dev' +export interface Selection { names: string[], open: boolean, analyze?: boolean, help?: boolean } + +export function getProjects(root = process.cwd()): Projects { + const packages = path.join(root, 'packages') + return Object.fromEntries(fs.readdirSync(packages).sort() + .filter(name => name !== 'artplayer-vitepress' && fs.existsSync(path.join(packages, name, 'package.json'))) + .map(name => [name, path.join(packages, name)])) +} + +export function getEntryFile(projectDir: string): string { + const entries = ['index.js', 'index.ts'].map(name => path.join(projectDir, 'src', name)).filter(file => fs.existsSync(file) && fs.statSync(file).isFile()) + if (entries.length !== 1) + throw new Error(`${path.basename(projectDir)} must have exactly one src/index.js or src/index.ts; found ${entries.length}`) + return entries[0]! +} + +export function parseProjects(args: string[], projects: Projects, command: Command): Selection { + const names: string[] = [] + let open = true + let analyze = false + for (const arg of args) { + if (arg === '--no-open' && command === 'dev') + open = false + else if (arg === '--analyze' && command === 'build') + analyze = true + else if (arg === '--help') + return { help: true, names: [], open } + else if (arg !== '--') + names.push(arg) + } + if (names.includes('all')) { + if (command !== 'build' || names.length !== 1) + throw new Error('Use build all on its own; dev accepts one package') + return { names: Object.keys(projects), open, analyze } + } + for (const name of names) { + if (!Object.hasOwn(projects, name)) + throw new Error(`Unknown package: ${name}. Use yarn ${command} --help`) + } + if (command === 'dev' && names.length > 1) + throw new Error('dev accepts one package') + return { names: [...new Set(names)], open, analyze } +} + +export async function selectProjects(projects: Projects, command: Command) { + const selection = parseProjects(process.argv.slice(2), projects, command) + if (selection.help) { + console.log(command === 'build' ? 'Usage: yarn build [all | package ...] [--analyze]' : 'Usage: yarn dev [package] [--no-open]') + return selection + } + if (!selection.names.length) { + if (!process.stdin.isTTY) + throw new Error(`Select a package explicitly in non-interactive mode: yarn ${command} artplayer`) + const { value } = await prompts({ + type: 'select', + name: 'value', + message: `Which project do you want to ${command === 'build' ? 'build' : 'develop'}?`, + choices: Object.keys(projects).map(name => ({ title: name, value: name })), + }) + selection.names = value ? [value] : [] + } + for (const name of selection.names) + getEntryFile(projects[name]!) + return selection +} diff --git a/scripts/library/rebuild.ts b/scripts/library/rebuild.ts new file mode 100644 index 000000000..8d4c11774 --- /dev/null +++ b/scripts/library/rebuild.ts @@ -0,0 +1,16 @@ +export function createRebuildQueue(build: () => void | Promise): () => Promise { + let pending = false + let active: Promise | undefined + return () => { + pending = true + if (!active) { + active = Promise.resolve().then(async () => { + while (pending) { + pending = false + await build() + } + }).finally(() => { active = undefined }) + } + return active + } +} diff --git a/scripts/library/vendor.d.ts b/scripts/library/vendor.d.ts new file mode 100644 index 000000000..1a2b52b16 --- /dev/null +++ b/scripts/library/vendor.d.ts @@ -0,0 +1,22 @@ +// Only the installed APIs used by library tooling; not general SDK declarations. +declare module 'prompts' { + export default function prompts(question: { + type: 'select' + name: 'value' + message: string + choices: { title: string, value: string }[] + }): Promise<{ value?: string }> +} + +declare module 'servor' { + export default function servor(options: { + root: string + fallback: string + reload: boolean + port: number + }): Promise<{ url: string }> +} + +declare module 'servor/utils/openBrowser.js' { + export default function openBrowser(url: string): true | undefined +} diff --git a/scripts/performance-artifacts.mjs b/scripts/performance-artifacts.mjs index a1ef19326..894f017f7 100644 --- a/scripts/performance-artifacts.mjs +++ b/scripts/performance-artifacts.mjs @@ -29,6 +29,10 @@ export function verifyPerformanceArtifacts(root, mapFile) { toolchain[file] = digest(path.join(root, file)) assert.equal(digest(path.join(directory, 'build', file)), toolchain[file], `Stale build input: ${file}`) } + const libraryInputs = location => Object.fromEntries(walk(path.join(location, 'scripts/library')).filter(file => /\.(?:[cm]?[jt]s|json)$/.test(file)).sort().map(file => [path.relative(location, file).replaceAll('\\', '/'), digest(file)])) + const currentLibrary = libraryInputs(root) + assert.deepEqual(libraryInputs(path.join(directory, 'build')), currentLibrary, 'Stale library build inputs; rerun yarn test:package') + Object.assign(toolchain, currentLibrary) const inputs = [] for (const name of ['artplayer', 'artplayer-plugin-chapter']) { const pkg = report.packages.find(pkg => pkg.name === name) diff --git a/scripts/projects.js b/scripts/projects.js index 7caedfc70..908676844 100644 --- a/scripts/projects.js +++ b/scripts/projects.js @@ -1,68 +1 @@ -import fs from 'node:fs' -import path from 'node:path' -import process from 'node:process' -import prompts from 'prompts' - -export function getProjects(root = process.cwd()) { - const packages = path.join(root, 'packages') - return Object.fromEntries(fs.readdirSync(packages).sort() - .filter(name => name !== 'artplayer-vitepress' && fs.existsSync(path.join(packages, name, 'package.json'))) - .map(name => [name, path.join(packages, name)])) -} - -export function getEntryFile(projectDir) { - const entries = ['index.js', 'index.ts'].map(name => path.join(projectDir, 'src', name)).filter(file => fs.existsSync(file) && fs.statSync(file).isFile()) - if (entries.length !== 1) - throw new Error(`${path.basename(projectDir)} must have exactly one src/index.js or src/index.ts; found ${entries.length}`) - return entries[0] -} - -export function parseProjects(args, projects, command) { - const names = [] - let open = true - let analyze = false - for (const arg of args) { - if (arg === '--no-open' && command === 'dev') - open = false - else if (arg === '--analyze' && command === 'build') - analyze = true - else if (arg === '--help') - return { help: true, names: [], open } - else if (arg !== '--') - names.push(arg) - } - if (names.includes('all')) { - if (command !== 'build' || names.length !== 1) - throw new Error('Use build all on its own; dev accepts one package') - return { names: Object.keys(projects), open, analyze } - } - for (const name of names) { - if (!Object.hasOwn(projects, name)) - throw new Error(`Unknown package: ${name}. Use yarn ${command} --help`) - } - if (command === 'dev' && names.length > 1) - throw new Error('dev accepts one package') - return { names: [...new Set(names)], open, analyze } -} - -export async function selectProjects(projects, command) { - const selection = parseProjects(process.argv.slice(2), projects, command) - if (selection.help) { - console.log(command === 'build' ? 'Usage: yarn build [all | package ...] [--analyze]' : 'Usage: yarn dev [package] [--no-open]') - return selection - } - if (!selection.names.length) { - if (!process.stdin.isTTY) - throw new Error(`Select a package explicitly in non-interactive mode: yarn ${command} artplayer`) - const { value } = await prompts({ - type: 'select', - name: 'value', - message: `Which project do you want to ${command === 'build' ? 'build' : 'develop'}?`, - choices: Object.keys(projects).map(name => ({ title: name, value: name })), - }) - selection.names = value ? [value] : [] - } - for (const name of selection.names) - getEntryFile(projects[name]) - return selection -} +export { getEntryFile, getProjects, parseProjects, selectProjects } from './library/projects.ts' diff --git a/scripts/rebuild.js b/scripts/rebuild.js index 6420ec630..2f6721c8a 100644 --- a/scripts/rebuild.js +++ b/scripts/rebuild.js @@ -1,16 +1 @@ -export function createRebuildQueue(build) { - let pending = false - let active - return () => { - pending = true - if (!active) { - active = Promise.resolve().then(async () => { - while (pending) { - pending = false - await build() - } - }).finally(() => { active = undefined }) - } - return active - } -} +export { createRebuildQueue } from './library/rebuild.ts' diff --git a/scripts/tsconfig.library.json b/scripts/tsconfig.library.json new file mode 100644 index 000000000..cbaeded00 --- /dev/null +++ b/scripts/tsconfig.library.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["node"], + "allowImportingTsExtensions": true, + "checkJs": true + }, + "include": ["library/**/*.ts", "build.js", "dev.js", "utils.js", "projects.js", "rebuild.js", "build-analysis.mjs"] +} diff --git a/scripts/utils.js b/scripts/utils.js index 1da24e90b..769829198 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -1,114 +1,3 @@ -export { getProjects } from './projects.js' - -// Convert kebab-case to PascalCase: artplayer-plugin-ads -> ArtplayerPluginAds -export function toPascalCase(name) { - return name.replace(/(^|-)([a-z])/g, (_, _p, c) => c.toUpperCase()) -} - -// Convert kebab-case to camelCase: artplayer-plugin-ads -> artplayerPluginAds -export function toCamelCase(name) { - return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase()) -} - -// Get the global variable name for a package -// - artplayer, artplayer-tool-* -> PascalCase (class) -// - artplayer-plugin-*, artplayer-proxy-* -> camelCase (function) -export function getGlobalName(name) { - if (name === 'artplayer' || name.startsWith('artplayer-tool-')) { - return toPascalCase(name) - } - return toCamelCase(name) -} - -// Get Vite build config -export function getViteBuildConfig(options) { - const { - entry, - outDir, - name, - format, - fileName, - minify = 'esbuild', - target = 'es2020', - banner, - emptyOutDir = false, - } = options - - const config = { - configFile: false, - publicDir: false, - logLevel: 'warn', - build: { - outDir, - emptyOutDir, - minify, - target, - lib: { - entry, - name, - formats: [format], - fileName: () => fileName, - }, - rollupOptions: { - output: { - exports: 'default', - }, - plugins: banner - ? [{ - name: 'add-banner-and-global', - generateBundle(outputOptions, bundle) { - for (const chunk of Object.values(bundle)) { - if (chunk.type === 'chunk') { - // Remove any inline worker banners (inside template literals) - // Match: `/*! ... */\n and replace with just ` - let code = chunk.code.replace(/`\/\*![\s\S]*?\*\/\n/g, '`') - // Add main banner if not present - if (!code.startsWith('/*!')) { - code = `${banner}\n${code}` - } - // For UMD format, modify the wrapper to always expose global - // even when AMD loader is present (fix for RequireJS environments) - if (outputOptions.format === 'umd') { - const wrapper = code.replace(/^\/\*![\s\S]*?\*\/\s*/, '').match(/^[!(\s]*function\s*\(\s*([\w$]+)\s*,\s*([\w$]+)\s*\)/) - if (!wrapper) - throw new Error(`Unrecognized UMD wrapper for ${name}`) - const global = wrapper[1] - const amd = /["']function["']\s*===?\s*typeof define\s*&&\s*define\.amd\s*\?\s*define\(([\w$]+)\)\s*:/ - if (!amd.test(code)) - throw new Error(`Unrecognized AMD branch for ${name}`) - // Keep AMD and the global export identical regardless of minified parameter names. - code = code.replace( - amd, - `"function"==typeof define&&define.amd?(${global}.${name}=$1(),define(function(){return ${global}.${name}})):`, - ) - } - chunk.code = code - } - } - }, - }] - : [], - }, - }, - // Disable banner for inline workers - worker: { - format: 'es', - plugins: () => [{ - name: 'remove-worker-banner', - generateBundle(_, bundle) { - for (const chunk of Object.values(bundle)) { - if (chunk.type === 'chunk' && chunk.code.startsWith('/*!')) { - // Remove banner from worker code - chunk.code = chunk.code.replace(/^\/\*![\s\S]*?\*\/\s*/, '') - } - } - }, - }], - }, - define: { - 'process.env.NODE_ENV': JSON.stringify(minify ? 'production' : 'development'), - }, - } - - return config -} +export { getViteBuildConfig } from './library/config.ts' +export { getGlobalName, toCamelCase, toPascalCase } from './library/names.ts' +export { getProjects } from './library/projects.ts' diff --git a/test/README.md b/test/README.md index 4ccb1ff27..337c3ed89 100644 --- a/test/README.md +++ b/test/README.md @@ -1,5 +1,10 @@ # Tests and fixture ownership +`yarn test:library` runs actual library builds, build-input freshness checks and the +scaffold's generated consumers. The new build modules live in `scripts/library/`; +`yarn typecheck:library` checks them and the retained JS/MJS entrypoints. Browser +development/watch/error-recovery coverage is `browser/library-development.spec.js`. + `yarn test:scaffold` covers old plugin-generator defects, exclusive writes and rollback, the historical CLI path, and actual generated package builds and type consumers. It runs in ignored fixtures without adding a workspace or real demo; diff --git a/test/browser/README.md b/test/browser/README.md index 357827a84..dadd77264 100644 --- a/test/browser/README.md +++ b/test/browser/README.md @@ -1,5 +1,11 @@ # Browser regression entry +`library-development.spec.js` starts the actual typed development runner on an +isolated fixture port. It checks generated TS/Less/SVG/inline-worker assets, +automatic reload on edits and recovery after a compiler error in all three +engines. It owns and stops only its child server, never the user's port 8082 +session. This is development-tool validation, not media playback acceptance. + `site-loading.spec.js` covers actual desktop/mobile loading, dependency order, failure/retry/AMD restoration, example priority, repeated Run and stale responses, plus Run Code and language routing. External analytics/ads are inert test routes; diff --git a/test/browser/library-development.spec.js b/test/browser/library-development.spec.js new file mode 100644 index 000000000..a1b140a6a --- /dev/null +++ b/test/browser/library-development.spec.js @@ -0,0 +1,101 @@ +import { spawn } from 'node:child_process' +import fs from 'node:fs' +import net from 'node:net' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath, pathToFileURL } from 'node:url' +import { expect, test } from '@playwright/test' + +const root = fileURLToPath(new URL('../../', import.meta.url)) +const cache = path.join(root, 'refactor/.cache') + +function removeFixture(directory) { + if (fs.realpathSync(directory) !== directory || path.dirname(directory) !== fs.realpathSync(cache)) + throw new Error(`Refusing redirected fixture cleanup: ${directory}`) + fs.rmSync(directory, { recursive: true, force: true }) +} + +async function freePort() { + const server = net.createServer() + await new Promise((resolve, reject) => { + server.once('error', reject) + server.listen(0, resolve) + }) + const port = server.address().port + await new Promise(resolve => server.close(resolve)) + return port +} + +test('typed development builds real assets and workers, watches edits and recovers from compilation errors', async ({ page }, testInfo) => { + test.setTimeout(60000) + const directory = fs.mkdtempSync(path.join(cache, 'library-development-')) + const name = 'artplayer-plugin-build-probe' + const source = path.join(directory, 'packages', name, 'src') + fs.mkdirSync(source, { recursive: true }) + fs.cpSync(path.join(root, 'refactor/fixtures/build'), source, { recursive: true }) + fs.writeFileSync(path.join(source, '../package.json'), JSON.stringify({ name, version: '1.0.0' })) + fs.mkdirSync(path.join(directory, 'docs')) + fs.writeFileSync(path.join(directory, 'docs/index.html'), `
+`) + const port = await freePort() + const url = `http://localhost:${port}` + const module = pathToFileURL(path.join(root, 'scripts/library/development.ts')).href + const code = `import { runDevelop } from ${JSON.stringify(module)}; +process.argv = [process.execPath, 'fixture.js', '${name}', '--no-open']; +await runDevelop({port:${port}});` + const launcher = path.join(directory, 'launch.mjs') + fs.writeFileSync(launcher, code) + const child = spawn(process.execPath, [launcher], { cwd: directory, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }) + let output = '' + child.stdout.on('data', (data) => { + output += data + }) + child.stderr.on('data', (data) => { + output += data + }) + const exited = new Promise(resolve => child.once('exit', resolve)) + try { + await expect.poll(() => output, { timeout: 20000 }).toContain('Watching') + expect(child.exitCode).toBeNull() + await page.goto(url) + await expect(page.locator('main')).toHaveText('42') + expect(await page.evaluate(() => window.probe.css)).toContain('#123abc') + expect(await page.evaluate(() => window.probe.svg)).toContain(' new Promise((resolve, reject) => { + const worker = window.probe.createWorker() + worker.onmessage = ({ data }) => { + worker.terminate() + resolve(data) + } + worker.onerror = (error) => { + worker.terminate() + reject(new Error(error.message)) + } + worker.postMessage(21) + })) + expect(workerResult).toBe(22) + const entry = path.join(source, 'index.ts') + const original = fs.readFileSync(entry, 'utf8') + const before = output.length + fs.writeFileSync(entry, original.replace('add(20, 22)', 'add(20, 23)')) + await expect.poll(() => output.slice(before), { timeout: 15000 }).toContain('Built in') + await expect(page.locator('main')).toHaveText('43') + const valid = output.length + fs.writeFileSync(entry, 'export default function broken( {') + await expect.poll(() => output.slice(valid), { timeout: 15000 }).toContain('Build error') + expect(child.exitCode).toBeNull() + const failed = output.length + fs.writeFileSync(entry, original.replace('add(20, 22)', 'add(20, 24)')) + await expect.poll(() => output.slice(failed), { timeout: 15000 }).toContain('Built in') + await expect(page.locator('main')).toHaveText('44') + } + finally { + await testInfo.attach('development-server.log', { body: output, contentType: 'text/plain' }) + child.kill() + await exited + removeFixture(directory) + } +}) diff --git a/test/performance-report.test.js b/test/performance-report.test.js index 94fa3b94c..e827a9783 100644 --- a/test/performance-report.test.js +++ b/test/performance-report.test.js @@ -87,6 +87,7 @@ test('performance refuses stale sources, build tools and altered installed bundl 'scripts/build.js': 'build fixture\n', 'scripts/utils.js': 'utils fixture\n', 'scripts/projects.js': 'projects fixture\n', + 'scripts/library/config.ts': 'typed build fixture\n', } for (const [file, content] of Object.entries(toolchain)) { write(path.join(root, file), content) @@ -115,6 +116,12 @@ test('performance refuses stale sources, build tools and altered installed bundl write(path.join(root, 'scripts/build.js'), 'changed compiler\n') assert.throws(() => verifyPerformanceArtifacts(root, map), /Stale build input/) write(path.join(root, 'scripts/build.js'), toolchain['scripts/build.js']) + write(path.join(root, 'scripts/library/config.ts'), 'changed TS config\n') + assert.throws(() => verifyPerformanceArtifacts(root, map), /Stale library build inputs/) + write(path.join(root, 'scripts/library/config.ts'), toolchain['scripts/library/config.ts']) + write(path.join(snapshot, 'scripts/library/removed.ts'), 'stale module\n') + assert.throws(() => verifyPerformanceArtifacts(root, map), /Stale library build inputs/) + fs.unlinkSync(path.join(snapshot, 'scripts/library/removed.ts')) write(path.join(output, 'artifacts/artplayer/dist/artplayer.js'), 'changed bundle\n') assert.throws(() => verifyPerformanceArtifacts(root, map), /Installed performance artifact changed/) })