@xiaobuyu/nodesign 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -106
- package/bin/nodesign.js +0 -0
- package/package.json +1 -1
- package/server/api/README.md +45 -45
- package/server/api/_guard.js +75 -75
- package/server/api/admin.js +187 -187
- package/server/api/assets/docx-page.js +103 -103
- package/server/api/assets/helpers.js +42 -22
- package/server/api/assets/notes.js +194 -144
- package/server/api/assets.js +891 -896
- package/server/api/board.js +134 -57
- package/server/api/browse.js +138 -138
- package/server/api/canvas.js +444 -444
- package/server/api/chatai.js +324 -324
- package/server/api/exports/build-standalone.js +980 -980
- package/server/api/exports/cards.js +118 -118
- package/server/api/exports/docx-pdf.js +46 -46
- package/server/api/exports/export-page.js +142 -142
- package/server/api/exports/handoff.js +147 -147
- package/server/api/exports.js +690 -690
- package/server/api/instruction.js +75 -75
- package/server/api/me.js +116 -116
- package/server/api/memory.js +185 -185
- package/server/api/pending-changes.js +366 -366
- package/server/api/plugins.js +164 -164
- package/server/api/projects.js +220 -220
- package/server/api/publish.js +64 -64
- package/server/api/recent.js +73 -73
- package/server/api/sessions.js +589 -589
- package/server/api/skills.js +77 -77
- package/server/api/standalone-fit.js +168 -168
- package/server/api/turn-compose.js +180 -180
- package/server/api/turn-inflight.js +44 -44
- package/server/auth/README.md +17 -17
- package/server/auth/middleware.js +138 -138
- package/server/auth/origin-guard.js +71 -71
- package/server/auth/session.js +108 -108
- package/server/auth/tier.js +113 -113
- package/server/auth/users-store.js +348 -348
- package/server/edit/README.md +29 -29
- package/server/engine/README.md +113 -113
- package/server/engine/agent/agent-shared.js +550 -550
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -4
- package/server/engine/agent/auto-mode-rules.js +78 -78
- package/server/engine/agent/context.js +418 -418
- package/server/engine/agent/events.js +465 -464
- package/server/engine/agent/hooks/canvas-validate.js +199 -199
- package/server/engine/agent/hooks/failure.js +120 -120
- package/server/engine/agent/hooks/file-events.js +75 -75
- package/server/engine/agent/hooks/lifecycle.js +233 -233
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -80
- package/server/engine/agent/hooks/post-guidance.js +206 -206
- package/server/engine/agent/hooks/post-subagent-report.js +104 -104
- package/server/engine/agent/hooks/post-trim.js +58 -58
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -34
- package/server/engine/agent/hooks/pre-defaults.js +84 -84
- package/server/engine/agent/hooks/pre-injectors.js +277 -277
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -78
- package/server/engine/agent/hooks/pre-starter-files.js +61 -61
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -78
- package/server/engine/agent/hooks/site-validate.js +94 -94
- package/server/engine/agent/hooks/tool-prompts.js +31 -31
- package/server/engine/agent/hooks/turn-state-memory.js +56 -56
- package/server/engine/agent/hooks/user-prompt-submit.js +258 -215
- package/server/engine/agent/hooks.js +354 -354
- package/server/engine/agent/init-contract.js +69 -69
- package/server/engine/agent/isolation.js +159 -159
- package/server/engine/agent/model-table.js +292 -292
- package/server/engine/agent/plugin-loader.js +266 -266
- package/server/engine/agent/prompts/nodesign-prelude.md +388 -336
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -71
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -98
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -583
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -483
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -100
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -270
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -70
- package/server/engine/agent/prompts/tools/site-reference.md +106 -106
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -125
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -48
- package/server/engine/agent/session-loop.js +1035 -1024
- package/server/engine/agent/session-model.js +164 -164
- package/server/engine/agent/skill.js +204 -204
- package/server/engine/agent/task-events.js +78 -78
- package/server/engine/agents/ds-extractor.md +132 -132
- package/server/engine/agents/explorer.md +190 -190
- package/server/engine/agents/index.js +230 -230
- package/server/engine/agents/schemas/design-system.json +156 -156
- package/server/engine/agents/schemas/tweak-schema.json +113 -113
- package/server/engine/agents/tweak-proposer.md +127 -127
- package/server/engine/agents/vision-checker.md +254 -254
- package/server/engine/browse/capture.js +422 -422
- package/server/engine/browse/card.js +137 -137
- package/server/engine/browse/handover.js +59 -59
- package/server/engine/browse/page-digest.js +119 -119
- package/server/engine/browse/refs.js +196 -196
- package/server/engine/browse/registry.js +307 -307
- package/server/engine/browse/screencast.js +170 -170
- package/server/engine/browse/state.js +121 -121
- package/server/engine/chatai/chat-log.js +79 -79
- package/server/engine/chatai/index.js +193 -193
- package/server/engine/chatai/openai-compat.js +152 -152
- package/server/engine/chatai/orchestrate.js +309 -309
- package/server/engine/chatai/perform.js +40 -40
- package/server/engine/chatai/summarize.js +79 -79
- package/server/engine/mcp/capability-gate.js +52 -52
- package/server/engine/mcp/index.js +351 -339
- package/server/engine/mcp/param-sanitizer.js +142 -142
- package/server/engine/mcp/tools/arrange-on-board.js +124 -124
- package/server/engine/mcp/tools/artifact-session.js +278 -278
- package/server/engine/mcp/tools/browse-computer.js +421 -421
- package/server/engine/mcp/tools/browse-find-batch.js +183 -183
- package/server/engine/mcp/tools/browse-screenshot.js +157 -157
- package/server/engine/mcp/tools/browse.js +480 -480
- package/server/engine/mcp/tools/build-docx.js +101 -101
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -99
- package/server/engine/mcp/tools/create-on-board.js +152 -155
- package/server/engine/mcp/tools/crystallize-skill.js +168 -168
- package/server/engine/mcp/tools/edit-sketch.js +167 -0
- package/server/engine/mcp/tools/explain-style.js +237 -237
- package/server/engine/mcp/tools/export-handoff.js +133 -133
- package/server/engine/mcp/tools/expose-tweaks.js +149 -149
- package/server/engine/mcp/tools/generate-image.js +842 -842
- package/server/engine/mcp/tools/get-computed-styles.js +164 -164
- package/server/engine/mcp/tools/get-pending-changes.js +205 -205
- package/server/engine/mcp/tools/h3box-ssh.js +84 -84
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
- package/server/engine/mcp/tools/highlight.js +68 -68
- package/server/engine/mcp/tools/list-pages.js +222 -222
- package/server/engine/mcp/tools/look-at-board.js +124 -0
- package/server/engine/mcp/tools/lookup-tags.js +59 -59
- package/server/engine/mcp/tools/navigate-to-page.js +60 -60
- package/server/engine/mcp/tools/organize-board.js +64 -64
- package/server/engine/mcp/tools/paint-still.js +434 -434
- package/server/engine/mcp/tools/pin-to-board.js +130 -130
- package/server/engine/mcp/tools/preview-deck.js +130 -130
- package/server/engine/mcp/tools/profile-scroll.js +305 -305
- package/server/engine/mcp/tools/publish-site.js +102 -102
- package/server/engine/mcp/tools/query-elements.js +195 -195
- package/server/engine/mcp/tools/read-board.js +190 -103
- package/server/engine/mcp/tools/read-document.js +93 -93
- package/server/engine/mcp/tools/read-page.js +264 -264
- package/server/engine/mcp/tools/read-tavern-json.js +143 -143
- package/server/engine/mcp/tools/read-user-view.js +59 -0
- package/server/engine/mcp/tools/record-decision.js +140 -140
- package/server/engine/mcp/tools/relate-on-board.js +112 -110
- package/server/engine/mcp/tools/report-issue.js +115 -115
- package/server/engine/mcp/tools/roll-film.js +266 -266
- package/server/engine/mcp/tools/screenshot-docx.js +138 -138
- package/server/engine/mcp/tools/screenshot-url.js +195 -195
- package/server/engine/mcp/tools/screenshot.js +583 -583
- package/server/engine/mcp/tools/sketch-on-board.js +337 -0
- package/server/engine/mcp/tools/tier-gate.js +96 -96
- package/server/engine/mcp/tools/trace-motion.js +215 -215
- package/server/engine/mcp/tools/web-search.js +548 -548
- package/server/engine/mcp/tools/write-on-board.js +130 -0
- package/server/engine/motion/inventory.js +349 -349
- package/server/engine/perception/session.js +235 -235
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +199 -192
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +169 -162
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +235 -228
- package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -78
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +330 -323
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
- package/server/engine/runs/active-runs.js +698 -698
- package/server/engine/runs/board-tasklist.js +130 -0
- package/server/engine/runs/live-turn.js +252 -252
- package/server/engine/runs/store.js +364 -364
- package/server/engine/runs/turn-relay.js +217 -217
- package/server/engine/runtime/workspace.js +164 -164
- package/server/index.js +3 -0
- package/server/lib/artifact-file-path.js +80 -80
- package/server/lib/artifact-target.js +284 -284
- package/server/lib/asset-refs.js +239 -239
- package/server/lib/async-queue.js +104 -104
- package/server/lib/auto-relations.js +130 -130
- package/server/lib/binding-types.js +72 -60
- package/server/lib/board-graph-export.js +191 -0
- package/server/lib/board-groups.js +167 -0
- package/server/lib/board-hero.js +76 -0
- package/server/lib/board-kind-sizes.js +85 -62
- package/server/lib/board-relations.js +133 -133
- package/server/lib/browse-proxy.js +180 -180
- package/server/lib/canvas-id.js +36 -36
- package/server/lib/chalk.js +121 -0
- package/server/lib/cover.js +227 -227
- package/server/lib/danbooru-tags.js +291 -291
- package/server/lib/doc-extract.js +156 -156
- package/server/lib/docx/build-from-source.js +260 -260
- package/server/lib/docx/build.js +490 -490
- package/server/lib/docx/dump-styles.js +287 -287
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
- package/server/lib/docx/merge-runs.js +129 -129
- package/server/lib/docx/numbering.js +218 -218
- package/server/lib/docx/order.js +152 -152
- package/server/lib/docx/rawzip.js +172 -172
- package/server/lib/docx/render.js +92 -92
- package/server/lib/docx/text-lint.js +225 -225
- package/server/lib/docx/tokens.js +335 -335
- package/server/lib/docx/units.js +29 -29
- package/server/lib/docx/xml.js +291 -291
- package/server/lib/docx-pages.js +187 -187
- package/server/lib/export-collect.js +289 -289
- package/server/lib/export-package.js +210 -210
- package/server/lib/html-srcset.js +145 -145
- package/server/lib/image-variant.js +460 -460
- package/server/lib/ingress/forward-openai-chat.js +305 -305
- package/server/lib/ingress/openai-chat.js +491 -491
- package/server/lib/ingress/session-notice.js +66 -66
- package/server/lib/ingress/session-routes.js +68 -68
- package/server/lib/ingress/slot-probe.js +130 -130
- package/server/lib/ingress/upstream-billing.js +52 -52
- package/server/lib/ingress/upstream-fail-streak.js +72 -72
- package/server/lib/ingress/upstream-truncation.js +48 -48
- package/server/lib/issues-store.js +182 -182
- package/server/lib/kinds/deck.js +72 -72
- package/server/lib/kinds/docx.js +283 -283
- package/server/lib/kinds/file-kinds.js +113 -113
- package/server/lib/kinds/index.js +202 -202
- package/server/lib/kinds/site.js +161 -161
- package/server/lib/model-ingress.js +543 -543
- package/server/lib/moderation.js +255 -255
- package/server/lib/notice-store.js +103 -103
- package/server/lib/plugin-install.js +104 -104
- package/server/lib/plugin-validator.js +721 -721
- package/server/lib/publish-store.js +115 -115
- package/server/lib/quick-summary.js +52 -52
- package/server/lib/quota.js +270 -270
- package/server/lib/rate-window.js +29 -29
- package/server/lib/reference-assets.js +92 -92
- package/server/lib/region-shot.js +135 -135
- package/server/lib/safe-path.js +73 -73
- package/server/lib/sdk-session.js +33 -33
- package/server/lib/showcase-store.js +92 -92
- package/server/lib/site-publish.js +465 -465
- package/server/lib/sketch-layout.js +277 -0
- package/server/lib/ssrf-guard.js +432 -432
- package/server/lib/task-scan.js +158 -158
- package/server/lib/tavern-json.js +154 -154
- package/server/lib/video-variant.js +237 -237
- package/server/lib/workspace-path.js +33 -33
- package/server/ops/fix-sdk-musl.mjs +40 -40
- package/server/ops/install-macos-fonts.sh +114 -114
- package/server/ops/macos-fonts.conf +336 -336
- package/server/ops/sandbox-shim/bwrap +57 -57
- package/server/projects/assets-summary.js +119 -119
- package/server/projects/auto-name.js +53 -53
- package/server/projects/board-limits.js +12 -0
- package/server/projects/board-sanitize.js +252 -0
- package/server/projects/board-store.js +507 -640
- package/server/projects/move-entry.js +103 -103
- package/server/projects/store.js +270 -270
- package/server/projects/ui-config.js +60 -54
- package/server/projects/viewpoint-store.js +78 -0
- package/server/projects/workspace-templates.js +53 -53
- package/server/projects/workspace.js +1025 -1025
- package/server/runtime/local-config.js +177 -177
- package/server/runtime/profile.js +72 -72
- package/server/services/rembg-service.py +334 -334
- package/server/shared/README.md +25 -25
- package/server/shared/deck.js +54 -54
- package/server/shared/time.js +49 -49
- package/server/style-pipeline/README.md +53 -53
- package/server/ws/broker.js +39 -36
- package/server/ws/browse-channel.js +177 -177
- package/server/ws/index.js +386 -386
- package/web/README.md +89 -89
- package/web/dist/assets/BrowserWindow-P_vIdh-G.js +11 -0
- package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-BpLv5082.js} +3 -3
- package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-DSAU1-tD.js} +4 -4
- package/web/dist/assets/MermaidBlock-DR1GikCv.js +313 -0
- package/web/dist/assets/SiteWindow-92uBuRRR.js +18 -0
- package/web/dist/assets/abnfDiagram-VCTEODGH-uA8Esix4.js +1 -0
- package/web/dist/assets/arc-D_khH_xN.js +1 -0
- package/web/dist/assets/architectureDiagram-5GKGNRK7-DydJHqOt.js +36 -0
- package/web/dist/assets/blockDiagram-NRAW4CY4-B7wBbKiW.js +129 -0
- package/web/dist/assets/c4Diagram-UCG6FXSJ-6mrH0x7Y.js +38 -0
- package/web/dist/assets/channel-DGkccMO1.js +1 -0
- package/web/dist/assets/chunk-2Q5K7J3B-Cuikho5Q.js +1 -0
- package/web/dist/assets/chunk-5VM5RSS4-3-KZ5AUz.js +15 -0
- package/web/dist/assets/chunk-F27PBJKO-Bkwq2gGU.js +1 -0
- package/web/dist/assets/chunk-G27WJ6UU-BLyQzdGE.js +231 -0
- package/web/dist/assets/chunk-JWPE2WC7-B7HxxQds.js +1 -0
- package/web/dist/assets/chunk-LCL6LL3I-CtXrnUwF.js +206 -0
- package/web/dist/assets/chunk-POPQ4Y6H-dxddZlXt.js +1 -0
- package/web/dist/assets/chunk-SVP7TREG-DpkmrgHn.js +88 -0
- package/web/dist/assets/chunk-XXDRQBXY-BwZ7RbpQ.js +1 -0
- package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +1 -0
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +1 -0
- package/web/dist/assets/cose-bilkent-JH36ORCC-DGFOsMUt.js +1 -0
- package/web/dist/assets/cynefin-OW5HDTMX-DUwn3ehS.js +178 -0
- package/web/dist/assets/cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js +62 -0
- package/web/dist/assets/cytoscape.esm-BjAbmLdb.js +331 -0
- package/web/dist/assets/dagre-3AP2YEHR-CyourLfz.js +4 -0
- package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/web/dist/assets/diagram-S7CK7UJ4-BTMbx2np.js +30 -0
- package/web/dist/assets/diagram-UQ7AKVKN-UK94_1-P.js +41 -0
- package/web/dist/assets/diagram-VSXAHHWV-C7zS-v9v.js +3 -0
- package/web/dist/assets/diagram-VX7I27RA-DKJRa6CK.js +24 -0
- package/web/dist/assets/diagram-Z3DM3KII-kUbAfGAp.js +24 -0
- package/web/dist/assets/ebnfDiagram-PWID7BFC-CVW16_vb.js +1 -0
- package/web/dist/assets/erDiagram-SSCWMZ5O-BRQMFQ5y.js +99 -0
- package/web/dist/assets/flowDiagram-A5DVABFB-S34LpLtK.js +168 -0
- package/web/dist/assets/ganttDiagram-EL5Y4UJY-BFiqk1-G.js +292 -0
- package/web/dist/assets/gitGraphDiagram-WWUBYQGX-LZsrdyyv.js +106 -0
- package/web/dist/assets/index-GAamh1p4.js +2152 -0
- package/web/dist/assets/infoDiagram-RXCK75RN-0DvN6KIp.js +2 -0
- package/web/dist/assets/init-Gi6I4Gst.js +1 -0
- package/web/dist/assets/ishikawaDiagram-5VMMS53U-CaY3A-Bx.js +70 -0
- package/web/dist/assets/journeyDiagram-EYS64GPL-BBeiaCrj.js +139 -0
- package/web/dist/assets/kanban-definition-3QL26DDD-ux42DRXQ.js +89 -0
- package/web/dist/assets/layout-CnXiF_NB.js +1 -0
- package/web/dist/assets/linear-BMW0u6G6.js +1 -0
- package/web/dist/assets/mindmap-definition-FBJOCRG2-hUxSpdKn.js +96 -0
- package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/web/dist/assets/pegDiagram-XKGWAZYB-aCaQPH0w.js +1 -0
- package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-AcdOQ9Ij.js} +6 -6
- package/web/dist/assets/pieDiagram-E7YTZNPT-CmOEZ8zg.js +39 -0
- package/web/dist/assets/quadrantDiagram-AXDQQJYC-VW6fAhsV.js +7 -0
- package/web/dist/assets/railroadDiagram-O6MQD6OU-CKJ7Ba5K.js +1 -0
- package/web/dist/assets/requirementDiagram-EFPCY7ZU-pNGkq9Vw.js +84 -0
- package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-KAfChbGV.js} +1 -1
- package/web/dist/assets/sankeyDiagram-P5KCCOFB-BIMmDTfZ.js +40 -0
- package/web/dist/assets/sequenceDiagram-WJ2MYXX4-BBW7wzKY.js +162 -0
- package/web/dist/assets/sizeCapture-X5ZJPWSS-DdkXmLYR.js +1 -0
- package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-CDUlhUP7.js} +1 -1
- package/web/dist/assets/stateDiagram-HBIQ2CUA-W313Uz16.js +1 -0
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +1 -0
- package/web/dist/assets/swimlanes-XN3QIQJK-C29TL26K.js +1 -0
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +8 -0
- package/web/dist/assets/timeline-definition-24CTP7MA-CF0JMObX.js +120 -0
- package/web/dist/assets/vennDiagram-4TSXK5OY-DrDFsaH3.js +34 -0
- package/web/dist/assets/wardleyDiagram-VM6X3IG4-CVrnRIS_.js +78 -0
- package/web/dist/assets/xychartDiagram-S5SC5T6Z-BvkN1zsT.js +7 -0
- package/web/dist/index.html +17 -17
- package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +0 -11
- package/web/dist/assets/SiteWindow-D6eS-2gM.js +0 -18
- package/web/dist/assets/index-Cbl3ATvp.js +0 -2125
package/server/lib/asset-refs.js
CHANGED
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/asset-refs.js — 产物真正引用到了哪些素材(2026-08-17)
|
|
3
|
-
*
|
|
4
|
-
* 为什么要这个:旧的工程交付包把**整个** `shared/assets` 递归塞进 zip。那是
|
|
5
|
-
* **项目级**共享目录,不是这份产物引用的东西 —— 生产上最大的一个项目 assets
|
|
6
|
-
* 有 280MB,导出一份 deck 会把项目里每一张图(包括别的任务的)一起交出去。
|
|
7
|
-
*
|
|
8
|
-
* 所以改成扫引用:产物的 html/css/js 里写了哪些素材,就带哪些。
|
|
9
|
-
*
|
|
10
|
-
* ⚠️ **扫不到的一律不猜,但必须说出来。** 静态引用是确定的;算不出来的(动态拼
|
|
11
|
-
* 路径、脚本里的字面量、`<base href>` 改了解析基准)**不往包里多塞**,而是记进
|
|
12
|
-
* `unresolved` 由调用方写成清单放进包里。用户拍的板:宁可在纸面上说清缺什么,
|
|
13
|
-
* 也不靠多塞蒙混过去。**「既不进包也不进清单」是这条规矩里最坏的状态** —— 那是
|
|
14
|
-
* 假装没这回事,比多塞还糟。
|
|
15
|
-
*
|
|
16
|
-
* ── 2026-08-17 评审后重写,六条实测出来的病(每条都有单测钉着)──
|
|
17
|
-
* 1. `<script src="x.js">` 随整块脚本被摘掉 → 真引用静默丢失
|
|
18
|
-
* 2. HTML 注释里的旧引用照收 → 别的任务的图混进包
|
|
19
|
-
* 3. `src="it's.png"` 用混合字符类解析 → 幻影路径 `it`
|
|
20
|
-
* 4. `file:` 之类 scheme 漏过滤(原来是枚举表)→ 幻影 ref
|
|
21
|
-
* 5. 脚本里的字面量素材(`['a.png','b.png']`)既不进包也不进清单
|
|
22
|
-
* 6. 树外引用全域放行 → `<a href="../别的任务/x.html">` 把别人的产物收进来
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import path from 'node:path';
|
|
26
|
-
import fs from 'node:fs/promises';
|
|
27
|
-
|
|
28
|
-
/** 会被扫的文本类型(二进制不扫) */
|
|
29
|
-
const SCANNABLE = /\.(html?|css|jsx?|mjs|cjs)$/i;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 不是本地素材的引用。**用泛化判据不用枚举表** —— 枚举表漏一个 scheme(`file:`)
|
|
33
|
-
* 就会产出幻影 ref。代价是 `x:30.png` 这种带冒号的文件名会被当 scheme 滤掉,
|
|
34
|
-
* 但浏览器本来也按 scheme 解析它,滤掉才是对齐浏览器语义。
|
|
35
|
-
*/
|
|
36
|
-
const EXTERNAL = /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 静态引用的抓取规则。
|
|
40
|
-
*
|
|
41
|
-
* ⚠️ **引号必须分支写**(`"([^"]*)"|'([^']*)'`),不能用 `["']([^"']+)["']` 那种
|
|
42
|
-
* 混合字符类:后者遇到 `src="it's.png"` 会在撇号处截断,抓出幻影 `it`。
|
|
43
|
-
* ⚠️ 属性名前的 `(?<![.\w-])` 挡的是 JS 里的 `img.src = '...'` —— 没有它,属性
|
|
44
|
-
* 规则会把代码里的赋值当成 HTML 属性,同样抓出幻影。
|
|
45
|
-
*/
|
|
46
|
-
/**
|
|
47
|
-
* **只对标记语言成立**的规则。`src=` / `href=` 是 HTML 属性 —— 拿它去扫 .js
|
|
48
|
-
* 会把 `const data = 'hello world'` 当成引用,捞出幻影路径 `站/hello world`
|
|
49
|
-
* 塞进 missing,交付物里印出假警报(实测踩过)。
|
|
50
|
-
*/
|
|
51
|
-
const MARKUP_PATTERNS = [
|
|
52
|
-
/(?<![.\w-])(?:src|href|poster|data-src|data)\s*=\s*(?:"([^"]*)"|'([^']*)')/gi,
|
|
53
|
-
/\bsrcset\s*=\s*(?:"([^"]*)"|'([^']*)')/gi,
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
/** 在 html / css / js 里语义都成立的规则 */
|
|
57
|
-
const UNIVERSAL_PATTERNS = [
|
|
58
|
-
/url\(\s*(?:"([^"]*)"|'([^']*)'|([^'")][^)]*?))\s*\)/gi,
|
|
59
|
-
/@import\s+(?:"([^"]*)"|'([^']*)')/gi,
|
|
60
|
-
/\bfrom\s+["']([^"']+\.(?:css|png|jpe?g|webp|gif|svg))["']/gi,
|
|
61
|
-
// `new URL('x.png', import.meta.url)` 的第一个参数是**字面量**,静态可判,
|
|
62
|
-
// 不该只当成"算不出来"记进清单。
|
|
63
|
-
/new\s+URL\s*\(\s*(?:"([^"]*)"|'([^']*)')\s*,\s*import\.meta\.url/gi,
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
/** 从一次匹配里取出那个真正被捕获的分支 */
|
|
67
|
-
function capturedOf(m) {
|
|
68
|
-
for (let i = 1; i < m.length; i++) if (m[i] != null) return m[i];
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* 摘掉脚本体但**保留开标签** —— `<script src="lib/chart.js"></script>` 里的 src
|
|
74
|
-
* 是真引用,整块摘掉会静默丢失它(实测踩过)。
|
|
75
|
-
*/
|
|
76
|
-
const SCRIPT_BLOCK = /(<script\b[^>]*>)[\s\S]*?<\/script>/gi;
|
|
77
|
-
/** HTML 注释:里面的引用是历史残留,收进来就是把别的任务的旧图打进包 */
|
|
78
|
-
const HTML_COMMENT = /<!--[\s\S]*?-->/g;
|
|
79
|
-
/** CSS / JS 的块注释。**不摘 `//` 行注释** —— 会误杀 `https://` */
|
|
80
|
-
const BLOCK_COMMENT = /\/\*[\s\S]*?\*\//g;
|
|
81
|
-
|
|
82
|
-
/** 动态拼路径:抓到只报告不解析 */
|
|
83
|
-
const DYNAMIC_HINTS = [
|
|
84
|
-
{ re: /["'][^"']*assets\/[^"']*["']\s*\+/gi, why: '字符串拼接' },
|
|
85
|
-
{ re: /`[^`]*assets\/[^`]*\$\{[^`]*`/gi, why: '模板字符串插值' },
|
|
86
|
-
{ re: /\+\s*["'][^"']*\.(?:png|jpe?g|webp|gif|svg|mp4)["']/gi, why: '字符串拼接' },
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
/** 脚本里的素材字面量(画廊 / lightbox 的常见写法),只进清单不进包 */
|
|
90
|
-
const SCRIPT_LITERAL = /["'`]([^"'`\n]{1,200}\.(?:png|jpe?g|webp|gif|svg|mp4|webm|pdf|woff2?))["'`]/gi;
|
|
91
|
-
|
|
92
|
-
/** 去掉查询串和锚点(`x.webp?w=480` → `x.webp`)、反斜杠归一、URL 解码 */
|
|
93
|
-
function cleanRef(raw) {
|
|
94
|
-
const s = String(raw).trim().replace(/\\/g, '/').split('#')[0].split('?')[0];
|
|
95
|
-
try { return decodeURIComponent(s); } catch { return s; }
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** srcset 拆成一组候选:`a.png 1x, b.png 2x` → ['a.png','b.png'] */
|
|
99
|
-
function splitSrcset(value) {
|
|
100
|
-
return value.split(',').map(part => part.trim().split(/\s+/)[0]).filter(Boolean);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 扫一批产物文件,收出它们引用到的素材。
|
|
105
|
-
*
|
|
106
|
-
* @param {object} opts
|
|
107
|
-
* @param {Array<{abs:string, rel:string}>} opts.files 要扫的文件(rel 相对 baseRoot)
|
|
108
|
-
* @param {string} opts.baseRoot 解析基准(工作区根)。返回的路径都相对它
|
|
109
|
-
* @param {string[]} [opts.allowPrefixes] 允许收的前缀白名单(树外只认这些,通常是
|
|
110
|
-
* 产物自己的根 + `assets/`)。**留空 = 不设闸**(根层产物就是这种情况,它的树
|
|
111
|
-
* 本来就是整个工作区)。被挡下的引用不静默丢,进 unresolved。
|
|
112
|
-
* @returns {Promise<{refs:string[], unresolved:Array<{from,why,snippet}>}>}
|
|
113
|
-
*/
|
|
114
|
-
export async function collectAssetRefs({ files, baseRoot, allowPrefixes = [] }) {
|
|
115
|
-
const own = new Set(files.map(f => f.rel.replace(/\\/g, '/')));
|
|
116
|
-
const refs = new Set();
|
|
117
|
-
const unresolved = [];
|
|
118
|
-
const candidates = []; // 脚本里的字面量,等调用方按"磁盘上真有"再提升
|
|
119
|
-
const seenHint = new Set(); // 同一句话被多条规则命中时去重
|
|
120
|
-
|
|
121
|
-
const pushHint = (from, why, snippet) => {
|
|
122
|
-
const s = String(snippet).replace(/\s+/g, ' ').slice(0, 120);
|
|
123
|
-
const key = `${from}|${s}`;
|
|
124
|
-
if (seenHint.has(key)) return;
|
|
125
|
-
seenHint.add(key);
|
|
126
|
-
unresolved.push({ from, why, snippet: s });
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const allowed = (rel) => {
|
|
130
|
-
if (!allowPrefixes.length) return true;
|
|
131
|
-
return allowPrefixes.some(p => (p === '' ? true : rel === p || rel.startsWith(p)));
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
for (const f of files) {
|
|
135
|
-
if (!SCANNABLE.test(f.rel)) continue;
|
|
136
|
-
let text;
|
|
137
|
-
try { text = await fs.readFile(f.abs, 'utf8'); } catch { continue; }
|
|
138
|
-
|
|
139
|
-
const fileDirAbs = path.dirname(f.abs);
|
|
140
|
-
const isMarkup = /\.html?$/i.test(f.rel);
|
|
141
|
-
const isScript = /\.(m?js|cjs|jsx)$/i.test(f.rel);
|
|
142
|
-
// 静态引用只在「非注释、非脚本体」的部分找;动态线索照旧扫全文
|
|
143
|
-
const staticText = text
|
|
144
|
-
.replace(HTML_COMMENT, ' ')
|
|
145
|
-
.replace(BLOCK_COMMENT, ' ')
|
|
146
|
-
.replace(SCRIPT_BLOCK, '$1</script>');
|
|
147
|
-
|
|
148
|
-
for (const pattern of (isMarkup ? [...MARKUP_PATTERNS, ...UNIVERSAL_PATTERNS] : UNIVERSAL_PATTERNS)) {
|
|
149
|
-
pattern.lastIndex = 0;
|
|
150
|
-
let m;
|
|
151
|
-
while ((m = pattern.exec(staticText)) !== null) {
|
|
152
|
-
const captured = capturedOf(m);
|
|
153
|
-
if (captured == null) continue;
|
|
154
|
-
const isSrcset = /srcset/i.test(pattern.source);
|
|
155
|
-
for (const c of (isSrcset ? splitSrcset(captured) : [captured])) {
|
|
156
|
-
if (!c || EXTERNAL.test(c.trim())) continue;
|
|
157
|
-
const cleaned = cleanRef(c);
|
|
158
|
-
if (!cleaned || cleaned.startsWith('/')) continue; // 根路径引用平台本来就禁
|
|
159
|
-
const abs = path.resolve(fileDirAbs, cleaned);
|
|
160
|
-
const rel = path.relative(baseRoot, abs).replace(/\\/g, '/');
|
|
161
|
-
if (!rel || rel.startsWith('..')) continue; // 逃出工作区的不收
|
|
162
|
-
if (own.has(rel)) continue; // 产物自己的页面不算素材
|
|
163
|
-
if (!allowed(rel)) {
|
|
164
|
-
pushHint(f.rel, '引用落在产物树外(只有 assets/ 和产物自己的目录会进包)', cleaned);
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
refs.add(rel);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// `<base href>` 会改写整页相对路径的解析基准,扫描器按文件目录解析就会整体偏移
|
|
173
|
-
if (/<base\b[^>]*\bhref/i.test(text)) {
|
|
174
|
-
pushHint(f.rel, '页面有 <base href>,本页所有相对引用的解析基准可能跟打包时算的不一样', '<base href=…>');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
for (const { re, why } of DYNAMIC_HINTS) {
|
|
178
|
-
re.lastIndex = 0;
|
|
179
|
-
let m;
|
|
180
|
-
while ((m = re.exec(text)) !== null) pushHint(f.rel, why, m[0]);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// 脚本里的素材字面量(画廊 / lightbox 的常见写法)。
|
|
184
|
-
// **完整字面量是静态可判的**,所以不直接判死:能落进工作区、又过得了树外闸的
|
|
185
|
-
// 交给调用方按「磁盘上真有这个文件」提升成素材;剩下的才进清单。
|
|
186
|
-
// 这不违反「扫不到的不多塞」—— 它扫得到,只是来源是脚本而不是标签。
|
|
187
|
-
const scanLiterals = (body) => {
|
|
188
|
-
SCRIPT_LITERAL.lastIndex = 0;
|
|
189
|
-
let lit;
|
|
190
|
-
while ((lit = SCRIPT_LITERAL.exec(body)) !== null) {
|
|
191
|
-
const cleaned = cleanRef(lit[1]);
|
|
192
|
-
const snippet = lit[0].slice(0, 120);
|
|
193
|
-
if (!cleaned || EXTERNAL.test(cleaned) || cleaned.startsWith('/')) {
|
|
194
|
-
pushHint(f.rel, '脚本里的字符串字面量(打包时不解析)', snippet); continue;
|
|
195
|
-
}
|
|
196
|
-
const rel = path.relative(baseRoot, path.resolve(fileDirAbs, cleaned)).replace(/\\/g, '/');
|
|
197
|
-
if (!rel || rel.startsWith('..') || own.has(rel) || !allowed(rel)) {
|
|
198
|
-
pushHint(f.rel, '脚本里的字符串字面量(打包时不解析)', snippet); continue;
|
|
199
|
-
}
|
|
200
|
-
candidates.push({ rel, from: f.rel, snippet });
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
// ⚠️ 从**剥过注释**的文本里取脚本体:标签侧早就剥了注释,字面量侧漏剥的话,
|
|
204
|
-
// 注释里的旧图会被提升进包 —— 同一条规矩两个半边,只修一半等于没修。
|
|
205
|
-
const commentFree = text.replace(HTML_COMMENT, ' ');
|
|
206
|
-
for (const sm of commentFree.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi)) scanLiterals(sm[1]);
|
|
207
|
-
// ⚠️ 独立的 .js 文件**整个就是脚本体**,没有 <script> 标签可匹配。
|
|
208
|
-
// 不加这条的话,同一份画廊代码写在 HTML 里能收、抽成 js 文件就双失踪。
|
|
209
|
-
if (isScript) scanLiterals(text.replace(BLOCK_COMMENT, ' '));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return { refs: [...refs].sort(), candidates, unresolved };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* 把 unresolved 渲染成放进包里的清单。没有动态引用时返回 null(不放空文件 ——
|
|
217
|
-
* 旧交付包里那个常年空着的 prompt.txt 就是这么来的)。
|
|
218
|
-
*/
|
|
219
|
-
export function renderUnresolvedReport(unresolved) {
|
|
220
|
-
if (!unresolved?.length) return null;
|
|
221
|
-
const lines = [
|
|
222
|
-
'# 没能解析的素材引用',
|
|
223
|
-
'',
|
|
224
|
-
'下面这些地方打包时算不出具体引用了哪个文件,**素材可能没进包**。',
|
|
225
|
-
'如果打开后有图裂了,照这个清单去原工程里找对应文件补进来。',
|
|
226
|
-
'',
|
|
227
|
-
];
|
|
228
|
-
const byFile = new Map();
|
|
229
|
-
for (const u of unresolved) {
|
|
230
|
-
if (!byFile.has(u.from)) byFile.set(u.from, []);
|
|
231
|
-
byFile.get(u.from).push(u);
|
|
232
|
-
}
|
|
233
|
-
for (const [file, items] of [...byFile.entries()].sort()) {
|
|
234
|
-
lines.push(`## ${file}`, '');
|
|
235
|
-
for (const it of items) lines.push(`- (${it.why})\`${it.snippet}\``);
|
|
236
|
-
lines.push('');
|
|
237
|
-
}
|
|
238
|
-
return lines.join('\n');
|
|
239
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* lib/asset-refs.js — 产物真正引用到了哪些素材(2026-08-17)
|
|
3
|
+
*
|
|
4
|
+
* 为什么要这个:旧的工程交付包把**整个** `shared/assets` 递归塞进 zip。那是
|
|
5
|
+
* **项目级**共享目录,不是这份产物引用的东西 —— 生产上最大的一个项目 assets
|
|
6
|
+
* 有 280MB,导出一份 deck 会把项目里每一张图(包括别的任务的)一起交出去。
|
|
7
|
+
*
|
|
8
|
+
* 所以改成扫引用:产物的 html/css/js 里写了哪些素材,就带哪些。
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ **扫不到的一律不猜,但必须说出来。** 静态引用是确定的;算不出来的(动态拼
|
|
11
|
+
* 路径、脚本里的字面量、`<base href>` 改了解析基准)**不往包里多塞**,而是记进
|
|
12
|
+
* `unresolved` 由调用方写成清单放进包里。用户拍的板:宁可在纸面上说清缺什么,
|
|
13
|
+
* 也不靠多塞蒙混过去。**「既不进包也不进清单」是这条规矩里最坏的状态** —— 那是
|
|
14
|
+
* 假装没这回事,比多塞还糟。
|
|
15
|
+
*
|
|
16
|
+
* ── 2026-08-17 评审后重写,六条实测出来的病(每条都有单测钉着)──
|
|
17
|
+
* 1. `<script src="x.js">` 随整块脚本被摘掉 → 真引用静默丢失
|
|
18
|
+
* 2. HTML 注释里的旧引用照收 → 别的任务的图混进包
|
|
19
|
+
* 3. `src="it's.png"` 用混合字符类解析 → 幻影路径 `it`
|
|
20
|
+
* 4. `file:` 之类 scheme 漏过滤(原来是枚举表)→ 幻影 ref
|
|
21
|
+
* 5. 脚本里的字面量素材(`['a.png','b.png']`)既不进包也不进清单
|
|
22
|
+
* 6. 树外引用全域放行 → `<a href="../别的任务/x.html">` 把别人的产物收进来
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import fs from 'node:fs/promises';
|
|
27
|
+
|
|
28
|
+
/** 会被扫的文本类型(二进制不扫) */
|
|
29
|
+
const SCANNABLE = /\.(html?|css|jsx?|mjs|cjs)$/i;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 不是本地素材的引用。**用泛化判据不用枚举表** —— 枚举表漏一个 scheme(`file:`)
|
|
33
|
+
* 就会产出幻影 ref。代价是 `x:30.png` 这种带冒号的文件名会被当 scheme 滤掉,
|
|
34
|
+
* 但浏览器本来也按 scheme 解析它,滤掉才是对齐浏览器语义。
|
|
35
|
+
*/
|
|
36
|
+
const EXTERNAL = /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 静态引用的抓取规则。
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ **引号必须分支写**(`"([^"]*)"|'([^']*)'`),不能用 `["']([^"']+)["']` 那种
|
|
42
|
+
* 混合字符类:后者遇到 `src="it's.png"` 会在撇号处截断,抓出幻影 `it`。
|
|
43
|
+
* ⚠️ 属性名前的 `(?<![.\w-])` 挡的是 JS 里的 `img.src = '...'` —— 没有它,属性
|
|
44
|
+
* 规则会把代码里的赋值当成 HTML 属性,同样抓出幻影。
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* **只对标记语言成立**的规则。`src=` / `href=` 是 HTML 属性 —— 拿它去扫 .js
|
|
48
|
+
* 会把 `const data = 'hello world'` 当成引用,捞出幻影路径 `站/hello world`
|
|
49
|
+
* 塞进 missing,交付物里印出假警报(实测踩过)。
|
|
50
|
+
*/
|
|
51
|
+
const MARKUP_PATTERNS = [
|
|
52
|
+
/(?<![.\w-])(?:src|href|poster|data-src|data)\s*=\s*(?:"([^"]*)"|'([^']*)')/gi,
|
|
53
|
+
/\bsrcset\s*=\s*(?:"([^"]*)"|'([^']*)')/gi,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/** 在 html / css / js 里语义都成立的规则 */
|
|
57
|
+
const UNIVERSAL_PATTERNS = [
|
|
58
|
+
/url\(\s*(?:"([^"]*)"|'([^']*)'|([^'")][^)]*?))\s*\)/gi,
|
|
59
|
+
/@import\s+(?:"([^"]*)"|'([^']*)')/gi,
|
|
60
|
+
/\bfrom\s+["']([^"']+\.(?:css|png|jpe?g|webp|gif|svg))["']/gi,
|
|
61
|
+
// `new URL('x.png', import.meta.url)` 的第一个参数是**字面量**,静态可判,
|
|
62
|
+
// 不该只当成"算不出来"记进清单。
|
|
63
|
+
/new\s+URL\s*\(\s*(?:"([^"]*)"|'([^']*)')\s*,\s*import\.meta\.url/gi,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/** 从一次匹配里取出那个真正被捕获的分支 */
|
|
67
|
+
function capturedOf(m) {
|
|
68
|
+
for (let i = 1; i < m.length; i++) if (m[i] != null) return m[i];
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 摘掉脚本体但**保留开标签** —— `<script src="lib/chart.js"></script>` 里的 src
|
|
74
|
+
* 是真引用,整块摘掉会静默丢失它(实测踩过)。
|
|
75
|
+
*/
|
|
76
|
+
const SCRIPT_BLOCK = /(<script\b[^>]*>)[\s\S]*?<\/script>/gi;
|
|
77
|
+
/** HTML 注释:里面的引用是历史残留,收进来就是把别的任务的旧图打进包 */
|
|
78
|
+
const HTML_COMMENT = /<!--[\s\S]*?-->/g;
|
|
79
|
+
/** CSS / JS 的块注释。**不摘 `//` 行注释** —— 会误杀 `https://` */
|
|
80
|
+
const BLOCK_COMMENT = /\/\*[\s\S]*?\*\//g;
|
|
81
|
+
|
|
82
|
+
/** 动态拼路径:抓到只报告不解析 */
|
|
83
|
+
const DYNAMIC_HINTS = [
|
|
84
|
+
{ re: /["'][^"']*assets\/[^"']*["']\s*\+/gi, why: '字符串拼接' },
|
|
85
|
+
{ re: /`[^`]*assets\/[^`]*\$\{[^`]*`/gi, why: '模板字符串插值' },
|
|
86
|
+
{ re: /\+\s*["'][^"']*\.(?:png|jpe?g|webp|gif|svg|mp4)["']/gi, why: '字符串拼接' },
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
/** 脚本里的素材字面量(画廊 / lightbox 的常见写法),只进清单不进包 */
|
|
90
|
+
const SCRIPT_LITERAL = /["'`]([^"'`\n]{1,200}\.(?:png|jpe?g|webp|gif|svg|mp4|webm|pdf|woff2?))["'`]/gi;
|
|
91
|
+
|
|
92
|
+
/** 去掉查询串和锚点(`x.webp?w=480` → `x.webp`)、反斜杠归一、URL 解码 */
|
|
93
|
+
function cleanRef(raw) {
|
|
94
|
+
const s = String(raw).trim().replace(/\\/g, '/').split('#')[0].split('?')[0];
|
|
95
|
+
try { return decodeURIComponent(s); } catch { return s; }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** srcset 拆成一组候选:`a.png 1x, b.png 2x` → ['a.png','b.png'] */
|
|
99
|
+
function splitSrcset(value) {
|
|
100
|
+
return value.split(',').map(part => part.trim().split(/\s+/)[0]).filter(Boolean);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 扫一批产物文件,收出它们引用到的素材。
|
|
105
|
+
*
|
|
106
|
+
* @param {object} opts
|
|
107
|
+
* @param {Array<{abs:string, rel:string}>} opts.files 要扫的文件(rel 相对 baseRoot)
|
|
108
|
+
* @param {string} opts.baseRoot 解析基准(工作区根)。返回的路径都相对它
|
|
109
|
+
* @param {string[]} [opts.allowPrefixes] 允许收的前缀白名单(树外只认这些,通常是
|
|
110
|
+
* 产物自己的根 + `assets/`)。**留空 = 不设闸**(根层产物就是这种情况,它的树
|
|
111
|
+
* 本来就是整个工作区)。被挡下的引用不静默丢,进 unresolved。
|
|
112
|
+
* @returns {Promise<{refs:string[], unresolved:Array<{from,why,snippet}>}>}
|
|
113
|
+
*/
|
|
114
|
+
export async function collectAssetRefs({ files, baseRoot, allowPrefixes = [] }) {
|
|
115
|
+
const own = new Set(files.map(f => f.rel.replace(/\\/g, '/')));
|
|
116
|
+
const refs = new Set();
|
|
117
|
+
const unresolved = [];
|
|
118
|
+
const candidates = []; // 脚本里的字面量,等调用方按"磁盘上真有"再提升
|
|
119
|
+
const seenHint = new Set(); // 同一句话被多条规则命中时去重
|
|
120
|
+
|
|
121
|
+
const pushHint = (from, why, snippet) => {
|
|
122
|
+
const s = String(snippet).replace(/\s+/g, ' ').slice(0, 120);
|
|
123
|
+
const key = `${from}|${s}`;
|
|
124
|
+
if (seenHint.has(key)) return;
|
|
125
|
+
seenHint.add(key);
|
|
126
|
+
unresolved.push({ from, why, snippet: s });
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const allowed = (rel) => {
|
|
130
|
+
if (!allowPrefixes.length) return true;
|
|
131
|
+
return allowPrefixes.some(p => (p === '' ? true : rel === p || rel.startsWith(p)));
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
for (const f of files) {
|
|
135
|
+
if (!SCANNABLE.test(f.rel)) continue;
|
|
136
|
+
let text;
|
|
137
|
+
try { text = await fs.readFile(f.abs, 'utf8'); } catch { continue; }
|
|
138
|
+
|
|
139
|
+
const fileDirAbs = path.dirname(f.abs);
|
|
140
|
+
const isMarkup = /\.html?$/i.test(f.rel);
|
|
141
|
+
const isScript = /\.(m?js|cjs|jsx)$/i.test(f.rel);
|
|
142
|
+
// 静态引用只在「非注释、非脚本体」的部分找;动态线索照旧扫全文
|
|
143
|
+
const staticText = text
|
|
144
|
+
.replace(HTML_COMMENT, ' ')
|
|
145
|
+
.replace(BLOCK_COMMENT, ' ')
|
|
146
|
+
.replace(SCRIPT_BLOCK, '$1</script>');
|
|
147
|
+
|
|
148
|
+
for (const pattern of (isMarkup ? [...MARKUP_PATTERNS, ...UNIVERSAL_PATTERNS] : UNIVERSAL_PATTERNS)) {
|
|
149
|
+
pattern.lastIndex = 0;
|
|
150
|
+
let m;
|
|
151
|
+
while ((m = pattern.exec(staticText)) !== null) {
|
|
152
|
+
const captured = capturedOf(m);
|
|
153
|
+
if (captured == null) continue;
|
|
154
|
+
const isSrcset = /srcset/i.test(pattern.source);
|
|
155
|
+
for (const c of (isSrcset ? splitSrcset(captured) : [captured])) {
|
|
156
|
+
if (!c || EXTERNAL.test(c.trim())) continue;
|
|
157
|
+
const cleaned = cleanRef(c);
|
|
158
|
+
if (!cleaned || cleaned.startsWith('/')) continue; // 根路径引用平台本来就禁
|
|
159
|
+
const abs = path.resolve(fileDirAbs, cleaned);
|
|
160
|
+
const rel = path.relative(baseRoot, abs).replace(/\\/g, '/');
|
|
161
|
+
if (!rel || rel.startsWith('..')) continue; // 逃出工作区的不收
|
|
162
|
+
if (own.has(rel)) continue; // 产物自己的页面不算素材
|
|
163
|
+
if (!allowed(rel)) {
|
|
164
|
+
pushHint(f.rel, '引用落在产物树外(只有 assets/ 和产物自己的目录会进包)', cleaned);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
refs.add(rel);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// `<base href>` 会改写整页相对路径的解析基准,扫描器按文件目录解析就会整体偏移
|
|
173
|
+
if (/<base\b[^>]*\bhref/i.test(text)) {
|
|
174
|
+
pushHint(f.rel, '页面有 <base href>,本页所有相对引用的解析基准可能跟打包时算的不一样', '<base href=…>');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const { re, why } of DYNAMIC_HINTS) {
|
|
178
|
+
re.lastIndex = 0;
|
|
179
|
+
let m;
|
|
180
|
+
while ((m = re.exec(text)) !== null) pushHint(f.rel, why, m[0]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 脚本里的素材字面量(画廊 / lightbox 的常见写法)。
|
|
184
|
+
// **完整字面量是静态可判的**,所以不直接判死:能落进工作区、又过得了树外闸的
|
|
185
|
+
// 交给调用方按「磁盘上真有这个文件」提升成素材;剩下的才进清单。
|
|
186
|
+
// 这不违反「扫不到的不多塞」—— 它扫得到,只是来源是脚本而不是标签。
|
|
187
|
+
const scanLiterals = (body) => {
|
|
188
|
+
SCRIPT_LITERAL.lastIndex = 0;
|
|
189
|
+
let lit;
|
|
190
|
+
while ((lit = SCRIPT_LITERAL.exec(body)) !== null) {
|
|
191
|
+
const cleaned = cleanRef(lit[1]);
|
|
192
|
+
const snippet = lit[0].slice(0, 120);
|
|
193
|
+
if (!cleaned || EXTERNAL.test(cleaned) || cleaned.startsWith('/')) {
|
|
194
|
+
pushHint(f.rel, '脚本里的字符串字面量(打包时不解析)', snippet); continue;
|
|
195
|
+
}
|
|
196
|
+
const rel = path.relative(baseRoot, path.resolve(fileDirAbs, cleaned)).replace(/\\/g, '/');
|
|
197
|
+
if (!rel || rel.startsWith('..') || own.has(rel) || !allowed(rel)) {
|
|
198
|
+
pushHint(f.rel, '脚本里的字符串字面量(打包时不解析)', snippet); continue;
|
|
199
|
+
}
|
|
200
|
+
candidates.push({ rel, from: f.rel, snippet });
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
// ⚠️ 从**剥过注释**的文本里取脚本体:标签侧早就剥了注释,字面量侧漏剥的话,
|
|
204
|
+
// 注释里的旧图会被提升进包 —— 同一条规矩两个半边,只修一半等于没修。
|
|
205
|
+
const commentFree = text.replace(HTML_COMMENT, ' ');
|
|
206
|
+
for (const sm of commentFree.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi)) scanLiterals(sm[1]);
|
|
207
|
+
// ⚠️ 独立的 .js 文件**整个就是脚本体**,没有 <script> 标签可匹配。
|
|
208
|
+
// 不加这条的话,同一份画廊代码写在 HTML 里能收、抽成 js 文件就双失踪。
|
|
209
|
+
if (isScript) scanLiterals(text.replace(BLOCK_COMMENT, ' '));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { refs: [...refs].sort(), candidates, unresolved };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 把 unresolved 渲染成放进包里的清单。没有动态引用时返回 null(不放空文件 ——
|
|
217
|
+
* 旧交付包里那个常年空着的 prompt.txt 就是这么来的)。
|
|
218
|
+
*/
|
|
219
|
+
export function renderUnresolvedReport(unresolved) {
|
|
220
|
+
if (!unresolved?.length) return null;
|
|
221
|
+
const lines = [
|
|
222
|
+
'# 没能解析的素材引用',
|
|
223
|
+
'',
|
|
224
|
+
'下面这些地方打包时算不出具体引用了哪个文件,**素材可能没进包**。',
|
|
225
|
+
'如果打开后有图裂了,照这个清单去原工程里找对应文件补进来。',
|
|
226
|
+
'',
|
|
227
|
+
];
|
|
228
|
+
const byFile = new Map();
|
|
229
|
+
for (const u of unresolved) {
|
|
230
|
+
if (!byFile.has(u.from)) byFile.set(u.from, []);
|
|
231
|
+
byFile.get(u.from).push(u);
|
|
232
|
+
}
|
|
233
|
+
for (const [file, items] of [...byFile.entries()].sort()) {
|
|
234
|
+
lines.push(`## ${file}`, '');
|
|
235
|
+
for (const it of items) lines.push(`- (${it.why})\`${it.snippet}\``);
|
|
236
|
+
lines.push('');
|
|
237
|
+
}
|
|
238
|
+
return lines.join('\n');
|
|
239
|
+
}
|