@alpsckr/unitycli 0.4.0
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 +389 -0
- package/contracts/command-contract.json +3972 -0
- package/contracts/stable-session/admission-acquire-v1.schema.json +16 -0
- package/contracts/stable-session/admission-response-v1.schema.json +30 -0
- package/contracts/stable-session/admission-revoke-v1.schema.json +15 -0
- package/contracts/stable-session/admission-transport-v1.json +36 -0
- package/contracts/stable-session/bridge-request-v2.schema.json +26 -0
- package/contracts/stable-session/canonical-path-v1-golden.json +23 -0
- package/contracts/stable-session/completion-contract.schema.json +46 -0
- package/contracts/stable-session/direct-call-semantics.json +102 -0
- package/contracts/stable-session/editor-state.schema.json +92 -0
- package/contracts/stable-session/health-v3.schema.json +47 -0
- package/contracts/stable-session/instance-registry-v3.schema.json +152 -0
- package/contracts/stable-session/removal-map.json +27 -0
- package/contracts/stable-session/request-digest-v1-golden.json +7 -0
- package/contracts/stable-session/runtime-config-v2.schema.json +27 -0
- package/contracts/static-capability-catalog.json +48226 -0
- package/contracts/tool-completion-contract.json +11322 -0
- package/dist/args.d.ts +27 -0
- package/dist/args.js +109 -0
- package/dist/args.js.map +1 -0
- package/dist/bridge/admission.d.ts +28 -0
- package/dist/bridge/admission.js +29 -0
- package/dist/bridge/admission.js.map +1 -0
- package/dist/bridge/args.d.ts +16 -0
- package/dist/bridge/args.js +123 -0
- package/dist/bridge/args.js.map +1 -0
- package/dist/bridge/domains/animation.d.ts +2 -0
- package/dist/bridge/domains/animation.js +199 -0
- package/dist/bridge/domains/animation.js.map +1 -0
- package/dist/bridge/domains/asset.d.ts +2 -0
- package/dist/bridge/domains/asset.js +85 -0
- package/dist/bridge/domains/asset.js.map +1 -0
- package/dist/bridge/domains/build.d.ts +2 -0
- package/dist/bridge/domains/build.js +35 -0
- package/dist/bridge/domains/build.js.map +1 -0
- package/dist/bridge/domains/camera.d.ts +2 -0
- package/dist/bridge/domains/camera.js +54 -0
- package/dist/bridge/domains/camera.js.map +1 -0
- package/dist/bridge/domains/code.d.ts +2 -0
- package/dist/bridge/domains/code.js +17 -0
- package/dist/bridge/domains/code.js.map +1 -0
- package/dist/bridge/domains/console.d.ts +2 -0
- package/dist/bridge/domains/console.js +34 -0
- package/dist/bridge/domains/console.js.map +1 -0
- package/dist/bridge/domains/diagnose.d.ts +2 -0
- package/dist/bridge/domains/diagnose.js +11 -0
- package/dist/bridge/domains/diagnose.js.map +1 -0
- package/dist/bridge/domains/editor.d.ts +2 -0
- package/dist/bridge/domains/editor.js +66 -0
- package/dist/bridge/domains/editor.js.map +1 -0
- package/dist/bridge/domains/extension.d.ts +8 -0
- package/dist/bridge/domains/extension.js +20 -0
- package/dist/bridge/domains/extension.js.map +1 -0
- package/dist/bridge/domains/game-object.d.ts +2 -0
- package/dist/bridge/domains/game-object.js +179 -0
- package/dist/bridge/domains/game-object.js.map +1 -0
- package/dist/bridge/domains/graphics.d.ts +2 -0
- package/dist/bridge/domains/graphics.js +185 -0
- package/dist/bridge/domains/graphics.js.map +1 -0
- package/dist/bridge/domains/material.d.ts +2 -0
- package/dist/bridge/domains/material.js +70 -0
- package/dist/bridge/domains/material.js.map +1 -0
- package/dist/bridge/domains/navmesh.d.ts +2 -0
- package/dist/bridge/domains/navmesh.js +34 -0
- package/dist/bridge/domains/navmesh.js.map +1 -0
- package/dist/bridge/domains/object.d.ts +2 -0
- package/dist/bridge/domains/object.js +43 -0
- package/dist/bridge/domains/object.js.map +1 -0
- package/dist/bridge/domains/occlusion.d.ts +2 -0
- package/dist/bridge/domains/occlusion.js +12 -0
- package/dist/bridge/domains/occlusion.js.map +1 -0
- package/dist/bridge/domains/package.d.ts +2 -0
- package/dist/bridge/domains/package.js +2 -0
- package/dist/bridge/domains/package.js.map +1 -0
- package/dist/bridge/domains/physics.d.ts +2 -0
- package/dist/bridge/domains/physics.js +139 -0
- package/dist/bridge/domains/physics.js.map +1 -0
- package/dist/bridge/domains/prefab.d.ts +2 -0
- package/dist/bridge/domains/prefab.js +104 -0
- package/dist/bridge/domains/prefab.js.map +1 -0
- package/dist/bridge/domains/profiler.d.ts +2 -0
- package/dist/bridge/domains/profiler.js +105 -0
- package/dist/bridge/domains/profiler.js.map +1 -0
- package/dist/bridge/domains/project-tool.d.ts +2 -0
- package/dist/bridge/domains/project-tool.js +46 -0
- package/dist/bridge/domains/project-tool.js.map +1 -0
- package/dist/bridge/domains/reflection.d.ts +2 -0
- package/dist/bridge/domains/reflection.js +48 -0
- package/dist/bridge/domains/reflection.js.map +1 -0
- package/dist/bridge/domains/runtime-tool.d.ts +2 -0
- package/dist/bridge/domains/runtime-tool.js +48 -0
- package/dist/bridge/domains/runtime-tool.js.map +1 -0
- package/dist/bridge/domains/scene.d.ts +2 -0
- package/dist/bridge/domains/scene.js +100 -0
- package/dist/bridge/domains/scene.js.map +1 -0
- package/dist/bridge/domains/screenshot.d.ts +2 -0
- package/dist/bridge/domains/screenshot.js +85 -0
- package/dist/bridge/domains/screenshot.js.map +1 -0
- package/dist/bridge/domains/scriptable-object.d.ts +2 -0
- package/dist/bridge/domains/scriptable-object.js +53 -0
- package/dist/bridge/domains/scriptable-object.js.map +1 -0
- package/dist/bridge/domains/shader.d.ts +2 -0
- package/dist/bridge/domains/shader.js +66 -0
- package/dist/bridge/domains/shader.js.map +1 -0
- package/dist/bridge/domains/texture.d.ts +2 -0
- package/dist/bridge/domains/texture.js +78 -0
- package/dist/bridge/domains/texture.js.map +1 -0
- package/dist/bridge/domains/timeline.d.ts +2 -0
- package/dist/bridge/domains/timeline.js +75 -0
- package/dist/bridge/domains/timeline.js.map +1 -0
- package/dist/bridge/domains/ui.d.ts +2 -0
- package/dist/bridge/domains/ui.js +193 -0
- package/dist/bridge/domains/ui.js.map +1 -0
- package/dist/bridge/index.d.ts +9 -0
- package/dist/bridge/index.js +124 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/registry.d.ts +19 -0
- package/dist/bridge/registry.js +114 -0
- package/dist/bridge/registry.js.map +1 -0
- package/dist/bridge/session-supervisor.d.ts +138 -0
- package/dist/bridge/session-supervisor.js +873 -0
- package/dist/bridge/session-supervisor.js.map +1 -0
- package/dist/bridge/types.d.ts +61 -0
- package/dist/bridge/types.js +17 -0
- package/dist/bridge/types.js.map +1 -0
- package/dist/capabilities.d.ts +5 -0
- package/dist/capabilities.js +84 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/cli-commands/command.d.ts +7 -0
- package/dist/cli-commands/command.js +513 -0
- package/dist/cli-commands/command.js.map +1 -0
- package/dist/cli-commands/extension-auto-detect.d.ts +62 -0
- package/dist/cli-commands/extension-auto-detect.js +119 -0
- package/dist/cli-commands/extension-auto-detect.js.map +1 -0
- package/dist/cli-commands/extensions.d.ts +24 -0
- package/dist/cli-commands/extensions.js +428 -0
- package/dist/cli-commands/extensions.js.map +1 -0
- package/dist/cli-commands/package.d.ts +4 -0
- package/dist/cli-commands/package.js +151 -0
- package/dist/cli-commands/package.js.map +1 -0
- package/dist/cli-commands/skill-installer.d.ts +83 -0
- package/dist/cli-commands/skill-installer.js +703 -0
- package/dist/cli-commands/skill-installer.js.map +1 -0
- package/dist/cli-commands/types.d.ts +17 -0
- package/dist/cli-commands/types.js +2 -0
- package/dist/cli-commands/types.js.map +1 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +302 -0
- package/dist/cli.js.map +1 -0
- package/dist/contracts/command-contract.d.ts +21 -0
- package/dist/contracts/command-contract.js +143 -0
- package/dist/contracts/command-contract.js.map +1 -0
- package/dist/contracts/csharp-command-extractor.d.ts +1 -0
- package/dist/contracts/csharp-command-extractor.js +89 -0
- package/dist/contracts/csharp-command-extractor.js.map +1 -0
- package/dist/contracts/validate-command-contract.d.ts +1 -0
- package/dist/contracts/validate-command-contract.js +86 -0
- package/dist/contracts/validate-command-contract.js.map +1 -0
- package/dist/doctor.d.ts +22 -0
- package/dist/doctor.js +498 -0
- package/dist/doctor.js.map +1 -0
- package/dist/editor-blocker-observer.d.ts +45 -0
- package/dist/editor-blocker-observer.js +55 -0
- package/dist/editor-blocker-observer.js.map +1 -0
- package/dist/editor-dialogs.d.ts +53 -0
- package/dist/editor-dialogs.js +79 -0
- package/dist/editor-dialogs.js.map +1 -0
- package/dist/extension-availability.d.ts +30 -0
- package/dist/extension-availability.js +83 -0
- package/dist/extension-availability.js.map +1 -0
- package/dist/extensions/.generated-bridge-domains/cinemachine.d.ts +2 -0
- package/dist/extensions/.generated-bridge-domains/cinemachine.js +104 -0
- package/dist/extensions/.generated-bridge-domains/cinemachine.js.map +1 -0
- package/dist/extensions/.generated-bridge-domains/hdrp.d.ts +2 -0
- package/dist/extensions/.generated-bridge-domains/hdrp.js +35 -0
- package/dist/extensions/.generated-bridge-domains/hdrp.js.map +1 -0
- package/dist/extensions/.generated-bridge-domains/probuilder.d.ts +13 -0
- package/dist/extensions/.generated-bridge-domains/probuilder.js +342 -0
- package/dist/extensions/.generated-bridge-domains/probuilder.js.map +1 -0
- package/dist/extensions/.generated-bridge-domains/urp.d.ts +2 -0
- package/dist/extensions/.generated-bridge-domains/urp.js +52 -0
- package/dist/extensions/.generated-bridge-domains/urp.js.map +1 -0
- package/dist/extensions/.generated-bridge-domains/vfx.d.ts +2 -0
- package/dist/extensions/.generated-bridge-domains/vfx.js +312 -0
- package/dist/extensions/.generated-bridge-domains/vfx.js.map +1 -0
- package/dist/extensions/.generated-registry-data.d.ts +7 -0
- package/dist/extensions/.generated-registry-data.js +895 -0
- package/dist/extensions/.generated-registry-data.js.map +1 -0
- package/dist/extensions/contribution-loader.d.ts +27 -0
- package/dist/extensions/contribution-loader.js +60 -0
- package/dist/extensions/contribution-loader.js.map +1 -0
- package/dist/extensions/descriptor-loader.d.ts +73 -0
- package/dist/extensions/descriptor-loader.js +126 -0
- package/dist/extensions/descriptor-loader.js.map +1 -0
- package/dist/extensions/index.d.ts +19 -0
- package/dist/extensions/index.js +23 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/installer.d.ts +80 -0
- package/dist/extensions/installer.js +594 -0
- package/dist/extensions/installer.js.map +1 -0
- package/dist/extensions/metadata/index.d.ts +15 -0
- package/dist/extensions/metadata/index.js +41 -0
- package/dist/extensions/metadata/index.js.map +1 -0
- package/dist/extensions/metadata/types.d.ts +38 -0
- package/dist/extensions/metadata/types.js +11 -0
- package/dist/extensions/metadata/types.js.map +1 -0
- package/dist/extensions/project-resolver.d.ts +35 -0
- package/dist/extensions/project-resolver.js +61 -0
- package/dist/extensions/project-resolver.js.map +1 -0
- package/dist/extensions/registry.d.ts +86 -0
- package/dist/extensions/registry.js +262 -0
- package/dist/extensions/registry.js.map +1 -0
- package/dist/extensions/tool-manifest.d.ts +29 -0
- package/dist/extensions/tool-manifest.js +53 -0
- package/dist/extensions/tool-manifest.js.map +1 -0
- package/dist/extensions/types.d.ts +115 -0
- package/dist/extensions/types.js +12 -0
- package/dist/extensions/types.js.map +1 -0
- package/dist/instances.d.ts +163 -0
- package/dist/instances.js +1137 -0
- package/dist/instances.js.map +1 -0
- package/dist/output.d.ts +119 -0
- package/dist/output.js +204 -0
- package/dist/output.js.map +1 -0
- package/dist/package-manager.d.ts +280 -0
- package/dist/package-manager.js +1832 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/platform.d.ts +3 -0
- package/dist/platform.js +19 -0
- package/dist/platform.js.map +1 -0
- package/dist/project-identity.d.ts +48 -0
- package/dist/project-identity.js +254 -0
- package/dist/project-identity.js.map +1 -0
- package/dist/project-tool-discovery.d.ts +23 -0
- package/dist/project-tool-discovery.js +292 -0
- package/dist/project-tool-discovery.js.map +1 -0
- package/dist/protocol/envelope.d.ts +64 -0
- package/dist/protocol/envelope.js +8 -0
- package/dist/protocol/envelope.js.map +1 -0
- package/dist/protocol/index.d.ts +7 -0
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/runtime-code.d.ts +20 -0
- package/dist/runtime-code.js +232 -0
- package/dist/runtime-code.js.map +1 -0
- package/dist/schema/any-json.d.ts +4 -0
- package/dist/schema/any-json.js +2 -0
- package/dist/schema/any-json.js.map +1 -0
- package/dist/schema/validator.d.ts +7 -0
- package/dist/schema/validator.js +135 -0
- package/dist/schema/validator.js.map +1 -0
- package/dist/services/docs-reflect-cache.d.ts +106 -0
- package/dist/services/docs-reflect-cache.js +301 -0
- package/dist/services/docs-reflect-cache.js.map +1 -0
- package/dist/services/docs-service.d.ts +16 -0
- package/dist/services/docs-service.js +208 -0
- package/dist/services/docs-service.js.map +1 -0
- package/dist/services/reflect-service.d.ts +12 -0
- package/dist/services/reflect-service.js +122 -0
- package/dist/services/reflect-service.js.map +1 -0
- package/dist/static-capability-catalog.d.ts +13 -0
- package/dist/static-capability-catalog.js +41 -0
- package/dist/static-capability-catalog.js.map +1 -0
- package/dist/test-results.d.ts +13 -0
- package/dist/test-results.js +44 -0
- package/dist/test-results.js.map +1 -0
- package/dist/tools/.generated-completion-registry.d.ts +17 -0
- package/dist/tools/.generated-completion-registry.js +5862 -0
- package/dist/tools/.generated-completion-registry.js.map +1 -0
- package/dist/tools/.generated-registry.d.ts +53 -0
- package/dist/tools/.generated-registry.js +55 -0
- package/dist/tools/.generated-registry.js.map +1 -0
- package/dist/tools/.generated-schemas.d.ts +1 -0
- package/dist/tools/.generated-schemas.js +56 -0
- package/dist/tools/.generated-schemas.js.map +1 -0
- package/dist/tools/asset/refresh.d.ts +26 -0
- package/dist/tools/asset/refresh.js +97 -0
- package/dist/tools/asset/refresh.js.map +1 -0
- package/dist/tools/build/run.d.ts +45 -0
- package/dist/tools/build/run.js +115 -0
- package/dist/tools/build/run.js.map +1 -0
- package/dist/tools/catalog.d.ts +33 -0
- package/dist/tools/catalog.js +202 -0
- package/dist/tools/catalog.js.map +1 -0
- package/dist/tools/code/exec.d.ts +53 -0
- package/dist/tools/code/exec.js +59 -0
- package/dist/tools/code/exec.js.map +1 -0
- package/dist/tools/commands.d.ts +5 -0
- package/dist/tools/commands.js +45 -0
- package/dist/tools/commands.js.map +1 -0
- package/dist/tools/completion-probe.d.ts +22 -0
- package/dist/tools/completion-probe.js +2 -0
- package/dist/tools/completion-probe.js.map +1 -0
- package/dist/tools/define.d.ts +77 -0
- package/dist/tools/define.js +58 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/diagnose/capabilities.d.ts +13 -0
- package/dist/tools/diagnose/capabilities.js +34 -0
- package/dist/tools/diagnose/capabilities.js.map +1 -0
- package/dist/tools/dispatcher.d.ts +27 -0
- package/dist/tools/dispatcher.js +971 -0
- package/dist/tools/dispatcher.js.map +1 -0
- package/dist/tools/docs/get.d.ts +22 -0
- package/dist/tools/docs/get.js +22 -0
- package/dist/tools/docs/get.js.map +1 -0
- package/dist/tools/docs/search.d.ts +31 -0
- package/dist/tools/docs/search.js +22 -0
- package/dist/tools/docs/search.js.map +1 -0
- package/dist/tools/editor/compile.d.ts +66 -0
- package/dist/tools/editor/compile.js +225 -0
- package/dist/tools/editor/compile.js.map +1 -0
- package/dist/tools/editor/dialog/click.d.ts +12 -0
- package/dist/tools/editor/dialog/click.js +71 -0
- package/dist/tools/editor/dialog/click.js.map +1 -0
- package/dist/tools/editor/dialogs.d.ts +12 -0
- package/dist/tools/editor/dialogs.js +58 -0
- package/dist/tools/editor/dialogs.js.map +1 -0
- package/dist/tools/editor/readiness.d.ts +13 -0
- package/dist/tools/editor/readiness.js +24 -0
- package/dist/tools/editor/readiness.js.map +1 -0
- package/dist/tools/editor/refresh.d.ts +25 -0
- package/dist/tools/editor/refresh.js +154 -0
- package/dist/tools/editor/refresh.js.map +1 -0
- package/dist/tools/editor/state.d.ts +63 -0
- package/dist/tools/editor/state.js +286 -0
- package/dist/tools/editor/state.js.map +1 -0
- package/dist/tools/errors.d.ts +30 -0
- package/dist/tools/errors.js +46 -0
- package/dist/tools/errors.js.map +1 -0
- package/dist/tools/metadata/derived.d.ts +34 -0
- package/dist/tools/metadata/derived.js +136 -0
- package/dist/tools/metadata/derived.js.map +1 -0
- package/dist/tools/metadata/index.d.ts +34 -0
- package/dist/tools/metadata/index.js +130 -0
- package/dist/tools/metadata/index.js.map +1 -0
- package/dist/tools/metadata/schemas.d.ts +3 -0
- package/dist/tools/metadata/schemas.js +19 -0
- package/dist/tools/metadata/schemas.js.map +1 -0
- package/dist/tools/metadata/status.d.ts +4 -0
- package/dist/tools/metadata/status.js +61 -0
- package/dist/tools/metadata/status.js.map +1 -0
- package/dist/tools/metadata/types.d.ts +79 -0
- package/dist/tools/metadata/types.js +2 -0
- package/dist/tools/metadata/types.js.map +1 -0
- package/dist/tools/navmesh/bake-cancel.d.ts +7 -0
- package/dist/tools/navmesh/bake-cancel.js +21 -0
- package/dist/tools/navmesh/bake-cancel.js.map +1 -0
- package/dist/tools/navmesh/bake-status.d.ts +5 -0
- package/dist/tools/navmesh/bake-status.js +18 -0
- package/dist/tools/navmesh/bake-status.js.map +1 -0
- package/dist/tools/navmesh/bake.d.ts +5 -0
- package/dist/tools/navmesh/bake.js +23 -0
- package/dist/tools/navmesh/bake.js.map +1 -0
- package/dist/tools/navmesh/live.d.ts +86 -0
- package/dist/tools/navmesh/live.js +206 -0
- package/dist/tools/navmesh/live.js.map +1 -0
- package/dist/tools/navmesh/surface-bake.d.ts +5 -0
- package/dist/tools/navmesh/surface-bake.js +24 -0
- package/dist/tools/navmesh/surface-bake.js.map +1 -0
- package/dist/tools/occlusion/bake-cancel.d.ts +7 -0
- package/dist/tools/occlusion/bake-cancel.js +21 -0
- package/dist/tools/occlusion/bake-cancel.js.map +1 -0
- package/dist/tools/occlusion/bake-status.d.ts +5 -0
- package/dist/tools/occlusion/bake-status.js +18 -0
- package/dist/tools/occlusion/bake-status.js.map +1 -0
- package/dist/tools/occlusion/bake.d.ts +11 -0
- package/dist/tools/occlusion/bake.js +23 -0
- package/dist/tools/occlusion/bake.js.map +1 -0
- package/dist/tools/occlusion/live.d.ts +74 -0
- package/dist/tools/occlusion/live.js +201 -0
- package/dist/tools/occlusion/live.js.map +1 -0
- package/dist/tools/package/bridge-remove.d.ts +26 -0
- package/dist/tools/package/bridge-remove.js +32 -0
- package/dist/tools/package/bridge-remove.js.map +1 -0
- package/dist/tools/package/deploy.d.ts +49 -0
- package/dist/tools/package/deploy.js +134 -0
- package/dist/tools/package/deploy.js.map +1 -0
- package/dist/tools/package/embed.d.ts +14 -0
- package/dist/tools/package/embed.js +93 -0
- package/dist/tools/package/embed.js.map +1 -0
- package/dist/tools/package/file-mutation-worker.d.ts +6 -0
- package/dist/tools/package/file-mutation-worker.js +154 -0
- package/dist/tools/package/file-mutation-worker.js.map +1 -0
- package/dist/tools/package/info.d.ts +19 -0
- package/dist/tools/package/info.js +27 -0
- package/dist/tools/package/info.js.map +1 -0
- package/dist/tools/package/install.d.ts +24 -0
- package/dist/tools/package/install.js +65 -0
- package/dist/tools/package/install.js.map +1 -0
- package/dist/tools/package/list.d.ts +27 -0
- package/dist/tools/package/list.js +21 -0
- package/dist/tools/package/list.js.map +1 -0
- package/dist/tools/package/live-resolve.d.ts +46 -0
- package/dist/tools/package/live-resolve.js +497 -0
- package/dist/tools/package/live-resolve.js.map +1 -0
- package/dist/tools/package/public-result.d.ts +4 -0
- package/dist/tools/package/public-result.js +25 -0
- package/dist/tools/package/public-result.js.map +1 -0
- package/dist/tools/package/registry-clear.d.ts +15 -0
- package/dist/tools/package/registry-clear.js +27 -0
- package/dist/tools/package/registry-clear.js.map +1 -0
- package/dist/tools/package/registry-get.d.ts +10 -0
- package/dist/tools/package/registry-get.js +22 -0
- package/dist/tools/package/registry-get.js.map +1 -0
- package/dist/tools/package/registry-set.d.ts +17 -0
- package/dist/tools/package/registry-set.js +28 -0
- package/dist/tools/package/registry-set.js.map +1 -0
- package/dist/tools/package/remove.d.ts +47 -0
- package/dist/tools/package/remove.js +67 -0
- package/dist/tools/package/remove.js.map +1 -0
- package/dist/tools/package/resolve.d.ts +6 -0
- package/dist/tools/package/resolve.js +14 -0
- package/dist/tools/package/resolve.js.map +1 -0
- package/dist/tools/package/restore.d.ts +42 -0
- package/dist/tools/package/restore.js +122 -0
- package/dist/tools/package/restore.js.map +1 -0
- package/dist/tools/package/scoped-registry-add.d.ts +25 -0
- package/dist/tools/package/scoped-registry-add.js +30 -0
- package/dist/tools/package/scoped-registry-add.js.map +1 -0
- package/dist/tools/package/scoped-registry-list.d.ts +14 -0
- package/dist/tools/package/scoped-registry-list.js +22 -0
- package/dist/tools/package/scoped-registry-list.js.map +1 -0
- package/dist/tools/package/scoped-registry-remove.d.ts +29 -0
- package/dist/tools/package/scoped-registry-remove.js +31 -0
- package/dist/tools/package/scoped-registry-remove.js.map +1 -0
- package/dist/tools/package/search.d.ts +17 -0
- package/dist/tools/package/search.js +39 -0
- package/dist/tools/package/search.js.map +1 -0
- package/dist/tools/package/status.d.ts +21 -0
- package/dist/tools/package/status.js +37 -0
- package/dist/tools/package/status.js.map +1 -0
- package/dist/tools/reflect/member.d.ts +22 -0
- package/dist/tools/reflect/member.js +22 -0
- package/dist/tools/reflect/member.js.map +1 -0
- package/dist/tools/reflect/search.d.ts +30 -0
- package/dist/tools/reflect/search.js +22 -0
- package/dist/tools/reflect/search.js.map +1 -0
- package/dist/tools/reflect/type.d.ts +21 -0
- package/dist/tools/reflect/type.js +22 -0
- package/dist/tools/reflect/type.js.map +1 -0
- package/dist/tools/runtime/console.d.ts +24 -0
- package/dist/tools/runtime/console.js +17 -0
- package/dist/tools/runtime/console.js.map +1 -0
- package/dist/tools/runtime/input/key.d.ts +23 -0
- package/dist/tools/runtime/input/key.js +18 -0
- package/dist/tools/runtime/input/key.js.map +1 -0
- package/dist/tools/runtime/input/pointer.d.ts +34 -0
- package/dist/tools/runtime/input/pointer.js +18 -0
- package/dist/tools/runtime/input/pointer.js.map +1 -0
- package/dist/tools/runtime/log.d.ts +20 -0
- package/dist/tools/runtime/log.js +17 -0
- package/dist/tools/runtime/log.js.map +1 -0
- package/dist/tools/runtime/state.d.ts +45 -0
- package/dist/tools/runtime/state.js +17 -0
- package/dist/tools/runtime/state.js.map +1 -0
- package/dist/tools/runtime/target-framerate/set.d.ts +16 -0
- package/dist/tools/runtime/target-framerate/set.js +17 -0
- package/dist/tools/runtime/target-framerate/set.js.map +1 -0
- package/dist/tools/runtime/time-scale/set.d.ts +16 -0
- package/dist/tools/runtime/time-scale/set.js +17 -0
- package/dist/tools/runtime/time-scale/set.js.map +1 -0
- package/dist/tools/runtime/ui/capture-element.d.ts +31 -0
- package/dist/tools/runtime/ui/capture-element.js +17 -0
- package/dist/tools/runtime/ui/capture-element.js.map +1 -0
- package/dist/tools/test/cancel.d.ts +19 -0
- package/dist/tools/test/cancel.js +94 -0
- package/dist/tools/test/cancel.js.map +1 -0
- package/dist/tools/test/run.d.ts +39 -0
- package/dist/tools/test/run.js +101 -0
- package/dist/tools/test/run.js.map +1 -0
- package/dist/tools/tools/describe.d.ts +27 -0
- package/dist/tools/tools/describe.js +35 -0
- package/dist/tools/tools/describe.js.map +1 -0
- package/dist/tools/tools/group-list.d.ts +20 -0
- package/dist/tools/tools/group-list.js +39 -0
- package/dist/tools/tools/group-list.js.map +1 -0
- package/dist/tools/tools/group-summaries.d.ts +3 -0
- package/dist/tools/tools/group-summaries.js +52 -0
- package/dist/tools/tools/group-summaries.js.map +1 -0
- package/dist/tools/tools/group.d.ts +13 -0
- package/dist/tools/tools/group.js +34 -0
- package/dist/tools/tools/group.js.map +1 -0
- package/dist/tools/tools/search.d.ts +17 -0
- package/dist/tools/tools/search.js +42 -0
- package/dist/tools/tools/search.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/unity-project-processes.d.ts +12 -0
- package/dist/unity-project-processes.js +59 -0
- package/dist/unity-project-processes.js.map +1 -0
- package/dist/util.d.ts +11 -0
- package/dist/util.js +22 -0
- package/dist/util.js.map +1 -0
- package/dist/windows-editor-state.d.ts +31 -0
- package/dist/windows-editor-state.js +98 -0
- package/dist/windows-editor-state.js.map +1 -0
- package/docs/README.md +43 -0
- package/docs/availability-differences.md +97 -0
- package/docs/command-execution.md +89 -0
- package/docs/error-codes.md +204 -0
- package/docs/extensions.md +132 -0
- package/docs/risk-and-side-effects.md +93 -0
- package/docs/safety-boundaries.md +103 -0
- package/docs/target-and-instance.md +83 -0
- package/docs/tool-discovery.md +104 -0
- package/docs/tool-execution.md +178 -0
- package/extensions/cinemachine/bridge/domains/cinemachine.ts +115 -0
- package/extensions/cinemachine/docs/cinemachine.md +50 -0
- package/extensions/cinemachine/extension.json +129 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/CinemachineExtensionToolSupport.cs +15 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/CinemachineToolSupport.cs +854 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/aim.cs +96 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/blend.cs +140 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/body.cs +96 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/brain-ensure.cs +16 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/brain-status.cs +12 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/create.cs +147 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/extension-add.cs +13 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/extension-list.cs +13 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/extension-remove.cs +13 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/lens.cs +142 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/noise.cs +126 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/ping.cs +43 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/priority.cs +113 -0
- package/extensions/cinemachine/unitypkg/Editor/Cinemachine/target.cs +84 -0
- package/extensions/cinemachine/unitypkg/UnityCli.Extensions.Cinemachine.Editor.asmdef +18 -0
- package/extensions/hdrp/bridge/domains/hdrp.ts +41 -0
- package/extensions/hdrp/docs/hdrp.md +15 -0
- package/extensions/hdrp/extension.json +31 -0
- package/extensions/hdrp/unitypkg/Editor/Hdrp/HdrpToolSupport.cs +607 -0
- package/extensions/hdrp/unitypkg/Editor/Hdrp/settings-get.cs +29 -0
- package/extensions/hdrp/unitypkg/Editor/Hdrp/settings-set.cs +47 -0
- package/extensions/hdrp/unitypkg/Editor/Hdrp/volume-add-effect.cs +28 -0
- package/extensions/hdrp/unitypkg/Editor/Hdrp/volume-create.cs +28 -0
- package/extensions/hdrp/unitypkg/Editor/Hdrp/volume-info.cs +25 -0
- package/extensions/hdrp/unitypkg/Editor/Hdrp/volume-list.cs +25 -0
- package/extensions/hdrp/unitypkg/Editor/Hdrp/volume-set.cs +28 -0
- package/extensions/hdrp/unitypkg/UnityCli.Extensions.Hdrp.Editor.asmdef +19 -0
- package/extensions/probuilder/bridge/domains/probuilder.ts +386 -0
- package/extensions/probuilder/docs/probuilder.md +125 -0
- package/extensions/probuilder/extension.json +291 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderAppendCompletionProof.cs +254 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderAtomicReadback.cs +293 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderBevelCompletionProof.cs +299 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderCanonicalMeshSnapshot.cs +951 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderConnectCompletionProof.cs +407 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderEdgeCompletionProof.cs +318 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderErrors.cs +83 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderFaceCompletionProof.cs +152 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderFaceExtrudeCompletionProof.cs +283 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderFlipCompletionProof.cs +170 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderInput.cs +85 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderInsertCompletionProof.cs +674 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderOperationProof.cs +137 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderReflection.cs +1064 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderRefreshUvProof.cs +53 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderSceneTool.cs +184 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderShapeTopologyRecords.g.cs +82 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ProBuilderVertexObjectCompletionProof.cs +346 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/append-vertices-to-edge.cs +118 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/auto-smooth.cs +104 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/bevel-edges.cs +119 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/bridge-edges.cs +113 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/center-pivot.cs +43 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/combine-meshes.cs +58 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/connect-elements.cs +128 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/convert-to-probuilder.cs +40 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/create-poly-shape.cs +55 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/create-polygon.cs +28 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/create-shape.cs +168 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/delete-faces.cs +42 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/detach-faces.cs +46 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/duplicate-and-flip.cs +100 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/extrude-edges.cs +118 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/extrude-faces.cs +110 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/flip-normals.cs +102 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/freeze-transform.cs +45 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/insert-vertex.cs +132 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/merge-faces.cs +44 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/merge-objects.cs +130 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/merge-vertices.cs +109 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/mesh-info.cs +117 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/move-vertices.cs +33 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/ping.cs +51 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/repair-mesh.cs +40 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/select-faces.cs +161 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/set-face-color.cs +24 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/set-face-material.cs +23 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/set-face-uvs.cs +28 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/set-pivot.cs +42 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/set-smoothing.cs +23 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/split-vertices.cs +64 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/subdivide.cs +111 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/validate-mesh.cs +75 -0
- package/extensions/probuilder/unitypkg/Editor/ProBuilder/weld-vertices.cs +112 -0
- package/extensions/probuilder/unitypkg/UnityCli.Extensions.ProBuilder.Editor.asmdef +25 -0
- package/extensions/urp/bridge/domains/urp.ts +62 -0
- package/extensions/urp/docs/urp.md +20 -0
- package/extensions/urp/extension.json +41 -0
- package/extensions/urp/unitypkg/Editor/Urp/UrpFeatureToolSupport.cs +336 -0
- package/extensions/urp/unitypkg/Editor/Urp/UrpToolSupport.cs +801 -0
- package/extensions/urp/unitypkg/Editor/Urp/feature-add.cs +12 -0
- package/extensions/urp/unitypkg/Editor/Urp/feature-configure.cs +12 -0
- package/extensions/urp/unitypkg/Editor/Urp/feature-list.cs +12 -0
- package/extensions/urp/unitypkg/Editor/Urp/feature-remove.cs +12 -0
- package/extensions/urp/unitypkg/Editor/Urp/feature-toggle.cs +12 -0
- package/extensions/urp/unitypkg/Editor/Urp/settings-get.cs +27 -0
- package/extensions/urp/unitypkg/Editor/Urp/settings-set.cs +47 -0
- package/extensions/urp/unitypkg/Editor/Urp/volume-add-effect.cs +33 -0
- package/extensions/urp/unitypkg/Editor/Urp/volume-create.cs +33 -0
- package/extensions/urp/unitypkg/Editor/Urp/volume-info.cs +31 -0
- package/extensions/urp/unitypkg/Editor/Urp/volume-list.cs +39 -0
- package/extensions/urp/unitypkg/Editor/Urp/volume-set.cs +33 -0
- package/extensions/urp/unitypkg/UnityCli.Extensions.Urp.Editor.asmdef +19 -0
- package/extensions/vfx-graph/bridge/domains/vfx.ts +337 -0
- package/extensions/vfx-graph/docs/vfx.md +28 -0
- package/extensions/vfx-graph/extension.json +72 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/VfxActionToolSupport.cs +248 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/VfxToolSupport.cs +1325 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/control-pause.cs +37 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/control-play.cs +40 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/control-reinitialize.cs +37 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/control-set-rate.cs +36 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/control-set-seed.cs +42 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/control-stop.cs +37 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/effect-add.cs +55 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/effect-assign.cs +44 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/effect-info.cs +30 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/graph-create.cs +62 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/graph-info.cs +32 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/line-create.cs +75 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/line-positions.cs +100 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/parameter.cs +216 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/particle-clear.cs +39 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/particle-create.cs +80 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/particle-emission.cs +126 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/particle-info.cs +55 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/particle-main.cs +75 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/particle-pause.cs +38 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/particle-play.cs +38 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/particle-shape.cs +148 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/particle-stop.cs +41 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/template-create.cs +58 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/template-list.cs +31 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/trail-create.cs +41 -0
- package/extensions/vfx-graph/unitypkg/Editor/Vfx/trail-set.cs +44 -0
- package/extensions/vfx-graph/unitypkg/UnityCli.Extensions.VfxGraph.Editor.asmdef +18 -0
- package/package.json +46 -0
- package/schemas/error-code.schema.json +70 -0
- package/schemas/extension-descriptor.schema.json +88 -0
- package/schemas/extension-manifest.schema.json +125 -0
- package/schemas/request-envelope.schema.json +34 -0
- package/schemas/response-envelope.schema.json +42 -0
- package/schemas/tool-metadata.schema.json +111 -0
- package/skills/unitycli/SKILL.md +61 -0
- package/skills/unitycli/agents/interface.yaml +30 -0
- package/skills/unitycli/evals/history/2026-08-05-governed-baseline.json +44 -0
- package/skills/unitycli/evals/output/cases.jsonl +5 -0
- package/skills/unitycli/evals/output/fixtures/feedback-scenario.json +27 -0
- package/skills/unitycli/evals/output/schema.json +32 -0
- package/skills/unitycli/evals/semantic_config.json +46 -0
- package/skills/unitycli/evals/trigger_cases.json +39 -0
- package/skills/unitycli/examples/feedback-event.input.json +8 -0
- package/skills/unitycli/examples/feedback-record.output.json +12 -0
- package/skills/unitycli/manifest.json +112 -0
- package/skills/unitycli/references/custom-tools.md +111 -0
- package/skills/unitycli/references/feedback.md +59 -0
- package/skills/unitycli/references/operation-protocol.md +83 -0
- package/skills/unitycli/reports/output_quality_scorecard.md +56 -0
- package/skills/unitycli/reports/trust-report.md +57 -0
- package/skills/unitycli/scripts/record-feedback.ps1 +210 -0
- package/skills/unitycli/security/network_policy.json +18 -0
- package/skills/unitycli/security/permission_policy.json +40 -0
- package/unitypkg/Editor/Bridge/BridgeCommandContext.cs +64 -0
- package/unitypkg/Editor/Bridge/BridgeCommandException.cs +39 -0
- package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +172 -0
- package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +534 -0
- package/unitypkg/Editor/Bridge/BridgeResponse.cs +58 -0
- package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +325 -0
- package/unitypkg/Editor/BridgeStartupStateMachine.cs +81 -0
- package/unitypkg/Editor/Commands/ProjectToolCommands.cs +43 -0
- package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +43 -0
- package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +428 -0
- package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +664 -0
- package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +148 -0
- package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +411 -0
- package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +485 -0
- package/unitypkg/Editor/Infrastructure/UnityCliArtifacts.cs +110 -0
- package/unitypkg/Editor/Infrastructure/UnityCliAssetRefreshCoordinator.cs +106 -0
- package/unitypkg/Editor/Infrastructure/UnityCliBridgeHelpers.cs +155 -0
- package/unitypkg/Editor/Infrastructure/UnityCliCanonicalProjectPath.cs +325 -0
- package/unitypkg/Editor/Infrastructure/UnityCliCompilationEventBroker.cs +192 -0
- package/unitypkg/Editor/Infrastructure/UnityCliCompileCoordinator.cs +271 -0
- package/unitypkg/Editor/Infrastructure/UnityCliCompletionProbe.cs +515 -0
- package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +84 -0
- package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +558 -0
- package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +155 -0
- package/unitypkg/Editor/Infrastructure/UnityCliLargeOutput.cs +128 -0
- package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +230 -0
- package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +164 -0
- package/unitypkg/Editor/Infrastructure/UnityCliLog.cs +127 -0
- package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +2073 -0
- package/unitypkg/Editor/Infrastructure/UnityCliObjectPatch.cs +193 -0
- package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +278 -0
- package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +620 -0
- package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +1739 -0
- package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +828 -0
- package/unitypkg/Editor/Infrastructure/UnityCliPackageEmbedCoordinator.cs +147 -0
- package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +454 -0
- package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +561 -0
- package/unitypkg/Editor/Infrastructure/UnityCliPipelineHandoff.cs +742 -0
- package/unitypkg/Editor/Infrastructure/UnityCliProjectSettings.cs +92 -0
- package/unitypkg/Editor/Infrastructure/UnityCliProtocolRequest.cs +116 -0
- package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +477 -0
- package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +664 -0
- package/unitypkg/Editor/Infrastructure/UnityCliRequestDigest.cs +34 -0
- package/unitypkg/Editor/Infrastructure/UnityCliRuntimeBuildInjection.cs +121 -0
- package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +391 -0
- package/unitypkg/Editor/Infrastructure/UnityCliSceneMutation.cs +84 -0
- package/unitypkg/Editor/Infrastructure/UnityCliScriptableObjectHelpers.cs +235 -0
- package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +580 -0
- package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +568 -0
- package/unitypkg/Editor/Infrastructure/UnityCliStateIO.cs +73 -0
- package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +598 -0
- package/unitypkg/Editor/Infrastructure/UnityCliTransactionLog.cs +112 -0
- package/unitypkg/Editor/Infrastructure/UnityCliValueConverter.cs +146 -0
- package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +716 -0
- package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +152 -0
- package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +992 -0
- package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +938 -0
- package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +258 -0
- package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +75 -0
- package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +68 -0
- package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +297 -0
- package/unitypkg/Editor/Protocol/ProtocolEnvelopeFactory.cs +92 -0
- package/unitypkg/Editor/Protocol/envelope.cs +50 -0
- package/unitypkg/Editor/Roslyn/LICENSE.txt +23 -0
- package/unitypkg/Editor/Roslyn/Microsoft.CodeAnalysis.CSharp.dll +0 -0
- package/unitypkg/Editor/Roslyn/Microsoft.CodeAnalysis.dll +0 -0
- package/unitypkg/Editor/Roslyn/System.Collections.Immutable.dll +0 -0
- package/unitypkg/Editor/Roslyn/System.Reflection.Metadata.dll +0 -0
- package/unitypkg/Editor/Tools/animation/AnimationToolSupport.cs +335 -0
- package/unitypkg/Editor/Tools/animation/AnimatorRuntimeToolSupport.cs +24 -0
- package/unitypkg/Editor/Tools/animation/animator/crossfade.cs +117 -0
- package/unitypkg/Editor/Tools/animation/animator/get-info.cs +24 -0
- package/unitypkg/Editor/Tools/animation/animator/get-parameter.cs +36 -0
- package/unitypkg/Editor/Tools/animation/animator/set-enabled.cs +33 -0
- package/unitypkg/Editor/Tools/animation/animator/set-parameter.cs +71 -0
- package/unitypkg/Editor/Tools/animation/animator/set-speed.cs +33 -0
- package/unitypkg/Editor/Tools/animation/clip/create.cs +48 -0
- package/unitypkg/Editor/Tools/animation/clip/modify.cs +43 -0
- package/unitypkg/Editor/Tools/animation/clip.cs +23 -0
- package/unitypkg/Editor/Tools/animation/play.cs +41 -0
- package/unitypkg/Editor/Tools/animation/state.cs +25 -0
- package/unitypkg/Editor/Tools/animation/stop.cs +45 -0
- package/unitypkg/Editor/Tools/asset/AssetImportSettingsToolSupport.cs +633 -0
- package/unitypkg/Editor/Tools/asset/AssetToolSupport.cs +253 -0
- package/unitypkg/Editor/Tools/asset/create.cs +84 -0
- package/unitypkg/Editor/Tools/asset/delete.cs +88 -0
- package/unitypkg/Editor/Tools/asset/duplicate.cs +68 -0
- package/unitypkg/Editor/Tools/asset/get-components.cs +177 -0
- package/unitypkg/Editor/Tools/asset/get-data.cs +73 -0
- package/unitypkg/Editor/Tools/asset/import-settings/set.cs +127 -0
- package/unitypkg/Editor/Tools/asset/import-settings.cs +54 -0
- package/unitypkg/Editor/Tools/asset/import.cs +132 -0
- package/unitypkg/Editor/Tools/asset/info.cs +48 -0
- package/unitypkg/Editor/Tools/asset/modify.cs +69 -0
- package/unitypkg/Editor/Tools/asset/move.cs +70 -0
- package/unitypkg/Editor/Tools/asset/preview.cs +86 -0
- package/unitypkg/Editor/Tools/asset/rename.cs +84 -0
- package/unitypkg/Editor/Tools/asset/search-built-in.cs +90 -0
- package/unitypkg/Editor/Tools/asset/search.cs +83 -0
- package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +37 -0
- package/unitypkg/Editor/Tools/build/BuildSettingsToolSupport.cs +4 -0
- package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +15 -0
- package/unitypkg/Editor/Tools/build/BuildToolSupport.cs +111 -0
- package/unitypkg/Editor/Tools/build/platform/get.cs +12 -0
- package/unitypkg/Editor/Tools/build/platform/set.cs +45 -0
- package/unitypkg/Editor/Tools/build/profile/create.cs +44 -0
- package/unitypkg/Editor/Tools/build/profile/list.cs +12 -0
- package/unitypkg/Editor/Tools/build/profile/modify.cs +42 -0
- package/unitypkg/Editor/Tools/build/profile/switch.cs +30 -0
- package/unitypkg/Editor/Tools/build/scene/add.cs +45 -0
- package/unitypkg/Editor/Tools/build/scene/disable.cs +3 -0
- package/unitypkg/Editor/Tools/build/scene/enable.cs +4 -0
- package/unitypkg/Editor/Tools/build/scene/list.cs +4 -0
- package/unitypkg/Editor/Tools/build/scene/remove.cs +29 -0
- package/unitypkg/Editor/Tools/build/settings/get.cs +3 -0
- package/unitypkg/Editor/Tools/build/settings/set.cs +74 -0
- package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +292 -0
- package/unitypkg/Editor/Tools/camera/create.cs +114 -0
- package/unitypkg/Editor/Tools/camera/info.cs +49 -0
- package/unitypkg/Editor/Tools/camera/lens.cs +119 -0
- package/unitypkg/Editor/Tools/camera/list.cs +52 -0
- package/unitypkg/Editor/Tools/camera/multiview.cs +96 -0
- package/unitypkg/Editor/Tools/camera/screenshot.cs +65 -0
- package/unitypkg/Editor/Tools/camera/target.cs +94 -0
- package/unitypkg/Editor/Tools/component/add.cs +125 -0
- package/unitypkg/Editor/Tools/component/get.cs +73 -0
- package/unitypkg/Editor/Tools/component/list-types.cs +90 -0
- package/unitypkg/Editor/Tools/component/modify.cs +103 -0
- package/unitypkg/Editor/Tools/component/remove.cs +147 -0
- package/unitypkg/Editor/Tools/component/set.cs +93 -0
- package/unitypkg/Editor/Tools/console/clear.cs +38 -0
- package/unitypkg/Editor/Tools/console/get.cs +28 -0
- package/unitypkg/Editor/Tools/controller/add-blend-tree-child.cs +88 -0
- package/unitypkg/Editor/Tools/controller/add-layer.cs +43 -0
- package/unitypkg/Editor/Tools/controller/add-parameter.cs +51 -0
- package/unitypkg/Editor/Tools/controller/add-state.cs +44 -0
- package/unitypkg/Editor/Tools/controller/add-transition.cs +71 -0
- package/unitypkg/Editor/Tools/controller/assign.cs +38 -0
- package/unitypkg/Editor/Tools/controller/create-blend-tree-1d.cs +40 -0
- package/unitypkg/Editor/Tools/controller/create-blend-tree-2d.cs +42 -0
- package/unitypkg/Editor/Tools/controller/create.cs +31 -0
- package/unitypkg/Editor/Tools/controller/get-info.cs +27 -0
- package/unitypkg/Editor/Tools/controller/remove-layer.cs +57 -0
- package/unitypkg/Editor/Tools/controller/set-layer-weight.cs +42 -0
- package/unitypkg/Editor/Tools/diagnose/preflight.cs +269 -0
- package/unitypkg/Editor/Tools/editor/EditorActionToolSupport.cs +117 -0
- package/unitypkg/Editor/Tools/editor/EditorSearchToolSupport.cs +146 -0
- package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +132 -0
- package/unitypkg/Editor/Tools/editor/EditorWindowFocusToolSupport.cs +194 -0
- package/unitypkg/Editor/Tools/editor/active-tool/set.cs +33 -0
- package/unitypkg/Editor/Tools/editor/active-tool.cs +100 -0
- package/unitypkg/Editor/Tools/editor/autotick/set.cs +102 -0
- package/unitypkg/Editor/Tools/editor/console.cs +26 -0
- package/unitypkg/Editor/Tools/editor/focus.cs +80 -0
- package/unitypkg/Editor/Tools/editor/menu-items.cs +50 -0
- package/unitypkg/Editor/Tools/editor/menu.cs +53 -0
- package/unitypkg/Editor/Tools/editor/pause.cs +38 -0
- package/unitypkg/Editor/Tools/editor/play.cs +43 -0
- package/unitypkg/Editor/Tools/editor/redo.cs +56 -0
- package/unitypkg/Editor/Tools/editor/resolution.cs +34 -0
- package/unitypkg/Editor/Tools/editor/search.cs +55 -0
- package/unitypkg/Editor/Tools/editor/selection/set.cs +37 -0
- package/unitypkg/Editor/Tools/editor/selection.cs +153 -0
- package/unitypkg/Editor/Tools/editor/stop.cs +43 -0
- package/unitypkg/Editor/Tools/editor/tool-states.cs +140 -0
- package/unitypkg/Editor/Tools/editor/undo.cs +56 -0
- package/unitypkg/Editor/Tools/editor/windows.cs +87 -0
- package/unitypkg/Editor/Tools/extension/status.cs +87 -0
- package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +524 -0
- package/unitypkg/Editor/Tools/go/create.cs +333 -0
- package/unitypkg/Editor/Tools/go/delete.cs +87 -0
- package/unitypkg/Editor/Tools/go/duplicate.cs +76 -0
- package/unitypkg/Editor/Tools/go/find.cs +152 -0
- package/unitypkg/Editor/Tools/go/inspect.cs +49 -0
- package/unitypkg/Editor/Tools/go/look-at.cs +76 -0
- package/unitypkg/Editor/Tools/go/modify.cs +156 -0
- package/unitypkg/Editor/Tools/go/move-relative.cs +74 -0
- package/unitypkg/Editor/Tools/go/set-parent.cs +84 -0
- package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +396 -0
- package/unitypkg/Editor/Tools/graphics/bake-cancel.cs +27 -0
- package/unitypkg/Editor/Tools/graphics/bake-clear.cs +31 -0
- package/unitypkg/Editor/Tools/graphics/bake-reflection-probe.cs +35 -0
- package/unitypkg/Editor/Tools/graphics/bake-settings.cs +131 -0
- package/unitypkg/Editor/Tools/graphics/bake-start.cs +27 -0
- package/unitypkg/Editor/Tools/graphics/bake-status.cs +31 -0
- package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +56 -0
- package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +19 -0
- package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +24 -0
- package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +46 -0
- package/unitypkg/Editor/Tools/graphics/probe-positions.cs +58 -0
- package/unitypkg/Editor/Tools/graphics/quality.cs +85 -0
- package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +73 -0
- package/unitypkg/Editor/Tools/graphics/scene-debug.cs +37 -0
- package/unitypkg/Editor/Tools/graphics/settings.cs +104 -0
- package/unitypkg/Editor/Tools/graphics/skybox-get.cs +33 -0
- package/unitypkg/Editor/Tools/graphics/skybox-set-ambient.cs +24 -0
- package/unitypkg/Editor/Tools/graphics/skybox-set-fog.cs +24 -0
- package/unitypkg/Editor/Tools/graphics/skybox-set-material.cs +33 -0
- package/unitypkg/Editor/Tools/graphics/skybox-set-properties.cs +51 -0
- package/unitypkg/Editor/Tools/graphics/skybox-set-reflection.cs +31 -0
- package/unitypkg/Editor/Tools/graphics/skybox-set-sun.cs +35 -0
- package/unitypkg/Editor/Tools/graphics/stats-counters.cs +28 -0
- package/unitypkg/Editor/Tools/graphics/stats-memory.cs +21 -0
- package/unitypkg/Editor/Tools/graphics/stats.cs +17 -0
- package/unitypkg/Editor/Tools/graphics/volume.cs +59 -0
- package/unitypkg/Editor/Tools/instances/list.cs +71 -0
- package/unitypkg/Editor/Tools/material/MaterialToolSupport.cs +401 -0
- package/unitypkg/Editor/Tools/material/assign.cs +124 -0
- package/unitypkg/Editor/Tools/material/create.cs +82 -0
- package/unitypkg/Editor/Tools/material/info.cs +55 -0
- package/unitypkg/Editor/Tools/material/set-renderer-color.cs +108 -0
- package/unitypkg/Editor/Tools/material/set.cs +114 -0
- package/unitypkg/Editor/Tools/navmesh/NavMeshToolSupport.cs +404 -0
- package/unitypkg/Editor/Tools/navmesh/bake-clear.cs +70 -0
- package/unitypkg/Editor/Tools/navmesh/settings/set.cs +73 -0
- package/unitypkg/Editor/Tools/navmesh/settings.cs +37 -0
- package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +99 -0
- package/unitypkg/Editor/Tools/object/get-data.cs +69 -0
- package/unitypkg/Editor/Tools/object/modify.cs +126 -0
- package/unitypkg/Editor/Tools/occlusion/OcclusionToolSupport.cs +422 -0
- package/unitypkg/Editor/Tools/occlusion/bake-clear.cs +67 -0
- package/unitypkg/Editor/Tools/physics/PhysicsJointToolSupport.cs +13 -0
- package/unitypkg/Editor/Tools/physics/PhysicsToolSupport.cs +389 -0
- package/unitypkg/Editor/Tools/physics/assign-material.cs +88 -0
- package/unitypkg/Editor/Tools/physics/collision-matrix.cs +90 -0
- package/unitypkg/Editor/Tools/physics/force.cs +127 -0
- package/unitypkg/Editor/Tools/physics/joint/add.cs +99 -0
- package/unitypkg/Editor/Tools/physics/joint/remove.cs +4 -0
- package/unitypkg/Editor/Tools/physics/linecast.cs +59 -0
- package/unitypkg/Editor/Tools/physics/material.cs +73 -0
- package/unitypkg/Editor/Tools/physics/overlap.cs +84 -0
- package/unitypkg/Editor/Tools/physics/raycast-all.cs +67 -0
- package/unitypkg/Editor/Tools/physics/raycast.cs +96 -0
- package/unitypkg/Editor/Tools/physics/rigidbody.cs +116 -0
- package/unitypkg/Editor/Tools/physics/settings.cs +116 -0
- package/unitypkg/Editor/Tools/physics/shapecast.cs +127 -0
- package/unitypkg/Editor/Tools/physics/simulation.cs +89 -0
- package/unitypkg/Editor/Tools/physics/validate.cs +67 -0
- package/unitypkg/Editor/Tools/prefab/PrefabOverrideToolSupport.cs +355 -0
- package/unitypkg/Editor/Tools/prefab/PrefabToolSupport.cs +446 -0
- package/unitypkg/Editor/Tools/prefab/create.cs +125 -0
- package/unitypkg/Editor/Tools/prefab/hierarchy.cs +157 -0
- package/unitypkg/Editor/Tools/prefab/info.cs +44 -0
- package/unitypkg/Editor/Tools/prefab/instantiate.cs +106 -0
- package/unitypkg/Editor/Tools/prefab/modify.cs +112 -0
- package/unitypkg/Editor/Tools/prefab/overrides/apply.cs +51 -0
- package/unitypkg/Editor/Tools/prefab/overrides/revert.cs +51 -0
- package/unitypkg/Editor/Tools/prefab/stage-close.cs +67 -0
- package/unitypkg/Editor/Tools/prefab/stage-open.cs +69 -0
- package/unitypkg/Editor/Tools/prefab/stage-save.cs +64 -0
- package/unitypkg/Editor/Tools/prefab/stage.cs +68 -0
- package/unitypkg/Editor/Tools/prefab/unpack.cs +80 -0
- package/unitypkg/Editor/Tools/prefab/variant/create.cs +53 -0
- package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +68 -0
- package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +30 -0
- package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +515 -0
- package/unitypkg/Editor/Tools/profiler/areas.cs +36 -0
- package/unitypkg/Editor/Tools/profiler/capture.cs +188 -0
- package/unitypkg/Editor/Tools/profiler/clear.cs +57 -0
- package/unitypkg/Editor/Tools/profiler/counter.cs +34 -0
- package/unitypkg/Editor/Tools/profiler/deep.cs +48 -0
- package/unitypkg/Editor/Tools/profiler/export.cs +85 -0
- package/unitypkg/Editor/Tools/profiler/frame-debugger/disable.cs +53 -0
- package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +66 -0
- package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +140 -0
- package/unitypkg/Editor/Tools/profiler/frame.cs +38 -0
- package/unitypkg/Editor/Tools/profiler/hierarchy.cs +183 -0
- package/unitypkg/Editor/Tools/profiler/load.cs +85 -0
- package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +78 -0
- package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +73 -0
- package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +147 -0
- package/unitypkg/Editor/Tools/profiler/memory.cs +34 -0
- package/unitypkg/Editor/Tools/profiler/module-enable.cs +52 -0
- package/unitypkg/Editor/Tools/profiler/modules.cs +33 -0
- package/unitypkg/Editor/Tools/profiler/object-memory.cs +49 -0
- package/unitypkg/Editor/Tools/profiler/pause.cs +49 -0
- package/unitypkg/Editor/Tools/profiler/rendering-stats.cs +109 -0
- package/unitypkg/Editor/Tools/profiler/script-stats.cs +35 -0
- package/unitypkg/Editor/Tools/profiler/start.cs +55 -0
- package/unitypkg/Editor/Tools/profiler/status.cs +36 -0
- package/unitypkg/Editor/Tools/profiler/stop.cs +53 -0
- package/unitypkg/Editor/Tools/project/ProjectSettingsReader.cs +31 -0
- package/unitypkg/Editor/Tools/project/ProjectSettingsToolSupport.cs +757 -0
- package/unitypkg/Editor/Tools/project/ProjectTagLayerToolSupport.cs +23 -0
- package/unitypkg/Editor/Tools/project/audio-settings/set.cs +35 -0
- package/unitypkg/Editor/Tools/project/audio-settings.cs +31 -0
- package/unitypkg/Editor/Tools/project/info.cs +52 -0
- package/unitypkg/Editor/Tools/project/input-settings/set.cs +35 -0
- package/unitypkg/Editor/Tools/project/input-settings.cs +35 -0
- package/unitypkg/Editor/Tools/project/layer/add.cs +3 -0
- package/unitypkg/Editor/Tools/project/layer/remove.cs +3 -0
- package/unitypkg/Editor/Tools/project/layers.cs +61 -0
- package/unitypkg/Editor/Tools/project/player-settings/set.cs +35 -0
- package/unitypkg/Editor/Tools/project/player-settings.cs +31 -0
- package/unitypkg/Editor/Tools/project/tag/add.cs +3 -0
- package/unitypkg/Editor/Tools/project/tag/remove.cs +3 -0
- package/unitypkg/Editor/Tools/project/tags.cs +45 -0
- package/unitypkg/Editor/Tools/project/time-settings/set.cs +35 -0
- package/unitypkg/Editor/Tools/project/time-settings.cs +31 -0
- package/unitypkg/Editor/Tools/reflection/method/call.cs +114 -0
- package/unitypkg/Editor/Tools/reflection/method/find.cs +72 -0
- package/unitypkg/Editor/Tools/reflection/type/schema.cs +56 -0
- package/unitypkg/Editor/Tools/scene/SceneToolSupport.cs +181 -0
- package/unitypkg/Editor/Tools/scene/active.cs +62 -0
- package/unitypkg/Editor/Tools/scene/build-settings.cs +69 -0
- package/unitypkg/Editor/Tools/scene/create.cs +78 -0
- package/unitypkg/Editor/Tools/scene/data.cs +125 -0
- package/unitypkg/Editor/Tools/scene/gameobject/component.cs +63 -0
- package/unitypkg/Editor/Tools/scene/gameobject/components.cs +77 -0
- package/unitypkg/Editor/Tools/scene/gameobject-support.cs +101 -0
- package/unitypkg/Editor/Tools/scene/gameobject.cs +83 -0
- package/unitypkg/Editor/Tools/scene/hierarchy.cs +250 -0
- package/unitypkg/Editor/Tools/scene/list-opened.cs +77 -0
- package/unitypkg/Editor/Tools/scene/load.cs +68 -0
- package/unitypkg/Editor/Tools/scene/move-object.cs +122 -0
- package/unitypkg/Editor/Tools/scene/save.cs +231 -0
- package/unitypkg/Editor/Tools/scene/set-active.cs +76 -0
- package/unitypkg/Editor/Tools/scene/unload.cs +109 -0
- package/unitypkg/Editor/Tools/scene/validate.cs +115 -0
- package/unitypkg/Editor/Tools/scene/view-frame.cs +218 -0
- package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +181 -0
- package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +196 -0
- package/unitypkg/Editor/Tools/screenshot/editor-element.cs +87 -0
- package/unitypkg/Editor/Tools/screenshot/game-view.cs +116 -0
- package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +93 -0
- package/unitypkg/Editor/Tools/screenshot/orbit.cs +103 -0
- package/unitypkg/Editor/Tools/screenshot/scene-view.cs +99 -0
- package/unitypkg/Editor/Tools/screenshot/screen.cs +89 -0
- package/unitypkg/Editor/Tools/screenshot/surround.cs +181 -0
- package/unitypkg/Editor/Tools/scriptable-object/ScriptableObjectToolSupport.cs +196 -0
- package/unitypkg/Editor/Tools/scriptable-object/create.cs +79 -0
- package/unitypkg/Editor/Tools/scriptable-object/info.cs +49 -0
- package/unitypkg/Editor/Tools/scriptable-object/list-types.cs +103 -0
- package/unitypkg/Editor/Tools/scriptable-object/modify.cs +208 -0
- package/unitypkg/Editor/Tools/shader/ShaderToolSupport.cs +326 -0
- package/unitypkg/Editor/Tools/shader/create.cs +82 -0
- package/unitypkg/Editor/Tools/shader/delete.cs +90 -0
- package/unitypkg/Editor/Tools/shader/get-data.cs +125 -0
- package/unitypkg/Editor/Tools/shader/info.cs +48 -0
- package/unitypkg/Editor/Tools/shader/list.cs +82 -0
- package/unitypkg/Editor/Tools/shader/read.cs +84 -0
- package/unitypkg/Editor/Tools/shader/update.cs +71 -0
- package/unitypkg/Editor/Tools/tests/list.cs +98 -0
- package/unitypkg/Editor/Tools/texture/TextureToolSupport.cs +327 -0
- package/unitypkg/Editor/Tools/texture/create.cs +182 -0
- package/unitypkg/Editor/Tools/texture/delete.cs +89 -0
- package/unitypkg/Editor/Tools/texture/generate.cs +189 -0
- package/unitypkg/Editor/Tools/texture/import-settings.cs +104 -0
- package/unitypkg/Editor/Tools/texture/info.cs +50 -0
- package/unitypkg/Editor/Tools/texture/modify.cs +154 -0
- package/unitypkg/Editor/Tools/texture/sprite.cs +132 -0
- package/unitypkg/Editor/Tools/timeline/TimelineMutationToolSupport.cs +696 -0
- package/unitypkg/Editor/Tools/timeline/TimelineReadToolSupport.cs +439 -0
- package/unitypkg/Editor/Tools/timeline/TimelineToolSupport.cs +203 -0
- package/unitypkg/Editor/Tools/timeline/clip/add.cs +93 -0
- package/unitypkg/Editor/Tools/timeline/create.cs +88 -0
- package/unitypkg/Editor/Tools/timeline/info.cs +54 -0
- package/unitypkg/Editor/Tools/timeline/track/add.cs +91 -0
- package/unitypkg/Editor/Tools/ui/UiToolSupport.cs +162 -0
- package/unitypkg/Editor/Tools/ui/UiUguiToolSupport.cs +24 -0
- package/unitypkg/Editor/Tools/ui/attach-document.cs +46 -0
- package/unitypkg/Editor/Tools/ui/button/create.cs +58 -0
- package/unitypkg/Editor/Tools/ui/canvas/create.cs +40 -0
- package/unitypkg/Editor/Tools/ui/create-panel-settings.cs +44 -0
- package/unitypkg/Editor/Tools/ui/detach-document.cs +41 -0
- package/unitypkg/Editor/Tools/ui/document/create.cs +49 -0
- package/unitypkg/Editor/Tools/ui/document/delete.cs +33 -0
- package/unitypkg/Editor/Tools/ui/document/info.cs +33 -0
- package/unitypkg/Editor/Tools/ui/document/update.cs +36 -0
- package/unitypkg/Editor/Tools/ui/get-visual-tree.cs +34 -0
- package/unitypkg/Editor/Tools/ui/hierarchy.cs +27 -0
- package/unitypkg/Editor/Tools/ui/image/create.cs +47 -0
- package/unitypkg/Editor/Tools/ui/link-stylesheet.cs +54 -0
- package/unitypkg/Editor/Tools/ui/list-documents.cs +34 -0
- package/unitypkg/Editor/Tools/ui/modify-visual-element.cs +76 -0
- package/unitypkg/Editor/Tools/ui/render-preview.cs +128 -0
- package/unitypkg/Editor/Tools/ui/text/create.cs +45 -0
- package/unitypkg/Editor/Tools/ui/update-panel-settings.cs +49 -0
- package/unitypkg/Editor/UnityCliBridge.cs +431 -0
- package/unitypkg/Editor/UnityCliBridgeBuildInfo.cs +9 -0
- package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +580 -0
- package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeJson.cs +232 -0
- package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeRegistry.cs +297 -0
- package/unitypkg/Runtime/Tools/UnityCliRuntimeTools.cs +701 -0
- package/unitypkg/Runtime/UnityCli.Runtime.asmdef +14 -0
- package/unitypkg/Shared/UnityCli.Shared.asmdef +14 -0
- package/unitypkg/Shared/UnityCliAdmissionPool.cs +426 -0
- package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +15 -0
- package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +262 -0
- package/unitypkg/UnityCli.Editor.asmdef +26 -0
- package/unitypkg/package.json +11 -0
|
@@ -0,0 +1,1832 @@
|
|
|
1
|
+
import { realpath, readFile, readdir, stat, mkdir, copyFile, rename, rm, rmdir, open } from "node:fs/promises";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { basename, join, relative, resolve, win32, dirname, posix, isAbsolute } from "node:path";
|
|
4
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
/** 严格读取 Unity Packages/manifest.json;调用方不得把损坏的依赖声明当作空状态。 */
|
|
9
|
+
export async function readUnityManifestDependencies(projectPath) {
|
|
10
|
+
const manifest = await readManifest(packagePaths(projectPath).manifestPath, false);
|
|
11
|
+
if (!isPlainObject(manifest) || !isPlainObject(manifest.dependencies)
|
|
12
|
+
|| !Object.values(manifest.dependencies).every((value) => typeof value === "string")) {
|
|
13
|
+
throw new PackageManagerError("MANIFEST_INVALID", `Unity manifest requires an object root with a string-valued dependencies object: ${packagePaths(projectPath).manifestPath}`);
|
|
14
|
+
}
|
|
15
|
+
return { ...manifest.dependencies };
|
|
16
|
+
}
|
|
17
|
+
const bridgePackageName = "com.alpsckr.unitycli";
|
|
18
|
+
const cliCompatibilityRange = ">=0.4.0 <0.5.0";
|
|
19
|
+
const installMetadataFileName = "unitycli.install.json";
|
|
20
|
+
const fileManifestFileName = "unitycli.files.json";
|
|
21
|
+
const deploymentMetadataFileName = "unitycli.package-deployments.json";
|
|
22
|
+
const deploymentBackupDirName = ".unitycli_deploy_backups";
|
|
23
|
+
const packageBridgeProcessStartIdentity = new Date(Date.now() - Math.floor(process.uptime() * 1_000)).toISOString();
|
|
24
|
+
const execFileAsync = promisify(execFile);
|
|
25
|
+
const activeFileMutationCandidates = new Set();
|
|
26
|
+
const activeAtomicWriteCandidates = new Set();
|
|
27
|
+
let cachedCurrentProcessStartIdentity;
|
|
28
|
+
export function packagePaths(projectPath) {
|
|
29
|
+
const resolvedProject = resolve(projectPath);
|
|
30
|
+
return {
|
|
31
|
+
projectPath: resolvedProject,
|
|
32
|
+
manifestPath: join(resolvedProject, "Packages", "manifest.json"),
|
|
33
|
+
lockPath: join(resolvedProject, "Packages", "packages-lock.json")
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export async function readSnapshot(projectPath) {
|
|
37
|
+
const paths = packagePaths(projectPath);
|
|
38
|
+
const manifest = await readManifest(paths.manifestPath, false);
|
|
39
|
+
const lock = await readLock(paths.lockPath);
|
|
40
|
+
const dependencyNames = new Set([...Object.keys(manifest.dependencies ?? {}), ...Object.keys(lock.dependencies ?? {})]);
|
|
41
|
+
const packages = [...dependencyNames].sort().map((name) => toPackageEntry(name, manifest.dependencies?.[name], lock.dependencies?.[name]));
|
|
42
|
+
return {
|
|
43
|
+
projectPath: paths.projectPath,
|
|
44
|
+
manifestPath: paths.manifestPath,
|
|
45
|
+
lockPath: paths.lockPath,
|
|
46
|
+
registry: typeof manifest.registry === "string" ? manifest.registry : undefined,
|
|
47
|
+
scopedRegistries: normalizeScopedRegistries(manifest.scopedRegistries),
|
|
48
|
+
packages
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function packageInfo(projectPath, name) {
|
|
52
|
+
return (await readSnapshot(projectPath)).packages.find((entry) => entry.name === name);
|
|
53
|
+
}
|
|
54
|
+
export async function installPackage(projectPath, spec, options = {}) {
|
|
55
|
+
const resolvedProject = packagePaths(projectPath).projectPath;
|
|
56
|
+
return options.dryRun ? installPackageLocked(resolvedProject, spec, options) : withProjectFileMutationLease(resolvedProject, "package-manifest-mutation", () => installPackageLocked(resolvedProject, spec, options));
|
|
57
|
+
}
|
|
58
|
+
async function installPackageLocked(projectPath, spec, options) {
|
|
59
|
+
const paths = packagePaths(projectPath);
|
|
60
|
+
const manifest = await readManifest(paths.manifestPath, false);
|
|
61
|
+
if (!spec.version) {
|
|
62
|
+
throw new PackageManagerError("PACKAGE_VERSION_REQUIRED", `Package install requires an explicit Unity package version for manifest writes: ${spec.name}`, [
|
|
63
|
+
"Pass a SemVer version with --version or append @<version> to the package name, e.g. com.unity.textmeshpro@3.0.7.",
|
|
64
|
+
"Use package.search or Unity Package Manager documentation to choose a compatible version."
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
manifest.dependencies ??= {};
|
|
68
|
+
const previousVersion = manifest.dependencies[spec.name];
|
|
69
|
+
if (previousVersion && isPackageVersionDowngrade(spec.version, previousVersion)) {
|
|
70
|
+
throw new PackageManagerError("PACKAGE_DOWNGRADE_BLOCKED", `Refusing to downgrade ${spec.name} from ${previousVersion} to ${spec.version}.`, [
|
|
71
|
+
"Choose a version greater than or equal to the currently requested manifest version.",
|
|
72
|
+
"Run tools run package.info --input '{\"name\":\"<package>\"}' to inspect the current requested and locked versions."
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
if (options.dryRun)
|
|
76
|
+
return { manifestPath: paths.manifestPath, installedVersion: spec.version, dryRun: true, plannedEffects: [{ type: "package.install", package: spec.name, version: spec.version, previousVersion }] };
|
|
77
|
+
manifest.dependencies[spec.name] = spec.version;
|
|
78
|
+
await writeManifest(paths.manifestPath, manifest);
|
|
79
|
+
const readback = await readManifest(paths.manifestPath, false);
|
|
80
|
+
const installedVersion = readback.dependencies?.[spec.name];
|
|
81
|
+
if (installedVersion !== spec.version) {
|
|
82
|
+
throw manifestReadbackMismatch(paths.manifestPath, `dependency ${spec.name} was not written with the requested version`);
|
|
83
|
+
}
|
|
84
|
+
return { manifestPath: paths.manifestPath, installedVersion, dependencyInstalled: true };
|
|
85
|
+
}
|
|
86
|
+
export async function removePackage(projectPath, name, options = {}) {
|
|
87
|
+
const resolvedProject = packagePaths(projectPath).projectPath;
|
|
88
|
+
return options.dryRun ? removePackageLocked(resolvedProject, name, options) : withProjectFileMutationLease(resolvedProject, "package-manifest-mutation", () => removePackageLocked(resolvedProject, name, options));
|
|
89
|
+
}
|
|
90
|
+
async function removePackageLocked(projectPath, name, options) {
|
|
91
|
+
const paths = packagePaths(projectPath);
|
|
92
|
+
const manifest = await readManifest(paths.manifestPath, false);
|
|
93
|
+
const lock = await readLock(paths.lockPath);
|
|
94
|
+
const removedVersion = manifest.dependencies?.[name];
|
|
95
|
+
if (!removedVersion) {
|
|
96
|
+
throw new PackageManagerError("PACKAGE_NOT_FOUND", `Package is not declared in manifest: ${name}`, ["Run tools run package.list to inspect installed packages."]);
|
|
97
|
+
}
|
|
98
|
+
const dependents = packageDependents(lock, name);
|
|
99
|
+
const blocked = dependents.length > 0 && !options.force;
|
|
100
|
+
if (blocked && !options.dryRun) {
|
|
101
|
+
throw new PackageManagerError("PACKAGE_DEPENDENTS_BLOCKED", `Package ${name} is required by ${dependents.length} locked package(s): ${dependents.map((entry) => entry.name).join(", ")}.`, [
|
|
102
|
+
"Remove or replace dependent packages first, then retry package.remove.",
|
|
103
|
+
"Rerun with force=true only when you intentionally want Unity PackageManager to resolve the broken dependency graph."
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
const plannedEffects = [{ type: "package.remove", package: name, removedVersion, dependents, forced: options.force === true, blocked }];
|
|
107
|
+
if (!options.dryRun) {
|
|
108
|
+
delete manifest.dependencies?.[name];
|
|
109
|
+
await writeManifest(paths.manifestPath, manifest);
|
|
110
|
+
const readback = await readManifest(paths.manifestPath, false);
|
|
111
|
+
if (readback.dependencies && Object.prototype.hasOwnProperty.call(readback.dependencies, name)) {
|
|
112
|
+
throw manifestReadbackMismatch(paths.manifestPath, `dependency ${name} is still declared`);
|
|
113
|
+
}
|
|
114
|
+
return { manifestPath: paths.manifestPath, dependencyRemoved: true, dependents, forced: options.force === true, dryRun: false, blocked: false };
|
|
115
|
+
}
|
|
116
|
+
return { manifestPath: paths.manifestPath, removedVersion, dependents, forced: options.force === true, dryRun: true, blocked, plannedEffects };
|
|
117
|
+
}
|
|
118
|
+
export async function setRegistry(projectPath, url, options = {}) {
|
|
119
|
+
const resolvedProject = packagePaths(projectPath).projectPath;
|
|
120
|
+
return options.dryRun ? setRegistryLocked(resolvedProject, url, options) : withProjectFileMutationLease(resolvedProject, "package-manifest-mutation", () => setRegistryLocked(resolvedProject, url, options));
|
|
121
|
+
}
|
|
122
|
+
async function setRegistryLocked(projectPath, url, options) {
|
|
123
|
+
const paths = packagePaths(projectPath);
|
|
124
|
+
const manifest = await readManifest(paths.manifestPath, false);
|
|
125
|
+
if (options.dryRun)
|
|
126
|
+
return { manifestPath: paths.manifestPath, registry: url, dryRun: true, plannedEffects: [{ type: "package.registry.set", registry: url, previousRegistry: manifest.registry }] };
|
|
127
|
+
manifest.registry = url;
|
|
128
|
+
await writeManifest(paths.manifestPath, manifest);
|
|
129
|
+
const readback = await readManifest(paths.manifestPath, false);
|
|
130
|
+
if (readback.registry !== url) {
|
|
131
|
+
throw manifestReadbackMismatch(paths.manifestPath, "primary registry does not match the written value");
|
|
132
|
+
}
|
|
133
|
+
return { manifestPath: paths.manifestPath, registry: readback.registry };
|
|
134
|
+
}
|
|
135
|
+
export async function clearRegistry(projectPath, options = {}) {
|
|
136
|
+
const resolvedProject = packagePaths(projectPath).projectPath;
|
|
137
|
+
return options.dryRun ? clearRegistryLocked(resolvedProject, options) : withProjectFileMutationLease(resolvedProject, "package-manifest-mutation", () => clearRegistryLocked(resolvedProject, options));
|
|
138
|
+
}
|
|
139
|
+
async function clearRegistryLocked(projectPath, options) {
|
|
140
|
+
const paths = packagePaths(projectPath);
|
|
141
|
+
const manifest = await readManifest(paths.manifestPath, false);
|
|
142
|
+
if (options.dryRun)
|
|
143
|
+
return { manifestPath: paths.manifestPath, dryRun: true, plannedEffects: [{ type: "package.registry.clear", registry: manifest.registry }] };
|
|
144
|
+
delete manifest.registry;
|
|
145
|
+
await writeManifest(paths.manifestPath, manifest);
|
|
146
|
+
const readback = await readManifest(paths.manifestPath, false);
|
|
147
|
+
if (typeof readback.registry === "string") {
|
|
148
|
+
throw manifestReadbackMismatch(paths.manifestPath, "primary registry is still declared");
|
|
149
|
+
}
|
|
150
|
+
return { manifestPath: paths.manifestPath, registryCleared: true };
|
|
151
|
+
}
|
|
152
|
+
export async function addScopedRegistry(projectPath, registry, options = {}) {
|
|
153
|
+
const resolvedProject = packagePaths(projectPath).projectPath;
|
|
154
|
+
return options.dryRun ? addScopedRegistryLocked(resolvedProject, registry, options) : withProjectFileMutationLease(resolvedProject, "package-manifest-mutation", () => addScopedRegistryLocked(resolvedProject, registry, options));
|
|
155
|
+
}
|
|
156
|
+
async function addScopedRegistryLocked(projectPath, registry, options) {
|
|
157
|
+
const paths = packagePaths(projectPath);
|
|
158
|
+
const manifest = await readManifest(paths.manifestPath, false);
|
|
159
|
+
const registries = normalizeScopedRegistries(manifest.scopedRegistries);
|
|
160
|
+
const index = registries.findIndex((existing) => existing.name === registry.name);
|
|
161
|
+
const normalized = { name: registry.name, url: registry.url, scopes: uniqueScopes(registry.scopes) };
|
|
162
|
+
if (options.dryRun)
|
|
163
|
+
return { manifestPath: paths.manifestPath, registry: normalized, dryRun: true, plannedEffects: [{ type: "package.scoped-registry.add", ...normalized }] };
|
|
164
|
+
if (index >= 0) {
|
|
165
|
+
registries[index] = normalized;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
registries.push(normalized);
|
|
169
|
+
}
|
|
170
|
+
manifest.scopedRegistries = registries;
|
|
171
|
+
await writeManifest(paths.manifestPath, manifest);
|
|
172
|
+
const readback = await readManifest(paths.manifestPath, false);
|
|
173
|
+
const persisted = normalizeScopedRegistries(readback.scopedRegistries).find((entry) => entry.name === normalized.name);
|
|
174
|
+
if (!persisted || persisted.url !== normalized.url || !sameScopes(persisted.scopes, normalized.scopes)) {
|
|
175
|
+
throw manifestReadbackMismatch(paths.manifestPath, `scoped registry ${normalized.name} does not match the written value`);
|
|
176
|
+
}
|
|
177
|
+
return { manifestPath: paths.manifestPath, registry: persisted };
|
|
178
|
+
}
|
|
179
|
+
export async function removeScopedRegistry(projectPath, name, options = {}) {
|
|
180
|
+
const resolvedProject = packagePaths(projectPath).projectPath;
|
|
181
|
+
return options.dryRun ? removeScopedRegistryLocked(resolvedProject, name, options) : withProjectFileMutationLease(resolvedProject, "package-manifest-mutation", () => removeScopedRegistryLocked(resolvedProject, name, options));
|
|
182
|
+
}
|
|
183
|
+
async function removeScopedRegistryLocked(projectPath, name, options) {
|
|
184
|
+
const paths = packagePaths(projectPath);
|
|
185
|
+
const manifest = await readManifest(paths.manifestPath, false);
|
|
186
|
+
const registries = normalizeScopedRegistries(manifest.scopedRegistries);
|
|
187
|
+
const index = registries.findIndex((registry) => registry.name === name);
|
|
188
|
+
if (index < 0) {
|
|
189
|
+
throw new PackageManagerError("SCOPED_REGISTRY_NOT_FOUND", `Scoped registry not found: ${name}`, ["Run tools run package.scoped-registry.list to inspect scoped registries."]);
|
|
190
|
+
}
|
|
191
|
+
const removed = registries[index];
|
|
192
|
+
const matchedPackages = packagesUsingScopes(manifest, removed.scopes);
|
|
193
|
+
const blocked = matchedPackages.length > 0 && !options.force;
|
|
194
|
+
if (blocked && !options.dryRun) {
|
|
195
|
+
throw new PackageManagerError("SCOPED_REGISTRY_IN_USE", `Scoped registry ${name} still matches manifest package(s): ${matchedPackages.join(", ")}.`, [
|
|
196
|
+
"Remove or move packages using this registry scope before removing the scoped registry.",
|
|
197
|
+
"Rerun with force=true only when you intentionally want to leave those packages without their scoped registry."
|
|
198
|
+
]);
|
|
199
|
+
}
|
|
200
|
+
const plannedEffects = [{ type: "package.scoped-registry.remove", name, scopes: removed.scopes, matchedPackages, forced: options.force === true, blocked }];
|
|
201
|
+
if (!options.dryRun) {
|
|
202
|
+
registries.splice(index, 1);
|
|
203
|
+
manifest.scopedRegistries = registries;
|
|
204
|
+
await writeManifest(paths.manifestPath, manifest);
|
|
205
|
+
const readback = await readManifest(paths.manifestPath, false);
|
|
206
|
+
if (normalizeScopedRegistries(readback.scopedRegistries).some((entry) => entry.name === name)) {
|
|
207
|
+
throw manifestReadbackMismatch(paths.manifestPath, `scoped registry ${name} is still declared`);
|
|
208
|
+
}
|
|
209
|
+
return { manifestPath: paths.manifestPath, registryRemoved: true, matchedPackages, forced: options.force === true, dryRun: false, blocked: false };
|
|
210
|
+
}
|
|
211
|
+
return { manifestPath: paths.manifestPath, removed, matchedPackages, forced: options.force === true, dryRun: true, blocked, plannedEffects };
|
|
212
|
+
}
|
|
213
|
+
/** 在任何 init filesystem effect 前冻结 Bridge 源、目标与哈希。 */
|
|
214
|
+
export async function preparePackageBridgeInstall(projectPath) {
|
|
215
|
+
const paths = packagePaths(projectPath);
|
|
216
|
+
const sourcePath = packageSourcePath();
|
|
217
|
+
const packageManifest = await readPackageManifest(sourcePath);
|
|
218
|
+
const projectHash = await hashProjectPath(paths.projectPath);
|
|
219
|
+
const sourceHash = await hashSourcePath(sourcePath);
|
|
220
|
+
const bridgePath = join(paths.projectPath, "Packages", bridgePackageName);
|
|
221
|
+
return {
|
|
222
|
+
manifestPath: paths.manifestPath,
|
|
223
|
+
bridgePath,
|
|
224
|
+
dependencyName: bridgePackageName,
|
|
225
|
+
packageVersion: packageManifest.version,
|
|
226
|
+
sourceHash,
|
|
227
|
+
projectHash,
|
|
228
|
+
installMetadataPath: join(paths.projectPath, "Packages", installMetadataFileName),
|
|
229
|
+
fileManifestPath: join(bridgePath, fileManifestFileName),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/** owner先在候选目录完整fsync,再以目录rename原子发布为固定lock;崩溃不会留下不完整primary。 */
|
|
233
|
+
async function withProjectFileMutationLease(projectPath, leaseName, action) {
|
|
234
|
+
const stateRoot = join(projectPath, "Library", "UnityCli");
|
|
235
|
+
const lockPath = join(stateRoot, `${leaseName}.lock`);
|
|
236
|
+
const recoveredPath = join(stateRoot, `${leaseName}.recovered`);
|
|
237
|
+
const candidatePrefix = `${leaseName}.candidate-`;
|
|
238
|
+
await mkdir(stateRoot, { recursive: true });
|
|
239
|
+
await cleanupPackageBridgeLockCandidates(stateRoot, candidatePrefix);
|
|
240
|
+
if (await directoryExists(recoveredPath)) {
|
|
241
|
+
const recovered = await readPackageBridgeLock(recoveredPath);
|
|
242
|
+
if (!recovered || await isPackageBridgeOwnerAlive(recovered))
|
|
243
|
+
throw new PackageManagerError("E_UNITY_BUSY", "Bridge publish lease recovery owner is not proven dead.");
|
|
244
|
+
await removePackagePublishDirectory(recoveredPath, "INIT_PUBLISH_LEASE_RECOVERY_FAILED");
|
|
245
|
+
}
|
|
246
|
+
const token = randomUUID();
|
|
247
|
+
const currentProcessStartIdentity = await processStartIdentity(process.pid);
|
|
248
|
+
if (!currentProcessStartIdentity)
|
|
249
|
+
throw new PackageManagerError("PROCESS_IDENTITY_UNAVAILABLE", "Current process start identity is unavailable; file mutation cannot acquire a recoverable lease.");
|
|
250
|
+
const startHash = createHash("sha256").update(currentProcessStartIdentity).digest("hex").slice(0, 16);
|
|
251
|
+
const candidatePath = join(stateRoot, `${candidatePrefix}${process.pid}-${startHash}-${token}`);
|
|
252
|
+
const owner = { token, pid: process.pid, processStartIdentity: currentProcessStartIdentity };
|
|
253
|
+
await mkdir(candidatePath, { recursive: false });
|
|
254
|
+
activeFileMutationCandidates.add(candidatePath);
|
|
255
|
+
try {
|
|
256
|
+
const ownerHandle = await open(join(candidatePath, "owner.json"), "wx", 0o600);
|
|
257
|
+
try {
|
|
258
|
+
await ownerHandle.writeFile(`${JSON.stringify(owner)}\n`, "utf8");
|
|
259
|
+
await ownerHandle.sync();
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
await ownerHandle.close();
|
|
263
|
+
}
|
|
264
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
265
|
+
try {
|
|
266
|
+
await rename(candidatePath, lockPath);
|
|
267
|
+
try {
|
|
268
|
+
return await action();
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
const current = await readPackageBridgeLock(lockPath);
|
|
272
|
+
if (!samePackageBridgeLockOwner(owner, current))
|
|
273
|
+
throw new PackageManagerError("INIT_PUBLISH_LEASE_LOST", "Bridge publish lease ownership changed before release.");
|
|
274
|
+
await removePackagePublishDirectory(lockPath, "INIT_PUBLISH_LEASE_RELEASE_FAILED");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
if (!await directoryExists(candidatePath))
|
|
279
|
+
throw error;
|
|
280
|
+
const current = await readPackageBridgeLock(lockPath);
|
|
281
|
+
if (!current)
|
|
282
|
+
throw new PackageManagerError("INIT_PUBLISH_LEASE_RECOVERY_FAILED", "Bridge publish lock is corrupt or unreadable.");
|
|
283
|
+
if (await isPackageBridgeOwnerAlive(current)) {
|
|
284
|
+
await removePackagePublishDirectory(candidatePath, "INIT_PUBLISH_LEASE_RELEASE_FAILED");
|
|
285
|
+
throw new PackageManagerError("E_UNITY_BUSY", "Another process owns the Bridge package publish lease.");
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
await rename(lockPath, recoveredPath);
|
|
289
|
+
}
|
|
290
|
+
catch (recoveryError) {
|
|
291
|
+
const code = recoveryError?.code;
|
|
292
|
+
if (code === "ENOENT" || code === "EEXIST" || code === "EPERM")
|
|
293
|
+
continue;
|
|
294
|
+
throw recoveryError;
|
|
295
|
+
}
|
|
296
|
+
const isolated = await readPackageBridgeLock(recoveredPath);
|
|
297
|
+
if (!isolated || !samePackageBridgeLockOwner(current, isolated) || await isPackageBridgeOwnerAlive(isolated))
|
|
298
|
+
throw new PackageManagerError("INIT_PUBLISH_LEASE_RECOVERY_FAILED", "Recovered Bridge publish lease identity is not exact.");
|
|
299
|
+
await removePackagePublishDirectory(recoveredPath, "INIT_PUBLISH_LEASE_RECOVERY_FAILED");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
throw new PackageManagerError("INIT_PUBLISH_LEASE_RECOVERY_FAILED", "Bridge publish lease could not be acquired after exact recovery.");
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
activeFileMutationCandidates.delete(candidatePath);
|
|
306
|
+
if (await directoryExists(candidatePath))
|
|
307
|
+
await removePackagePublishDirectory(candidatePath, "INIT_PUBLISH_LEASE_RELEASE_FAILED");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function cleanupPackageBridgeLockCandidates(stateRoot, prefix) {
|
|
311
|
+
const entries = await readdir(stateRoot, { withFileTypes: true });
|
|
312
|
+
for (const entry of entries) {
|
|
313
|
+
if (!entry.isDirectory() || !entry.name.startsWith(prefix))
|
|
314
|
+
continue;
|
|
315
|
+
const match = new RegExp(`^${escapeRegExp(prefix)}(\\d+)-([0-9a-f]{16})-[0-9a-f-]{36}$`, "i").exec(entry.name);
|
|
316
|
+
const candidatePath = join(stateRoot, entry.name);
|
|
317
|
+
const sameProcessStale = !!match && Number(match[1]) === process.pid && !activeFileMutationCandidates.has(candidatePath);
|
|
318
|
+
if (!match || await isCandidateProcessActive(Number(match[1]), match[2]) && !sameProcessStale)
|
|
319
|
+
throw new PackageManagerError("E_UNITY_BUSY", "A Bridge publish lease candidate is still active or invalid.");
|
|
320
|
+
await removePackagePublishDirectory(candidatePath, "INIT_PUBLISH_LEASE_RECOVERY_FAILED");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async function readPackageBridgeLock(path) {
|
|
324
|
+
try {
|
|
325
|
+
return parsePackageBridgeLock(await readFile(join(path, "owner.json"), "utf8"));
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
const code = error?.code;
|
|
329
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
330
|
+
return undefined;
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function parsePackageBridgeLock(raw) {
|
|
335
|
+
try {
|
|
336
|
+
const value = JSON.parse(raw);
|
|
337
|
+
return typeof value.token === "string" && value.token.length > 0 && Number.isInteger(value.pid) && value.pid > 0
|
|
338
|
+
&& typeof value.processStartIdentity === "string" && value.processStartIdentity.length > 0 ? value : undefined;
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function samePackageBridgeLockOwner(left, right) {
|
|
345
|
+
return !!right && left.token === right.token && left.pid === right.pid && left.processStartIdentity === right.processStartIdentity;
|
|
346
|
+
}
|
|
347
|
+
function isProcessAlive(pid) {
|
|
348
|
+
try {
|
|
349
|
+
process.kill(pid, 0);
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
return error?.code === "EPERM";
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async function processStartIdentity(pid) {
|
|
357
|
+
if (pid === process.pid && cachedCurrentProcessStartIdentity)
|
|
358
|
+
return cachedCurrentProcessStartIdentity;
|
|
359
|
+
if (process.platform !== "win32")
|
|
360
|
+
return pid === process.pid ? packageBridgeProcessStartIdentity : undefined;
|
|
361
|
+
try {
|
|
362
|
+
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `$p=Get-Process -Id ${pid} -ErrorAction Stop; $p.StartTime.ToUniversalTime().ToString('O')`], { timeout: 5000, windowsHide: true });
|
|
363
|
+
const value = String(stdout).trim();
|
|
364
|
+
if (pid === process.pid && value)
|
|
365
|
+
cachedCurrentProcessStartIdentity = value;
|
|
366
|
+
return value || undefined;
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function isPackageBridgeOwnerAlive(owner) {
|
|
373
|
+
if (!isProcessAlive(owner.pid))
|
|
374
|
+
return false;
|
|
375
|
+
const observedStart = await processStartIdentity(owner.pid);
|
|
376
|
+
return observedStart === undefined || observedStart === owner.processStartIdentity;
|
|
377
|
+
}
|
|
378
|
+
async function isCandidateProcessActive(pid, startHash) {
|
|
379
|
+
if (!isProcessAlive(pid))
|
|
380
|
+
return false;
|
|
381
|
+
const observedStart = await processStartIdentity(pid);
|
|
382
|
+
return observedStart === undefined || createHash("sha256").update(observedStart).digest("hex").slice(0, 16) === startHash.toLowerCase();
|
|
383
|
+
}
|
|
384
|
+
export async function ensurePackageBridge(projectPath, prepared, runtime = {}) {
|
|
385
|
+
const resolvedProject = packagePaths(projectPath).projectPath;
|
|
386
|
+
return withProjectFileMutationLease(resolvedProject, "package-bridge-publish", () => ensurePackageBridgeLocked(resolvedProject, prepared, runtime));
|
|
387
|
+
}
|
|
388
|
+
async function ensurePackageBridgeLocked(projectPath, prepared, runtime = {}) {
|
|
389
|
+
const paths = packagePaths(projectPath);
|
|
390
|
+
const sourcePath = packageSourcePath();
|
|
391
|
+
const plan = prepared ?? await preparePackageBridgeInstall(paths.projectPath);
|
|
392
|
+
const packageManifest = await readPackageManifest(sourcePath);
|
|
393
|
+
// init plan 的 sourceHash 是 effect 前冻结证据;任何目标写入前必须重新散列并严格匹配。
|
|
394
|
+
const freshSourceHash = await hashSourcePath(sourcePath);
|
|
395
|
+
if (plan.sourceHash !== freshSourceHash || plan.packageVersion !== packageManifest.version) {
|
|
396
|
+
throw new PackageManagerError("INIT_PLAN_STALE", "Bridge package source changed after init prevalidation.", ["Submit init again so it can build a fresh transaction plan."]);
|
|
397
|
+
}
|
|
398
|
+
const projectHash = plan.projectHash;
|
|
399
|
+
const sourceHash = freshSourceHash;
|
|
400
|
+
const targetPath = plan.bridgePath;
|
|
401
|
+
const installMetadataPath = plan.installMetadataPath;
|
|
402
|
+
const fileManifestPath = plan.fileManifestPath;
|
|
403
|
+
const publishDeadlineMs = runtime.absoluteDeadlineMs ?? Date.now() + 5_000;
|
|
404
|
+
const abandonedPreviousCount = await recoverAbandonedPackagePublish(targetPath, publishDeadlineMs, runtime);
|
|
405
|
+
// Check existing installation: skip if same version & hash
|
|
406
|
+
const expectedManagedFiles = await listManagedSourceFiles(sourcePath);
|
|
407
|
+
const expectedManagedFilesSha256 = managedFilesSha256(expectedManagedFiles);
|
|
408
|
+
const targetExists = await directoryExists(targetPath);
|
|
409
|
+
// 包目录不存在时 metadata 只是孤立残留;普通 init 可用当前完整投影替换它。
|
|
410
|
+
const existing = targetExists ? await readOptionalInstallMetadata(installMetadataPath) : undefined;
|
|
411
|
+
if (existing)
|
|
412
|
+
await readVerifiedManagedFileList(fileManifestPath, existing.managedFilesSha256);
|
|
413
|
+
if (existing && existing.packageName === bridgePackageName && existing.installMode === "copy"
|
|
414
|
+
&& existing.packageVersion === packageManifest.version && existing.sourceHash === sourceHash && existing.projectHash === projectHash
|
|
415
|
+
&& existing.managedFilesSha256 === expectedManagedFilesSha256 && existing.cliCompatibilityRange === cliCompatibilityRange
|
|
416
|
+
&& await managedPackageFilesMatch(sourcePath, targetPath, fileManifestPath)) {
|
|
417
|
+
await cleanupPublishedPackageSiblings(targetPath);
|
|
418
|
+
return {
|
|
419
|
+
manifestPath: paths.manifestPath,
|
|
420
|
+
bridgePath: targetPath,
|
|
421
|
+
dependencyName: bridgePackageName,
|
|
422
|
+
packageVersion: packageManifest.version,
|
|
423
|
+
sourceHash,
|
|
424
|
+
projectHash,
|
|
425
|
+
installMode: "copy",
|
|
426
|
+
installTime: existing.installTime,
|
|
427
|
+
cliCompatibilityRange,
|
|
428
|
+
installMetadataPath,
|
|
429
|
+
fileManifestPath
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (abandonedPreviousCount > 0)
|
|
433
|
+
throw new PackageManagerError("INIT_ROLLBACK_REQUIRED", "An abandoned Bridge publish backup cannot be discarded because the current installation is not fully proven.", ["Inspect the current package and its .previous sibling before retrying init."]);
|
|
434
|
+
// 完整投影先写入同级 staging 目录,再以 rename 发布;不会暴露半复制的 Bridge 包。
|
|
435
|
+
const publishNonce = randomUUID();
|
|
436
|
+
const stagingPath = `${targetPath}.staging-${publishNonce}`;
|
|
437
|
+
const backupPath = `${targetPath}.previous-${publishNonce}`;
|
|
438
|
+
// publish 阶段(复制+rename)必须尊重调用方 immutable deadline;Defender/索引器短暂占用时只在该窗口内重试。
|
|
439
|
+
let installTime;
|
|
440
|
+
let observedMetadata;
|
|
441
|
+
let commitProven = false;
|
|
442
|
+
let metadataWriteAttempted = false;
|
|
443
|
+
try {
|
|
444
|
+
const freshTargetExists = await directoryExists(targetPath);
|
|
445
|
+
const freshExisting = freshTargetExists ? await readOptionalInstallMetadata(installMetadataPath) : undefined;
|
|
446
|
+
if (freshTargetExists !== targetExists)
|
|
447
|
+
throw new PackageManagerError("INIT_PLAN_STALE", "Bridge package target changed before staging; no package was published.");
|
|
448
|
+
if ((!existing && freshExisting) || (existing && (!freshExisting || !sameInstallMetadata(existing, freshExisting))))
|
|
449
|
+
throw new PackageManagerError("PACKAGE_FILE_MANIFEST_INVALID", "Bridge install metadata changed before managed cleanup; no package was published.");
|
|
450
|
+
const freshExistingManagedFiles = freshExisting ? await readVerifiedManagedFileList(fileManifestPath, freshExisting.managedFilesSha256) : [];
|
|
451
|
+
await mkdir(stagingPath, { recursive: false });
|
|
452
|
+
if (freshTargetExists)
|
|
453
|
+
await copyDirRecursive(targetPath, stagingPath, targetPath, []);
|
|
454
|
+
await removeStaleManagedFiles(stagingPath, freshExistingManagedFiles);
|
|
455
|
+
await removeEmptyManagedDirectories(stagingPath, false);
|
|
456
|
+
const managedFiles = await copyPackageContents(sourcePath, stagingPath);
|
|
457
|
+
await assertRemovedManagedFilesAbsent(stagingPath, freshExistingManagedFiles, expectedManagedFiles);
|
|
458
|
+
const stagingManifestPath = join(stagingPath, fileManifestFileName);
|
|
459
|
+
await writeFileManifest(stagingManifestPath, managedFiles);
|
|
460
|
+
const publishSourceHash = await hashSourcePath(sourcePath);
|
|
461
|
+
if (publishSourceHash !== sourceHash || managedFilesSha256(managedFiles) !== expectedManagedFilesSha256
|
|
462
|
+
|| !await managedPackageFilesMatch(sourcePath, stagingPath, stagingManifestPath))
|
|
463
|
+
throw new PackageManagerError("INIT_PLAN_STALE", "Bridge package source or staging projection changed before publish.", ["Submit init again with a fresh source plan."]);
|
|
464
|
+
if (targetExists)
|
|
465
|
+
await renamePackageDirectory(targetPath, backupPath, publishDeadlineMs, runtime);
|
|
466
|
+
await renamePackageDirectory(stagingPath, targetPath, publishDeadlineMs, runtime);
|
|
467
|
+
installTime = new Date().toISOString();
|
|
468
|
+
metadataWriteAttempted = true;
|
|
469
|
+
await writeInstallMetadata(installMetadataPath, {
|
|
470
|
+
packageName: bridgePackageName,
|
|
471
|
+
packageVersion: packageManifest.version,
|
|
472
|
+
sourceHash,
|
|
473
|
+
managedFilesSha256: expectedManagedFilesSha256,
|
|
474
|
+
projectHash,
|
|
475
|
+
installMode: "copy",
|
|
476
|
+
installTime,
|
|
477
|
+
cliCompatibilityRange
|
|
478
|
+
});
|
|
479
|
+
// commit前必须fresh证明目标投影和metadata;旧backup在此之前不可删除。
|
|
480
|
+
observedMetadata = await readInstallMetadata(installMetadataPath);
|
|
481
|
+
if (observedMetadata.packageName !== bridgePackageName
|
|
482
|
+
|| observedMetadata.packageVersion !== packageManifest.version
|
|
483
|
+
|| observedMetadata.sourceHash !== sourceHash
|
|
484
|
+
|| observedMetadata.managedFilesSha256 !== expectedManagedFilesSha256
|
|
485
|
+
|| observedMetadata.projectHash !== projectHash
|
|
486
|
+
|| observedMetadata.installMode !== "copy"
|
|
487
|
+
|| observedMetadata.installTime !== installTime
|
|
488
|
+
|| observedMetadata.cliCompatibilityRange !== cliCompatibilityRange
|
|
489
|
+
|| !await managedPackageFilesMatch(sourcePath, targetPath, fileManifestPath))
|
|
490
|
+
throw new PackageManagerError("INIT_CHECK_FAILED", "Bridge package installation could not be proven from fresh filesystem readback.", ["Inspect Packages/com.alpsckr.unitycli and run init again."]);
|
|
491
|
+
commitProven = true;
|
|
492
|
+
// 独占publish lease覆盖整个事务,因此这里只会清理由当前或已退出owner留下的sibling。
|
|
493
|
+
await cleanupPublishedPackageSiblings(targetPath);
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
if (!commitProven) {
|
|
497
|
+
try {
|
|
498
|
+
const currentExists = await directoryExists(targetPath);
|
|
499
|
+
const backupExists = await directoryExists(backupPath);
|
|
500
|
+
if (targetExists) {
|
|
501
|
+
if (backupExists) {
|
|
502
|
+
if (currentExists)
|
|
503
|
+
await removePackagePublishDirectory(targetPath, "INIT_ROLLBACK_FAILED");
|
|
504
|
+
await renamePackageDirectory(backupPath, targetPath, publishDeadlineMs, runtime);
|
|
505
|
+
if (!await directoryExists(targetPath) || await directoryExists(backupPath))
|
|
506
|
+
throw new Error("rollback-readback-mismatch");
|
|
507
|
+
}
|
|
508
|
+
else if (!currentExists)
|
|
509
|
+
throw new Error("previous-target-and-backup-missing");
|
|
510
|
+
}
|
|
511
|
+
else if (currentExists)
|
|
512
|
+
await removePackagePublishDirectory(targetPath, "INIT_ROLLBACK_FAILED");
|
|
513
|
+
if (metadataWriteAttempted) {
|
|
514
|
+
if (existing) {
|
|
515
|
+
await writeInstallMetadata(installMetadataPath, existing);
|
|
516
|
+
const restoredMetadata = await readInstallMetadata(installMetadataPath);
|
|
517
|
+
if (!sameInstallMetadata(existing, restoredMetadata))
|
|
518
|
+
throw new Error("metadata-rollback-readback-mismatch");
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
await rm(installMetadataPath, { force: true });
|
|
522
|
+
await assertPathMissing(installMetadataPath, "INIT_ROLLBACK_FAILED");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
throw new PackageManagerError("INIT_ROLLBACK_FAILED", "Bridge package publish failed and the previous package state could not be restored.", [
|
|
528
|
+
"Inspect Packages/com.alpsckr.unitycli and its sibling .previous directory before submitting a new init request."
|
|
529
|
+
]);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
finally {
|
|
535
|
+
await removePackagePublishDirectory(stagingPath, "INIT_STAGING_CLEANUP_FAILED");
|
|
536
|
+
}
|
|
537
|
+
if (!observedMetadata || !installTime)
|
|
538
|
+
throw new PackageManagerError("INIT_CHECK_FAILED", "Bridge package commit proof is unavailable after publish.");
|
|
539
|
+
return {
|
|
540
|
+
manifestPath: paths.manifestPath,
|
|
541
|
+
bridgePath: targetPath,
|
|
542
|
+
dependencyName: bridgePackageName,
|
|
543
|
+
packageVersion: packageManifest.version,
|
|
544
|
+
sourceHash,
|
|
545
|
+
projectHash,
|
|
546
|
+
installMode: "copy",
|
|
547
|
+
installTime: observedMetadata.installTime,
|
|
548
|
+
cliCompatibilityRange,
|
|
549
|
+
installMetadataPath,
|
|
550
|
+
fileManifestPath
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/** lease恢复后只删除无业务价值的staging;目标缺失时仅从唯一backup恢复,绝不猜测多个候选。 */
|
|
554
|
+
async function recoverAbandonedPackagePublish(targetPath, publishDeadlineMs, runtime) {
|
|
555
|
+
const parent = dirname(targetPath);
|
|
556
|
+
const prefix = `${basename(targetPath)}.`;
|
|
557
|
+
const entries = await readdir(parent, { withFileTypes: true });
|
|
558
|
+
const staging = [];
|
|
559
|
+
const previous = [];
|
|
560
|
+
for (const entry of entries) {
|
|
561
|
+
if (!entry.isDirectory() || !entry.name.startsWith(prefix))
|
|
562
|
+
continue;
|
|
563
|
+
if (/\.staging-[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(entry.name))
|
|
564
|
+
staging.push(join(parent, entry.name));
|
|
565
|
+
if (/\.previous-[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(entry.name))
|
|
566
|
+
previous.push(join(parent, entry.name));
|
|
567
|
+
}
|
|
568
|
+
for (const path of staging)
|
|
569
|
+
await removePackagePublishDirectory(path, "INIT_STAGING_CLEANUP_FAILED");
|
|
570
|
+
if (!await directoryExists(targetPath) && previous.length > 0) {
|
|
571
|
+
if (previous.length !== 1)
|
|
572
|
+
throw new PackageManagerError("INIT_ROLLBACK_REQUIRED", "Multiple abandoned Bridge backups exist; automatic recovery is ambiguous.");
|
|
573
|
+
await renamePackageDirectory(previous[0], targetPath, publishDeadlineMs, runtime);
|
|
574
|
+
if (!await directoryExists(targetPath) || await directoryExists(previous[0]))
|
|
575
|
+
throw new PackageManagerError("INIT_ROLLBACK_FAILED", "The abandoned Bridge backup could not be restored exactly.");
|
|
576
|
+
return 0;
|
|
577
|
+
}
|
|
578
|
+
return previous.length;
|
|
579
|
+
}
|
|
580
|
+
/** 只清理 Core 发布器拥有的 sibling 命名空间,并以 fresh directory readback 证明终态。 */
|
|
581
|
+
async function cleanupPublishedPackageSiblings(targetPath) {
|
|
582
|
+
const parent = dirname(targetPath);
|
|
583
|
+
const prefix = `${basename(targetPath)}.`;
|
|
584
|
+
const entries = await readdir(parent, { withFileTypes: true });
|
|
585
|
+
const owned = entries
|
|
586
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith(prefix)
|
|
587
|
+
&& /\.(?:previous|staging)-[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(entry.name))
|
|
588
|
+
.map((entry) => join(parent, entry.name));
|
|
589
|
+
for (const path of owned)
|
|
590
|
+
await removePackagePublishDirectory(path, "INIT_BACKUP_CLEANUP_FAILED");
|
|
591
|
+
}
|
|
592
|
+
/** rm 返回后再次读取目录;清理失败不能被当作 package publish 成功。 */
|
|
593
|
+
async function removePackagePublishDirectory(path, code) {
|
|
594
|
+
try {
|
|
595
|
+
await rm(path, { recursive: true, force: true });
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
throw new PackageManagerError(code, `Unity package publish cleanup failed: ${path}`, [
|
|
599
|
+
"Close file handles that own the generated package sibling, then submit init again."
|
|
600
|
+
]);
|
|
601
|
+
}
|
|
602
|
+
if (await directoryExists(path)) {
|
|
603
|
+
throw new PackageManagerError(code, `Unity package publish cleanup could not prove directory absence: ${path}`, [
|
|
604
|
+
"Inspect the generated package sibling before submitting init again."
|
|
605
|
+
]);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/** Windows 文件观察器可能短暂占用 package 目录;只重试明确的 transient rename 错误。 */
|
|
609
|
+
async function renamePackageDirectory(source, target, absoluteDeadlineMs, runtime) {
|
|
610
|
+
const renameDirectory = runtime.renameDirectory ?? rename;
|
|
611
|
+
let retryDelayMs = Math.max(1, runtime.publishRetryDelayMs ?? 25);
|
|
612
|
+
while (true) {
|
|
613
|
+
try {
|
|
614
|
+
await renameDirectory(source, target);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
if (!isTransientRenameError(error) || Date.now() + retryDelayMs >= absoluteDeadlineMs)
|
|
619
|
+
throw error;
|
|
620
|
+
await delay(retryDelayMs);
|
|
621
|
+
retryDelayMs = Math.min(250, retryDelayMs * 2);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/** 仅 Windows 常见的短时占用错误允许 retry;目标冲突和路径错误继续 fail closed。 */
|
|
626
|
+
function isTransientRenameError(error) {
|
|
627
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
628
|
+
return code === "EPERM" || code === "EBUSY" || code === "EACCES";
|
|
629
|
+
}
|
|
630
|
+
async function verifyCurrentPackageBridge(projectPath, code, nextActions) {
|
|
631
|
+
const paths = packagePaths(projectPath);
|
|
632
|
+
const targetPath = join(paths.projectPath, "Packages", bridgePackageName);
|
|
633
|
+
const installMetadataPath = join(paths.projectPath, "Packages", installMetadataFileName);
|
|
634
|
+
const fileManifestPath = join(targetPath, fileManifestFileName);
|
|
635
|
+
const { packageManifest, installMetadata } = await readInstalledPackageBridge(paths.projectPath, code, nextActions);
|
|
636
|
+
const projectHash = await hashProjectPath(paths.projectPath);
|
|
637
|
+
const sourcePath = packageSourcePath();
|
|
638
|
+
const sourceHash = await hashSourcePath(sourcePath);
|
|
639
|
+
if (installMetadata.packageVersion !== packageManifest.version || installMetadata.sourceHash !== sourceHash
|
|
640
|
+
|| installMetadata.projectHash !== projectHash || installMetadata.cliCompatibilityRange !== cliCompatibilityRange)
|
|
641
|
+
throw new PackageManagerError(code, `${bridgePackageName} install metadata does not match the current project and bundled source.`, nextActions);
|
|
642
|
+
const managedFiles = await readVerifiedManagedFileList(fileManifestPath, installMetadata.managedFilesSha256);
|
|
643
|
+
if (!await managedPackageFilesMatch(sourcePath, targetPath, fileManifestPath))
|
|
644
|
+
throw new PackageManagerError(code, `${bridgePackageName} managed file projection does not match the bundled source.`, nextActions);
|
|
645
|
+
return { paths, targetPath, installMetadataPath, fileManifestPath, packageManifest, installMetadata, projectHash, sourceHash, managedFiles };
|
|
646
|
+
}
|
|
647
|
+
export async function checkPackageBridge(projectPath) {
|
|
648
|
+
const verified = await verifyCurrentPackageBridge(projectPath, "INIT_CHECK_FAILED", ["Run unitycli init --project <path> to restore the bundled package projection."]);
|
|
649
|
+
const { paths, targetPath, installMetadataPath, fileManifestPath, packageManifest, installMetadata, projectHash, sourceHash } = verified;
|
|
650
|
+
return {
|
|
651
|
+
manifestPath: paths.manifestPath,
|
|
652
|
+
bridgePath: targetPath,
|
|
653
|
+
dependencyName: bridgePackageName,
|
|
654
|
+
packageVersion: packageManifest.version,
|
|
655
|
+
sourceHash,
|
|
656
|
+
projectHash,
|
|
657
|
+
installMode: "copy",
|
|
658
|
+
installTime: installMetadata.installTime,
|
|
659
|
+
cliCompatibilityRange,
|
|
660
|
+
installMetadataPath,
|
|
661
|
+
fileManifestPath
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
/** 卸载 init 复制的 Bridge 包;只删除托管的 com.alpsckr.unitycli copy-mode 安装。 */
|
|
665
|
+
export async function removePackageBridge(projectPath, options = {}) {
|
|
666
|
+
const resolvedProject = packagePaths(projectPath).projectPath;
|
|
667
|
+
return withProjectFileMutationLease(resolvedProject, "package-bridge-publish", () => removePackageBridgeLocked(resolvedProject, options));
|
|
668
|
+
}
|
|
669
|
+
async function removePackageBridgeLocked(projectPath, options = {}) {
|
|
670
|
+
const verificationActions = ["Run unitycli init --project <path> to restore a fully verified bundled package before removal."];
|
|
671
|
+
const verified = await verifyCurrentPackageBridge(projectPath, "BRIDGE_PACKAGE_REMOVE_UNVERIFIED", verificationActions);
|
|
672
|
+
const { paths, targetPath, installMetadataPath, fileManifestPath, packageManifest, installMetadata, managedFiles } = verified;
|
|
673
|
+
const plannedEffects = [
|
|
674
|
+
{
|
|
675
|
+
type: "package.bridge-remove",
|
|
676
|
+
package: bridgePackageName,
|
|
677
|
+
bridgePath: targetPath,
|
|
678
|
+
installMetadataPath,
|
|
679
|
+
managedFileCount: managedFiles.length
|
|
680
|
+
}
|
|
681
|
+
];
|
|
682
|
+
if (!options.dryRun) {
|
|
683
|
+
const fresh = await verifyCurrentPackageBridge(projectPath, "BRIDGE_PACKAGE_REMOVE_UNVERIFIED", verificationActions);
|
|
684
|
+
if (!sameInstallMetadata(installMetadata, fresh.installMetadata))
|
|
685
|
+
throw new PackageManagerError("BRIDGE_PACKAGE_REMOVE_UNVERIFIED", "Bridge ownership changed before removal; no files were deleted.");
|
|
686
|
+
await removeManagedFilesStrict(targetPath, fresh.managedFiles);
|
|
687
|
+
await removeEmptyManagedDirectories(targetPath, false);
|
|
688
|
+
await rm(fileManifestPath, { force: true });
|
|
689
|
+
await assertPathMissing(fileManifestPath, "BRIDGE_PACKAGE_REMOVE_READBACK_FAILED");
|
|
690
|
+
await rm(installMetadataPath, { force: true });
|
|
691
|
+
await assertPathMissing(installMetadataPath, "BRIDGE_PACKAGE_REMOVE_READBACK_FAILED");
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
projectPath: paths.projectPath,
|
|
695
|
+
manifestPath: paths.manifestPath,
|
|
696
|
+
bridgePath: targetPath,
|
|
697
|
+
dependencyName: bridgePackageName,
|
|
698
|
+
packageVersion: packageManifest.version,
|
|
699
|
+
installMetadataPath,
|
|
700
|
+
fileManifestPath,
|
|
701
|
+
managedFileCount: managedFiles.length,
|
|
702
|
+
dryRun: options.dryRun === true,
|
|
703
|
+
plannedEffects
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
/** 将本地 Unity package 源复制到项目 Packages/<packageName>,并备份原目录以支持 package.restore。 */
|
|
707
|
+
export async function deployPackageSource(projectPath, sourcePath, options = {}) {
|
|
708
|
+
const resolvedProject = packagePaths(projectPath).projectPath;
|
|
709
|
+
return options.dryRun ? deployPackageSourceLocked(resolvedProject, sourcePath, options) : withProjectFileMutationLease(resolvedProject, "package-deployment-mutation", () => deployPackageSourceLocked(resolvedProject, sourcePath, options));
|
|
710
|
+
}
|
|
711
|
+
async function deployPackageSourceLocked(projectPath, sourcePath, options) {
|
|
712
|
+
const paths = packagePaths(projectPath);
|
|
713
|
+
await readManifest(paths.manifestPath, false);
|
|
714
|
+
if (!options.dryRun)
|
|
715
|
+
await recoverPackageDeploymentTransaction(paths.projectPath);
|
|
716
|
+
const resolvedSource = resolve(sourcePath);
|
|
717
|
+
const sourceManifest = await readAnyPackageManifest(resolvedSource);
|
|
718
|
+
const packageName = options.packageName ?? sourceManifest.name;
|
|
719
|
+
if (!packageName) {
|
|
720
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_NAME_REQUIRED", "Package deploy requires a package name in source package.json or packageName input.", [
|
|
721
|
+
"Pass source as a Unity package folder containing package.json with name/version.",
|
|
722
|
+
"Or pass packageName explicitly when deploying a source folder for a known package."
|
|
723
|
+
]);
|
|
724
|
+
}
|
|
725
|
+
if (sourceManifest.name !== packageName) {
|
|
726
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_NAME_MISMATCH", `Source package.json name ${sourceManifest.name} does not match requested packageName ${packageName}.`, [
|
|
727
|
+
"Pass the matching packageName, or choose a source package folder with the expected package.json name."
|
|
728
|
+
]);
|
|
729
|
+
}
|
|
730
|
+
assertSafePackageDirectoryName(packageName);
|
|
731
|
+
assertDeploySourceOutsideProjectPackages(resolvedSource, paths.projectPath);
|
|
732
|
+
const sourceHash = await hashSourcePath(resolvedSource);
|
|
733
|
+
const targetPath = join(paths.projectPath, "Packages", packageName);
|
|
734
|
+
const metadataPath = deploymentMetadataPath(paths.projectPath);
|
|
735
|
+
const existingTarget = await directoryExists(targetPath);
|
|
736
|
+
const existingManifest = existingTarget ? await readAnyPackageManifest(targetPath) : undefined;
|
|
737
|
+
const existingTargetHash = existingTarget ? await hashSourcePath(targetPath) : undefined;
|
|
738
|
+
const currentRecord = (await readDeploymentMetadata(metadataPath))[packageName];
|
|
739
|
+
if (currentRecord !== undefined) {
|
|
740
|
+
assertDeploymentRecordSafe(paths.projectPath, packageName, currentRecord);
|
|
741
|
+
}
|
|
742
|
+
const derivedBackupPath = currentRecord
|
|
743
|
+
? currentRecord.backupPath
|
|
744
|
+
: (existingTarget ? deploymentBackupPath(paths.projectPath, packageName, new Date()) : undefined);
|
|
745
|
+
const backupPath = options.plannedBackupPath ?? derivedBackupPath;
|
|
746
|
+
const retainedBackupPackageVersion = backupPath
|
|
747
|
+
? (currentRecord ? currentRecord.backupPackageVersion : existingManifest?.version)
|
|
748
|
+
: undefined;
|
|
749
|
+
if (options.plannedBackupPath !== undefined) {
|
|
750
|
+
if (!existingTarget || !isCanonicalDeploymentBackupPath(paths.projectPath, packageName, options.plannedBackupPath)
|
|
751
|
+
|| (currentRecord !== undefined && !isSameOptionalResolvedPath(options.plannedBackupPath, currentRecord.backupPath))) {
|
|
752
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_PLAN_INVALID", "Package deploy backup path does not match the frozen durable file plan.", [
|
|
753
|
+
"Inspect the canonical request journal and package deployment metadata before retrying; do not redeploy blindly."
|
|
754
|
+
]);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const repeatedDeployment = currentRecord !== undefined;
|
|
758
|
+
const copiedFileCount = await countPackageFiles(resolvedSource);
|
|
759
|
+
const plannedEffects = [];
|
|
760
|
+
if (existingTarget && !repeatedDeployment) {
|
|
761
|
+
plannedEffects.push({ type: "package.deploy.backup", package: packageName, from: targetPath, to: backupPath, packageVersion: existingManifest?.version ?? null });
|
|
762
|
+
}
|
|
763
|
+
plannedEffects.push({ type: "package.deploy.copy", package: packageName, from: resolvedSource, to: targetPath, fileCount: copiedFileCount });
|
|
764
|
+
plannedEffects.push({ type: "asset.refresh", reason: "package.deploy" });
|
|
765
|
+
if (!options.dryRun) {
|
|
766
|
+
if (options.expectedPreState)
|
|
767
|
+
await assertPackageDeployPreState(paths.projectPath, resolvedSource, sourceHash, targetPath, metadataPath, backupPath, options.expectedPreState);
|
|
768
|
+
await mkdir(dirname(metadataPath), { recursive: true });
|
|
769
|
+
const transactionRoot = join(deploymentRootPath(paths.projectPath), "transactions", randomUUID());
|
|
770
|
+
const stagingPath = join(transactionRoot, "staging");
|
|
771
|
+
const rollbackPath = join(transactionRoot, "rollback");
|
|
772
|
+
const transaction = {
|
|
773
|
+
version: 1, operation: "deploy", phase: "prepared", packageName, targetPath, stagingPath, rollbackPath, backupPath,
|
|
774
|
+
targetExisted: existingTarget, backupCreated: existingTarget && !repeatedDeployment && !!backupPath,
|
|
775
|
+
deleteBackupOnCommit: false, oldRecord: currentRecord
|
|
776
|
+
};
|
|
777
|
+
await writeDeploymentTransaction(paths.projectPath, transaction);
|
|
778
|
+
try {
|
|
779
|
+
await mkdir(transactionRoot, { recursive: true });
|
|
780
|
+
await copyDirRecursive(resolvedSource, stagingPath, resolvedSource, []);
|
|
781
|
+
if (await hashSourcePath(resolvedSource) !== sourceHash || await hashSourcePath(stagingPath) !== sourceHash || await countPackageFiles(stagingPath) !== copiedFileCount)
|
|
782
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_SOURCE_CHANGED", "Package source or staging changed before publish.");
|
|
783
|
+
if (transaction.backupCreated && backupPath) {
|
|
784
|
+
if (await directoryExists(backupPath))
|
|
785
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_BACKUP_CONFLICT", "Planned package backup path already exists.");
|
|
786
|
+
await copyDirRecursive(targetPath, backupPath, targetPath, []);
|
|
787
|
+
if (await hashSourcePath(backupPath) !== existingTargetHash)
|
|
788
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_BACKUP_FAILED", "Package backup could not be proven before publish.");
|
|
789
|
+
}
|
|
790
|
+
if (existingTarget) {
|
|
791
|
+
await rename(targetPath, rollbackPath);
|
|
792
|
+
transaction.phase = "target_moved";
|
|
793
|
+
await writeDeploymentTransaction(paths.projectPath, transaction);
|
|
794
|
+
}
|
|
795
|
+
await rename(stagingPath, targetPath);
|
|
796
|
+
transaction.phase = "target_published";
|
|
797
|
+
await writeDeploymentTransaction(paths.projectPath, transaction);
|
|
798
|
+
const metadata = await readDeploymentMetadata(metadataPath);
|
|
799
|
+
metadata[packageName] = {
|
|
800
|
+
packageName, packageVersion: sourceManifest.version, sourcePath: resolvedSource, sourceHash, targetPath, backupPath,
|
|
801
|
+
backupPackageVersion: retainedBackupPackageVersion, deployedAt: new Date().toISOString()
|
|
802
|
+
};
|
|
803
|
+
await writeDeploymentMetadata(metadataPath, metadata);
|
|
804
|
+
transaction.phase = "metadata_published";
|
|
805
|
+
await writeDeploymentTransaction(paths.projectPath, transaction);
|
|
806
|
+
const observedManifest = await readAnyPackageManifest(targetPath);
|
|
807
|
+
const observedRecord = (await readDeploymentMetadata(metadataPath))[packageName];
|
|
808
|
+
const backupHash = transaction.backupCreated && backupPath ? await hashSourcePath(backupPath) : undefined;
|
|
809
|
+
if (observedManifest.name !== packageName || observedManifest.version !== sourceManifest.version
|
|
810
|
+
|| await hashSourcePath(targetPath) !== sourceHash || await countPackageFiles(targetPath) !== copiedFileCount
|
|
811
|
+
|| !observedRecord || observedRecord.targetPath !== targetPath || observedRecord.sourceHash !== sourceHash
|
|
812
|
+
|| (transaction.backupCreated && backupHash !== existingTargetHash))
|
|
813
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_READBACK_FAILED", `Deployed package did not retain the requested filesystem state: ${packageName}.`);
|
|
814
|
+
transaction.phase = "commit_proven";
|
|
815
|
+
await writeDeploymentTransaction(paths.projectPath, transaction);
|
|
816
|
+
await removePackagePublishDirectory(transactionRoot, "PACKAGE_DEPLOYMENT_CLEANUP_FAILED");
|
|
817
|
+
await clearDeploymentTransaction(paths.projectPath);
|
|
818
|
+
}
|
|
819
|
+
catch (error) {
|
|
820
|
+
await recoverPackageDeploymentTransaction(paths.projectPath);
|
|
821
|
+
throw error;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return {
|
|
825
|
+
projectPath: paths.projectPath,
|
|
826
|
+
manifestPath: paths.manifestPath,
|
|
827
|
+
packageName,
|
|
828
|
+
packageVersion: sourceManifest.version,
|
|
829
|
+
sourcePath: resolvedSource,
|
|
830
|
+
sourceHash,
|
|
831
|
+
targetPath,
|
|
832
|
+
backupPath,
|
|
833
|
+
backupPackageVersion: retainedBackupPackageVersion,
|
|
834
|
+
backupCreated: existingTarget && !repeatedDeployment,
|
|
835
|
+
repeatedDeployment,
|
|
836
|
+
copiedFileCount,
|
|
837
|
+
dryRun: options.dryRun === true,
|
|
838
|
+
plannedEffects,
|
|
839
|
+
metadataPath
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
/** effect barrier内重读完整业务pre-state;任何target/record/backup漂移都在marker与rename前失败。 */
|
|
843
|
+
async function assertPackageDeployPreState(projectPath, sourcePath, sourceHash, targetPath, metadataPath, backupPath, expected) {
|
|
844
|
+
if (!isSameResolvedPath(sourcePath, expected.sourcePath) || sourceHash !== expected.sourceHash
|
|
845
|
+
|| !isSameOptionalResolvedPath(backupPath, expected.backupPath))
|
|
846
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_PRESTATE_CHANGED", "Package deploy source or planned backup identity changed after prepare.");
|
|
847
|
+
const targetExists = await directoryExists(targetPath);
|
|
848
|
+
const targetHash = targetExists ? await hashSourcePath(targetPath) : undefined;
|
|
849
|
+
const metadata = await readDeploymentMetadata(metadataPath);
|
|
850
|
+
const recordIdentity = deploymentRecordIdentity(metadata[basename(targetPath)]);
|
|
851
|
+
const backupExists = backupPath !== undefined ? await directoryExists(backupPath) : false;
|
|
852
|
+
const backupHash = backupExists && backupPath ? await hashSourcePath(backupPath) : undefined;
|
|
853
|
+
if (await hashSourcePath(sourcePath) !== expected.sourceHash || targetExists !== expected.targetExists
|
|
854
|
+
|| targetHash !== expected.targetHash || recordIdentity !== expected.recordIdentity
|
|
855
|
+
|| backupExists !== expected.backupExists || backupHash !== expected.backupHash)
|
|
856
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_PRESTATE_CHANGED", "Package deploy filesystem pre-state changed after prepare; no transaction marker or target mutation was created.");
|
|
857
|
+
if (backupPath !== undefined && !isCanonicalDeploymentBackupPath(projectPath, basename(targetPath), backupPath))
|
|
858
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_PRESTATE_CHANGED", "Package deploy backup path is not canonical for the frozen package.");
|
|
859
|
+
}
|
|
860
|
+
/** 从 package.deploy 创建的备份恢复目标包;没有备份时移除本次部署的包目录。 */
|
|
861
|
+
export async function restoreDeployedPackage(projectPath, packageName, options = {}) {
|
|
862
|
+
const resolvedProject = packagePaths(projectPath).projectPath;
|
|
863
|
+
return options.dryRun ? restoreDeployedPackageLocked(resolvedProject, packageName, options) : withProjectFileMutationLease(resolvedProject, "package-deployment-mutation", () => restoreDeployedPackageLocked(resolvedProject, packageName, options));
|
|
864
|
+
}
|
|
865
|
+
async function restoreDeployedPackageLocked(projectPath, packageName, options) {
|
|
866
|
+
const paths = packagePaths(projectPath);
|
|
867
|
+
await readManifest(paths.manifestPath, false);
|
|
868
|
+
if (!options.dryRun)
|
|
869
|
+
await recoverPackageDeploymentTransaction(paths.projectPath);
|
|
870
|
+
if (!packageName) {
|
|
871
|
+
throw new PackageManagerError("PACKAGE_NAME_REQUIRED", "Pass packageName to restore a deployed package.", [
|
|
872
|
+
"Run tools run package.restore --input '{\"packageName\":\"<package>\"}' --dry-run first to inspect planned effects."
|
|
873
|
+
]);
|
|
874
|
+
}
|
|
875
|
+
assertSafePackageDirectoryName(packageName);
|
|
876
|
+
const metadataPath = deploymentMetadataPath(paths.projectPath);
|
|
877
|
+
const metadata = await readDeploymentMetadata(metadataPath);
|
|
878
|
+
const record = metadata[packageName];
|
|
879
|
+
if (!record) {
|
|
880
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_NOT_FOUND", `No package deployment metadata found for ${packageName}.`, [
|
|
881
|
+
"Run package.deploy first, or pass the packageName that was deployed.",
|
|
882
|
+
"Check Packages/.unitycli_deploy_backups/unitycli.package-deployments.json in the target project."
|
|
883
|
+
]);
|
|
884
|
+
}
|
|
885
|
+
assertDeploymentRecordSafe(paths.projectPath, packageName, record);
|
|
886
|
+
const hasDeclaredBackup = record.backupPath !== undefined;
|
|
887
|
+
const backupExists = hasDeclaredBackup ? await directoryExists(record.backupPath) : false;
|
|
888
|
+
if (hasDeclaredBackup && !backupExists)
|
|
889
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_BACKUP_MISSING", `Declared package backup is missing or is not a directory: ${record.backupPath}`);
|
|
890
|
+
const backupFileCount = backupExists ? await countPackageFiles(record.backupPath) : 0;
|
|
891
|
+
const backupHash = backupExists ? await hashSourcePath(record.backupPath) : undefined;
|
|
892
|
+
const plannedEffects = [];
|
|
893
|
+
if (backupExists && record.backupPath) {
|
|
894
|
+
plannedEffects.push({ type: "package.restore.backup", package: packageName, from: record.backupPath, to: record.targetPath, fileCount: backupFileCount });
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
plannedEffects.push({ type: "package.restore.remove-deployed", package: packageName, targetPath: record.targetPath });
|
|
898
|
+
}
|
|
899
|
+
plannedEffects.push({ type: "asset.refresh", reason: "package.restore" });
|
|
900
|
+
if (!options.dryRun) {
|
|
901
|
+
if (options.expectedPreState)
|
|
902
|
+
await assertPackageRestorePreState(packageName, record.targetPath, metadataPath, record.backupPath, options.expectedPreState);
|
|
903
|
+
const transactionRoot = join(deploymentRootPath(paths.projectPath), "transactions", randomUUID());
|
|
904
|
+
const stagingPath = join(transactionRoot, "staging");
|
|
905
|
+
const rollbackPath = join(transactionRoot, "rollback");
|
|
906
|
+
const targetExisted = await directoryExists(record.targetPath);
|
|
907
|
+
const transaction = {
|
|
908
|
+
version: 1, operation: "restore", phase: "prepared", packageName, targetPath: record.targetPath,
|
|
909
|
+
stagingPath, rollbackPath, backupPath: record.backupPath, targetExisted, backupCreated: false,
|
|
910
|
+
deleteBackupOnCommit: backupExists, oldRecord: record
|
|
911
|
+
};
|
|
912
|
+
await writeDeploymentTransaction(paths.projectPath, transaction);
|
|
913
|
+
try {
|
|
914
|
+
await mkdir(transactionRoot, { recursive: true });
|
|
915
|
+
if (backupExists && record.backupPath) {
|
|
916
|
+
await copyDirRecursive(record.backupPath, stagingPath, record.backupPath, []);
|
|
917
|
+
if (await hashSourcePath(stagingPath) !== backupHash || await countPackageFiles(stagingPath) !== backupFileCount)
|
|
918
|
+
throw new PackageManagerError("PACKAGE_RESTORE_STAGING_FAILED", "Package restore staging could not be proven before publish.");
|
|
919
|
+
}
|
|
920
|
+
if (targetExisted) {
|
|
921
|
+
await rename(record.targetPath, rollbackPath);
|
|
922
|
+
transaction.phase = "target_moved";
|
|
923
|
+
await writeDeploymentTransaction(paths.projectPath, transaction);
|
|
924
|
+
}
|
|
925
|
+
if (backupExists) {
|
|
926
|
+
await rename(stagingPath, record.targetPath);
|
|
927
|
+
transaction.phase = "target_published";
|
|
928
|
+
await writeDeploymentTransaction(paths.projectPath, transaction);
|
|
929
|
+
}
|
|
930
|
+
delete metadata[packageName];
|
|
931
|
+
await writeDeploymentMetadata(metadataPath, metadata);
|
|
932
|
+
transaction.phase = "metadata_published";
|
|
933
|
+
await writeDeploymentTransaction(paths.projectPath, transaction);
|
|
934
|
+
const observedMetadata = await readDeploymentMetadata(metadataPath);
|
|
935
|
+
const observedTargetExists = await directoryExists(record.targetPath);
|
|
936
|
+
if (observedMetadata[packageName]
|
|
937
|
+
|| (backupExists && (!observedTargetExists || await hashSourcePath(record.targetPath) !== backupHash || await countPackageFiles(record.targetPath) !== backupFileCount))
|
|
938
|
+
|| (!backupExists && observedTargetExists))
|
|
939
|
+
throw new PackageManagerError("PACKAGE_RESTORE_READBACK_FAILED", `Restored package did not retain the requested filesystem state: ${packageName}.`);
|
|
940
|
+
transaction.phase = "commit_proven";
|
|
941
|
+
await writeDeploymentTransaction(paths.projectPath, transaction);
|
|
942
|
+
if (backupExists && record.backupPath)
|
|
943
|
+
await removePackagePublishDirectory(record.backupPath, "PACKAGE_DEPLOYMENT_CLEANUP_FAILED");
|
|
944
|
+
await removePackagePublishDirectory(transactionRoot, "PACKAGE_DEPLOYMENT_CLEANUP_FAILED");
|
|
945
|
+
await clearDeploymentTransaction(paths.projectPath);
|
|
946
|
+
}
|
|
947
|
+
catch (error) {
|
|
948
|
+
await recoverPackageDeploymentTransaction(paths.projectPath);
|
|
949
|
+
throw error;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return {
|
|
953
|
+
projectPath: paths.projectPath,
|
|
954
|
+
manifestPath: paths.manifestPath,
|
|
955
|
+
packageName,
|
|
956
|
+
targetPath: record.targetPath,
|
|
957
|
+
backupPath: record.backupPath,
|
|
958
|
+
restoredFromBackup: backupExists,
|
|
959
|
+
removedDeployedPackage: !backupExists,
|
|
960
|
+
copiedFileCount: backupFileCount,
|
|
961
|
+
dryRun: options.dryRun === true,
|
|
962
|
+
plannedEffects,
|
|
963
|
+
metadataPath
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
/** restore effect前重读target、record与backup;漂移时不得创建marker或移动target。 */
|
|
967
|
+
async function assertPackageRestorePreState(packageName, targetPath, metadataPath, backupPath, expected) {
|
|
968
|
+
if (!isSameResolvedPath(targetPath, expected.targetPath) || !isSameOptionalResolvedPath(backupPath, expected.backupPath))
|
|
969
|
+
throw new PackageManagerError("PACKAGE_RESTORE_PRESTATE_CHANGED", "Package restore target or backup identity changed after prepare.");
|
|
970
|
+
const targetExists = await directoryExists(targetPath);
|
|
971
|
+
const targetHash = targetExists ? await hashSourcePath(targetPath) : undefined;
|
|
972
|
+
const metadata = await readDeploymentMetadata(metadataPath);
|
|
973
|
+
const backupExists = backupPath !== undefined ? await directoryExists(backupPath) : false;
|
|
974
|
+
const backupHash = backupExists && backupPath ? await hashSourcePath(backupPath) : undefined;
|
|
975
|
+
if (targetExists !== expected.targetExists || targetHash !== expected.targetHash
|
|
976
|
+
|| deploymentRecordIdentity(metadata[packageName]) !== expected.recordIdentity
|
|
977
|
+
|| backupExists !== expected.backupExists || backupHash !== expected.backupHash)
|
|
978
|
+
throw new PackageManagerError("PACKAGE_RESTORE_PRESTATE_CHANGED", "Package restore filesystem pre-state changed after prepare; no transaction marker or target mutation was created.");
|
|
979
|
+
}
|
|
980
|
+
export function parsePackageSpec(raw, explicitVersion) {
|
|
981
|
+
const atIndex = raw.lastIndexOf("@");
|
|
982
|
+
if (atIndex > 0) {
|
|
983
|
+
return { name: raw.slice(0, atIndex), version: explicitVersion ?? raw.slice(atIndex + 1) };
|
|
984
|
+
}
|
|
985
|
+
return { name: raw, version: explicitVersion };
|
|
986
|
+
}
|
|
987
|
+
export function validatePackageName(name) {
|
|
988
|
+
if (!name) {
|
|
989
|
+
throw new PackageManagerError("PACKAGE_NAME_REQUIRED", "Pass a package name.");
|
|
990
|
+
}
|
|
991
|
+
return name;
|
|
992
|
+
}
|
|
993
|
+
export function validateRegistryUrl(url) {
|
|
994
|
+
if (!url) {
|
|
995
|
+
throw new PackageManagerError("REGISTRY_URL_REQUIRED", "Pass --url <registryUrl>.", [
|
|
996
|
+
"Pass an http(s) registry URL, for example https://package.openupm.com."
|
|
997
|
+
]);
|
|
998
|
+
}
|
|
999
|
+
try {
|
|
1000
|
+
const parsed = new URL(url);
|
|
1001
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
1002
|
+
throw new Error("Unsupported protocol");
|
|
1003
|
+
}
|
|
1004
|
+
return url;
|
|
1005
|
+
}
|
|
1006
|
+
catch {
|
|
1007
|
+
throw new PackageManagerError("REGISTRY_URL_INVALID", `Registry URL is invalid: ${url}`, [
|
|
1008
|
+
"Use an http(s) registry URL, for example https://package.openupm.com.",
|
|
1009
|
+
"Run unitycli tools describe package.search or package.registry.set for accepted registry inputs."
|
|
1010
|
+
]);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
export function parseScopes(raw) {
|
|
1014
|
+
const scopes = (raw ?? "").split(",").map((scope) => scope.trim()).filter(Boolean);
|
|
1015
|
+
if (scopes.length === 0) {
|
|
1016
|
+
throw new PackageManagerError("SCOPES_REQUIRED", "Pass --scope <scope> or comma-separated scopes.");
|
|
1017
|
+
}
|
|
1018
|
+
return uniqueScopes(scopes);
|
|
1019
|
+
}
|
|
1020
|
+
export async function searchRegistry(registry, query) {
|
|
1021
|
+
const endpoint = `${registry.replace(/\/$/, "")}/-/v1/search?text=${encodeURIComponent(query)}`;
|
|
1022
|
+
let response;
|
|
1023
|
+
try {
|
|
1024
|
+
response = await fetch(endpoint);
|
|
1025
|
+
}
|
|
1026
|
+
catch (error) {
|
|
1027
|
+
throw new PackageManagerError("REGISTRY_UNREACHABLE", error instanceof Error ? error.message : String(error), ["Check the registry URL and network connectivity."]);
|
|
1028
|
+
}
|
|
1029
|
+
if (!response.ok) {
|
|
1030
|
+
throw new PackageManagerError("REGISTRY_UNREACHABLE", `Registry returned HTTP ${response.status}.`, ["Check the registry URL and credentials."]);
|
|
1031
|
+
}
|
|
1032
|
+
const payload = (await response.json());
|
|
1033
|
+
return (payload.objects ?? []).flatMap((entry) => {
|
|
1034
|
+
const candidate = entry.package;
|
|
1035
|
+
if (typeof candidate?.name !== "string")
|
|
1036
|
+
return [];
|
|
1037
|
+
return [{
|
|
1038
|
+
name: candidate.name,
|
|
1039
|
+
...(typeof candidate.version === "string" ? { version: candidate.version } : {}),
|
|
1040
|
+
...(typeof candidate.description === "string" ? { description: candidate.description } : {}),
|
|
1041
|
+
}];
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
export class PackageManagerError extends Error {
|
|
1045
|
+
code;
|
|
1046
|
+
nextActions;
|
|
1047
|
+
constructor(code, message, nextActions = []) {
|
|
1048
|
+
super(message);
|
|
1049
|
+
this.code = code;
|
|
1050
|
+
this.nextActions = nextActions;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
function packageSourcePath() {
|
|
1054
|
+
return resolve(fileURLToPath(new URL("../unitypkg", import.meta.url)));
|
|
1055
|
+
}
|
|
1056
|
+
/** 读取并确认项目内 Bridge 包是 init 托管的 copy-mode 安装。 */
|
|
1057
|
+
async function readInstalledPackageBridge(projectPath, code, nextActions) {
|
|
1058
|
+
const targetPath = join(projectPath, "Packages", bridgePackageName);
|
|
1059
|
+
const installMetadataPath = join(projectPath, "Packages", installMetadataFileName);
|
|
1060
|
+
let packageManifest;
|
|
1061
|
+
try {
|
|
1062
|
+
packageManifest = await readPackageManifest(targetPath);
|
|
1063
|
+
}
|
|
1064
|
+
catch (error) {
|
|
1065
|
+
if (error instanceof PackageManagerError) {
|
|
1066
|
+
throw new PackageManagerError(code, `${bridgePackageName} is not installed in copy mode: ${error.message}`, nextActions);
|
|
1067
|
+
}
|
|
1068
|
+
throw error;
|
|
1069
|
+
}
|
|
1070
|
+
const installMetadata = await readInstallMetadata(installMetadataPath).catch((error) => {
|
|
1071
|
+
if (error instanceof PackageManagerError) {
|
|
1072
|
+
throw new PackageManagerError(code, `${bridgePackageName} install metadata is missing or invalid: ${error.message}`, nextActions);
|
|
1073
|
+
}
|
|
1074
|
+
throw error;
|
|
1075
|
+
});
|
|
1076
|
+
if (installMetadata.packageName !== bridgePackageName || installMetadata.installMode !== "copy") {
|
|
1077
|
+
throw new PackageManagerError(code, `${bridgePackageName} is not a managed copy-mode installation.`, nextActions);
|
|
1078
|
+
}
|
|
1079
|
+
return { packageManifest, installMetadata };
|
|
1080
|
+
}
|
|
1081
|
+
async function readPackageManifest(packagePath) {
|
|
1082
|
+
const manifest = await readAnyPackageManifest(packagePath);
|
|
1083
|
+
if (manifest.name !== bridgePackageName) {
|
|
1084
|
+
throw new PackageManagerError("PACKAGE_IDENTITY_INVALID", `Unity package name must be ${bridgePackageName}.`);
|
|
1085
|
+
}
|
|
1086
|
+
return manifest;
|
|
1087
|
+
}
|
|
1088
|
+
async function readAnyPackageManifest(packagePath) {
|
|
1089
|
+
try {
|
|
1090
|
+
const manifest = JSON.parse(stripJsonBom(await readFile(join(packagePath, "package.json"), "utf8")));
|
|
1091
|
+
if (typeof manifest.name !== "string" || !manifest.name) {
|
|
1092
|
+
throw new PackageManagerError("PACKAGE_IDENTITY_INVALID", `Unity package.json requires a non-empty name: ${join(packagePath, "package.json")}`);
|
|
1093
|
+
}
|
|
1094
|
+
if (typeof manifest.version !== "string" || !manifest.version) {
|
|
1095
|
+
throw new PackageManagerError("PACKAGE_VERSION_INVALID", `${manifest.name} package.json requires a version.`);
|
|
1096
|
+
}
|
|
1097
|
+
return { name: manifest.name, version: manifest.version };
|
|
1098
|
+
}
|
|
1099
|
+
catch (error) {
|
|
1100
|
+
const nodeError = error;
|
|
1101
|
+
if (nodeError.code === "ENOENT") {
|
|
1102
|
+
throw new PackageManagerError("PACKAGE_MANIFEST_NOT_FOUND", `Unity package manifest not found under: ${packagePath}`);
|
|
1103
|
+
}
|
|
1104
|
+
if (error instanceof SyntaxError) {
|
|
1105
|
+
throw new PackageManagerError("PACKAGE_MANIFEST_INVALID", `Unity package manifest is invalid JSON: ${join(packagePath, "package.json")}`);
|
|
1106
|
+
}
|
|
1107
|
+
throw error;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
export function fileDependencySpec(projectPath, packagePath) {
|
|
1111
|
+
const path = pathHelpersFor(projectPath, packagePath);
|
|
1112
|
+
const dependencyPath = normalizeRelativePath(path.relative(path.join(projectPath, "Packages"), packagePath));
|
|
1113
|
+
if (isAbsoluteDependencyPath(dependencyPath)) {
|
|
1114
|
+
return `file:${dependencyPath}`;
|
|
1115
|
+
}
|
|
1116
|
+
const normalized = dependencyPath.startsWith(".") ? dependencyPath : `./${dependencyPath}`;
|
|
1117
|
+
return `file:${normalized}`;
|
|
1118
|
+
}
|
|
1119
|
+
export function resolveFileDependency(projectPath, spec) {
|
|
1120
|
+
const dependencyPath = spec.slice("file:".length);
|
|
1121
|
+
const path = pathHelpersFor(projectPath, dependencyPath);
|
|
1122
|
+
return path.resolve(path.join(projectPath, "Packages"), dependencyPath);
|
|
1123
|
+
}
|
|
1124
|
+
async function hashProjectPath(projectPath) {
|
|
1125
|
+
const canonical = await realpath(projectPath).catch(() => resolve(projectPath));
|
|
1126
|
+
return createHash("sha256").update(canonical.toLowerCase()).digest("hex");
|
|
1127
|
+
}
|
|
1128
|
+
async function writeInstallMetadata(path, metadata) {
|
|
1129
|
+
await atomicWriteFile(path, `${JSON.stringify(metadata, null, 2)}\n`);
|
|
1130
|
+
}
|
|
1131
|
+
async function readExistingInstallMetadata(path) {
|
|
1132
|
+
return readInstallMetadata(path);
|
|
1133
|
+
}
|
|
1134
|
+
async function readOptionalInstallMetadata(path) {
|
|
1135
|
+
try {
|
|
1136
|
+
await stat(path);
|
|
1137
|
+
}
|
|
1138
|
+
catch (error) {
|
|
1139
|
+
const code = error?.code;
|
|
1140
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
1141
|
+
return undefined;
|
|
1142
|
+
throw new PackageManagerError("INIT_CHECK_FAILED", `Install metadata cannot be observed: ${path}`);
|
|
1143
|
+
}
|
|
1144
|
+
return readInstallMetadata(path);
|
|
1145
|
+
}
|
|
1146
|
+
function sameInstallMetadata(left, right) {
|
|
1147
|
+
return left.packageName === right.packageName && left.packageVersion === right.packageVersion
|
|
1148
|
+
&& left.sourceHash === right.sourceHash && left.managedFilesSha256 === right.managedFilesSha256
|
|
1149
|
+
&& left.projectHash === right.projectHash && left.installMode === right.installMode
|
|
1150
|
+
&& left.installTime === right.installTime && left.cliCompatibilityRange === right.cliCompatibilityRange;
|
|
1151
|
+
}
|
|
1152
|
+
async function readInstallMetadata(path) {
|
|
1153
|
+
try {
|
|
1154
|
+
const metadata = JSON.parse(stripJsonBom(await readFile(path, "utf8")));
|
|
1155
|
+
if (typeof metadata.packageName !== "string" || typeof metadata.packageVersion !== "string" || !isSha256(metadata.sourceHash) || !isSha256(metadata.managedFilesSha256) || typeof metadata.projectHash !== "string" || metadata.installMode !== "copy" || typeof metadata.installTime !== "string" || typeof metadata.cliCompatibilityRange !== "string") {
|
|
1156
|
+
throw new PackageManagerError("INIT_CHECK_FAILED", `Install metadata is invalid: ${path}`, ["Run unitycli init --project <path> to refresh install metadata."]);
|
|
1157
|
+
}
|
|
1158
|
+
return metadata;
|
|
1159
|
+
}
|
|
1160
|
+
catch (error) {
|
|
1161
|
+
if (error instanceof PackageManagerError) {
|
|
1162
|
+
throw error;
|
|
1163
|
+
}
|
|
1164
|
+
const nodeError = error;
|
|
1165
|
+
if (nodeError.code === "ENOENT") {
|
|
1166
|
+
throw new PackageManagerError("INIT_CHECK_FAILED", `Install metadata not found: ${path}`, ["Run unitycli init --project <path> to create install metadata."]);
|
|
1167
|
+
}
|
|
1168
|
+
if (error instanceof SyntaxError) {
|
|
1169
|
+
throw new PackageManagerError("INIT_CHECK_FAILED", `Install metadata is invalid JSON: ${path}`, ["Run unitycli init --project <path> to refresh install metadata."]);
|
|
1170
|
+
}
|
|
1171
|
+
throw error;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
async function copyPackageContents(sourcePath, targetPath) {
|
|
1175
|
+
const files = [];
|
|
1176
|
+
await copyDirRecursive(sourcePath, targetPath, sourcePath, files);
|
|
1177
|
+
return files;
|
|
1178
|
+
}
|
|
1179
|
+
async function copyDirRecursive(dir, targetRoot, sourceRoot, files) {
|
|
1180
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1181
|
+
for (const entry of entries) {
|
|
1182
|
+
const srcPath = join(dir, entry.name);
|
|
1183
|
+
const relPath = posix.normalize(relative(sourceRoot, srcPath).split("\\").join("/"));
|
|
1184
|
+
const destPath = join(targetRoot, relPath);
|
|
1185
|
+
if (entry.isDirectory()) {
|
|
1186
|
+
await mkdir(destPath, { recursive: true });
|
|
1187
|
+
await copyDirRecursive(srcPath, targetRoot, sourceRoot, files);
|
|
1188
|
+
}
|
|
1189
|
+
else if (entry.isFile()) {
|
|
1190
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
1191
|
+
await copyFile(srcPath, destPath);
|
|
1192
|
+
files.push(relPath);
|
|
1193
|
+
}
|
|
1194
|
+
// Symlinks are skipped
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
export async function hashPackageTree(sourcePath) {
|
|
1198
|
+
return hashSourcePath(sourcePath);
|
|
1199
|
+
}
|
|
1200
|
+
/** 仅重算 package.deploy/restore 的文件与 metadata 终态,供单 root redelivery 恢复。 */
|
|
1201
|
+
export async function inspectPackageDeploymentFiles(projectPath, packageName, targetPath, backupPath) {
|
|
1202
|
+
const paths = packagePaths(projectPath);
|
|
1203
|
+
assertSafePackageDirectoryName(packageName);
|
|
1204
|
+
const expectedTarget = join(paths.projectPath, "Packages", packageName);
|
|
1205
|
+
if (resolve(targetPath) !== resolve(expectedTarget))
|
|
1206
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_READBACK_FAILED", "Package target path does not match the project package root.");
|
|
1207
|
+
if (backupPath) {
|
|
1208
|
+
const backupRoot = `${resolve(deploymentRootPath(paths.projectPath))}${win32.sep}`.toLowerCase();
|
|
1209
|
+
if (!`${resolve(backupPath)}${win32.sep}`.toLowerCase().startsWith(backupRoot))
|
|
1210
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_READBACK_FAILED", "Package backup path is outside the deployment backup root.");
|
|
1211
|
+
}
|
|
1212
|
+
const metadataPath = deploymentMetadataPath(paths.projectPath);
|
|
1213
|
+
const metadata = await readDeploymentMetadata(metadataPath);
|
|
1214
|
+
const targetExists = await directoryExists(targetPath);
|
|
1215
|
+
const backupExists = backupPath ? await directoryExists(backupPath) : false;
|
|
1216
|
+
return {
|
|
1217
|
+
targetExists,
|
|
1218
|
+
targetHash: targetExists ? await hashSourcePath(targetPath) : undefined,
|
|
1219
|
+
targetFileCount: targetExists ? await countPackageFiles(targetPath) : 0,
|
|
1220
|
+
backupExists,
|
|
1221
|
+
backupHash: backupExists && backupPath ? await hashSourcePath(backupPath) : undefined,
|
|
1222
|
+
metadataPath,
|
|
1223
|
+
metadataHash: await optionalFileIdentity(metadataPath),
|
|
1224
|
+
record: metadata[packageName],
|
|
1225
|
+
recordIdentity: deploymentRecordIdentity(metadata[packageName])
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
async function optionalFileIdentity(path) {
|
|
1229
|
+
try {
|
|
1230
|
+
return createHash("sha256").update(await readFile(path)).digest("hex");
|
|
1231
|
+
}
|
|
1232
|
+
catch (error) {
|
|
1233
|
+
const code = error?.code;
|
|
1234
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
1235
|
+
return createHash("sha256").update("absent").digest("hex");
|
|
1236
|
+
throw error;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
function deploymentRecordIdentity(record) {
|
|
1240
|
+
if (!record)
|
|
1241
|
+
return createHash("sha256").update("absent").digest("hex");
|
|
1242
|
+
return createHash("sha256").update(JSON.stringify({
|
|
1243
|
+
packageName: record.packageName, packageVersion: record.packageVersion, sourcePath: record.sourcePath,
|
|
1244
|
+
sourceHash: record.sourceHash, targetPath: record.targetPath, backupPath: record.backupPath ?? null,
|
|
1245
|
+
backupPackageVersion: record.backupPackageVersion ?? null, deployedAt: record.deployedAt
|
|
1246
|
+
})).digest("hex");
|
|
1247
|
+
}
|
|
1248
|
+
async function hashSourcePath(sourcePath) {
|
|
1249
|
+
const hash = createHash("sha256");
|
|
1250
|
+
await hashDirectory(hash, sourcePath, sourcePath);
|
|
1251
|
+
return hash.digest("hex");
|
|
1252
|
+
}
|
|
1253
|
+
async function hashDirectory(hash, dir, root) {
|
|
1254
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1255
|
+
// 与 C# StringComparer.Ordinal 一致,禁止 locale 改变 package tree proof。
|
|
1256
|
+
const sorted = entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
1257
|
+
for (const entry of sorted) {
|
|
1258
|
+
const fullPath = join(dir, entry.name);
|
|
1259
|
+
if (entry.isDirectory()) {
|
|
1260
|
+
hash.update(`dir:${relative(root, fullPath).split("\\").join("/")}/\n`);
|
|
1261
|
+
await hashDirectory(hash, fullPath, root);
|
|
1262
|
+
}
|
|
1263
|
+
else if (entry.isFile()) {
|
|
1264
|
+
const relPath = relative(root, fullPath).split("\\").join("/");
|
|
1265
|
+
hash.update(`file:${relPath}\n`);
|
|
1266
|
+
const content = await readFile(fullPath);
|
|
1267
|
+
hash.update(content);
|
|
1268
|
+
hash.update("\n");
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
async function removeStaleManagedFiles(targetPath, managedFiles) {
|
|
1273
|
+
await removeManagedFilesStrict(targetPath, managedFiles);
|
|
1274
|
+
}
|
|
1275
|
+
async function removeManagedFilesStrict(targetPath, managedFiles) {
|
|
1276
|
+
for (const relPath of managedFiles) {
|
|
1277
|
+
const fullPath = resolveManagedFilePath(targetPath, relPath);
|
|
1278
|
+
try {
|
|
1279
|
+
await rm(fullPath, { force: true });
|
|
1280
|
+
await assertPathMissing(fullPath, "PACKAGE_MANAGED_FILE_DELETE_FAILED");
|
|
1281
|
+
}
|
|
1282
|
+
catch (error) {
|
|
1283
|
+
if (error instanceof PackageManagerError)
|
|
1284
|
+
throw error;
|
|
1285
|
+
throw new PackageManagerError("PACKAGE_MANAGED_FILE_DELETE_FAILED", `Managed package file could not be removed: ${relPath}`);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
async function assertRemovedManagedFilesAbsent(targetPath, oldFiles, newFiles) {
|
|
1290
|
+
const current = new Set(newFiles.map((file) => file.toLowerCase()));
|
|
1291
|
+
for (const relPath of oldFiles) {
|
|
1292
|
+
const identity = relPath.toLowerCase();
|
|
1293
|
+
if (current.has(identity) || [...current].some((file) => file.startsWith(`${identity}/`)))
|
|
1294
|
+
continue;
|
|
1295
|
+
await assertPathMissing(resolveManagedFilePath(targetPath, relPath), "PACKAGE_MANAGED_FILE_DELETE_FAILED");
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
async function removeEmptyManagedDirectories(path, removeSelf) {
|
|
1299
|
+
for (const entry of await readdir(path, { withFileTypes: true })) {
|
|
1300
|
+
if (entry.isDirectory())
|
|
1301
|
+
await removeEmptyManagedDirectories(join(path, entry.name), true);
|
|
1302
|
+
}
|
|
1303
|
+
if (!removeSelf || (await readdir(path)).length !== 0)
|
|
1304
|
+
return;
|
|
1305
|
+
await rmdir(path);
|
|
1306
|
+
await assertPathMissing(path, "BRIDGE_PACKAGE_REMOVE_READBACK_FAILED");
|
|
1307
|
+
}
|
|
1308
|
+
async function assertPathMissing(path, code) {
|
|
1309
|
+
try {
|
|
1310
|
+
await stat(path);
|
|
1311
|
+
throw new PackageManagerError(code, `Path still exists after managed removal: ${path}`);
|
|
1312
|
+
}
|
|
1313
|
+
catch (error) {
|
|
1314
|
+
if (error?.code === "ENOENT")
|
|
1315
|
+
return;
|
|
1316
|
+
throw error;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
async function readVerifiedManagedFileList(fileManifestPath, expectedSha256) {
|
|
1320
|
+
const files = await readManagedFileList(fileManifestPath);
|
|
1321
|
+
if (managedFilesSha256(files) !== expectedSha256)
|
|
1322
|
+
throw new PackageManagerError("PACKAGE_FILE_MANIFEST_INVALID", `Managed file manifest does not match its install metadata: ${fileManifestPath}`);
|
|
1323
|
+
return files;
|
|
1324
|
+
}
|
|
1325
|
+
function managedFilesSha256(files) {
|
|
1326
|
+
return createHash("sha256").update(`unity-cli-managed-files-v1\0${[...files].sort().join("\n")}`, "utf8").digest("hex");
|
|
1327
|
+
}
|
|
1328
|
+
function isSha256(value) {
|
|
1329
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
1330
|
+
}
|
|
1331
|
+
async function readManagedFileList(fileManifestPath) {
|
|
1332
|
+
try {
|
|
1333
|
+
const data = JSON.parse(stripJsonBom(await readFile(fileManifestPath, "utf8")));
|
|
1334
|
+
if (!data || data.version !== 1 || !Array.isArray(data.files) || data.files.length === 0)
|
|
1335
|
+
throw new PackageManagerError("PACKAGE_FILE_MANIFEST_INVALID", `Managed file manifest is invalid: ${fileManifestPath}`);
|
|
1336
|
+
const identities = new Set();
|
|
1337
|
+
for (const entry of data.files) {
|
|
1338
|
+
assertManagedRelativeFilePath(entry, fileManifestPath);
|
|
1339
|
+
const identity = entry.toLowerCase();
|
|
1340
|
+
if (identities.has(identity))
|
|
1341
|
+
throw new PackageManagerError("PACKAGE_FILE_MANIFEST_INVALID", `Managed file manifest contains a duplicate path: ${fileManifestPath}`);
|
|
1342
|
+
identities.add(identity);
|
|
1343
|
+
}
|
|
1344
|
+
return [...data.files];
|
|
1345
|
+
}
|
|
1346
|
+
catch (error) {
|
|
1347
|
+
if (error instanceof PackageManagerError)
|
|
1348
|
+
throw error;
|
|
1349
|
+
throw new PackageManagerError("PACKAGE_FILE_MANIFEST_INVALID", `Managed file manifest cannot be read: ${fileManifestPath}`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
function assertManagedRelativeFilePath(value, source) {
|
|
1353
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("\0") || value.includes("\\") || value !== posix.normalize(value)
|
|
1354
|
+
|| isAbsolute(value) || win32.isAbsolute(value) || posix.isAbsolute(value)
|
|
1355
|
+
|| value.includes(":") || value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
1356
|
+
throw new PackageManagerError("PACKAGE_FILE_MANIFEST_INVALID", `Managed file manifest contains an unsafe path: ${source}`);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
function resolveManagedFilePath(targetPath, relPath) {
|
|
1360
|
+
assertManagedRelativeFilePath(relPath, targetPath);
|
|
1361
|
+
const root = resolve(targetPath);
|
|
1362
|
+
const fullPath = resolve(root, relPath);
|
|
1363
|
+
const fromRoot = relative(root, fullPath);
|
|
1364
|
+
if (!fromRoot || fromRoot.split(/[\\/]/)[0] === ".." || isAbsolute(fromRoot) || win32.isAbsolute(fromRoot)) {
|
|
1365
|
+
throw new PackageManagerError("PACKAGE_FILE_MANIFEST_INVALID", `Managed file path escapes its package root: ${relPath}`);
|
|
1366
|
+
}
|
|
1367
|
+
return fullPath;
|
|
1368
|
+
}
|
|
1369
|
+
/** 使用安装 manifest 中的受管路径逐一哈希,排除 Unity 生成的 .meta 等非投影文件。 */
|
|
1370
|
+
async function managedPackageFilesMatch(sourcePath, targetPath, fileManifestPath) {
|
|
1371
|
+
const files = await readManagedFileList(fileManifestPath);
|
|
1372
|
+
const expectedFiles = await listManagedSourceFiles(sourcePath);
|
|
1373
|
+
if (files.length !== expectedFiles.length || [...files].sort().some((file, index) => file !== expectedFiles[index]))
|
|
1374
|
+
return false;
|
|
1375
|
+
for (const file of files) {
|
|
1376
|
+
try {
|
|
1377
|
+
const [source, target] = await Promise.all([readFile(join(sourcePath, file)), readFile(join(targetPath, file))]);
|
|
1378
|
+
if (!source.equals(target))
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
catch {
|
|
1382
|
+
return false;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
return true;
|
|
1386
|
+
}
|
|
1387
|
+
async function listManagedSourceFiles(sourcePath) {
|
|
1388
|
+
const files = [];
|
|
1389
|
+
const visit = async (dir) => {
|
|
1390
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
1391
|
+
const fullPath = join(dir, entry.name);
|
|
1392
|
+
if (entry.isDirectory())
|
|
1393
|
+
await visit(fullPath);
|
|
1394
|
+
else if (entry.isFile())
|
|
1395
|
+
files.push(posix.normalize(relative(sourcePath, fullPath).split("\\").join("/")));
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
await visit(sourcePath);
|
|
1399
|
+
return files.sort();
|
|
1400
|
+
}
|
|
1401
|
+
async function writeFileManifest(path, files) {
|
|
1402
|
+
const manifest = { version: 1, files };
|
|
1403
|
+
await atomicWriteFile(path, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
1404
|
+
}
|
|
1405
|
+
function deploymentRootPath(projectPath) {
|
|
1406
|
+
return join(projectPath, "Packages", deploymentBackupDirName);
|
|
1407
|
+
}
|
|
1408
|
+
function deploymentMetadataPath(projectPath) {
|
|
1409
|
+
return join(deploymentRootPath(projectPath), deploymentMetadataFileName);
|
|
1410
|
+
}
|
|
1411
|
+
function deploymentTransactionPath(projectPath) {
|
|
1412
|
+
return join(deploymentRootPath(projectPath), "package-deployment-transaction.json");
|
|
1413
|
+
}
|
|
1414
|
+
async function readDeploymentTransaction(projectPath) {
|
|
1415
|
+
const path = deploymentTransactionPath(projectPath);
|
|
1416
|
+
try {
|
|
1417
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
1418
|
+
if (typeof value.packageName !== "string" || !value.packageName)
|
|
1419
|
+
throw new Error("invalid-package-name");
|
|
1420
|
+
assertSafePackageDirectoryName(value.packageName);
|
|
1421
|
+
const transactionsRoot = join(deploymentRootPath(projectPath), "transactions");
|
|
1422
|
+
const transactionRoot = dirname(value.stagingPath);
|
|
1423
|
+
const transactionId = basename(transactionRoot);
|
|
1424
|
+
const expectedTarget = join(resolve(projectPath), "Packages", value.packageName);
|
|
1425
|
+
if (value.version !== 1 || !["deploy", "restore"].includes(value.operation)
|
|
1426
|
+
|| !["prepared", "target_moved", "target_published", "metadata_published", "commit_proven"].includes(value.phase)
|
|
1427
|
+
|| !isSameResolvedPath(value.targetPath, expectedTarget)
|
|
1428
|
+
|| !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(transactionId)
|
|
1429
|
+
|| !isSameResolvedPath(dirname(transactionRoot), transactionsRoot)
|
|
1430
|
+
|| !isSameResolvedPath(value.stagingPath, join(transactionRoot, "staging"))
|
|
1431
|
+
|| !isSameResolvedPath(value.rollbackPath, join(transactionRoot, "rollback"))
|
|
1432
|
+
|| value.backupPath !== undefined && !isCanonicalDeploymentBackupPath(projectPath, value.packageName, value.backupPath)
|
|
1433
|
+
|| typeof value.targetExisted !== "boolean" || typeof value.backupCreated !== "boolean" || typeof value.deleteBackupOnCommit !== "boolean"
|
|
1434
|
+
|| value.operation === "deploy" && value.deleteBackupOnCommit
|
|
1435
|
+
|| value.operation === "restore" && (value.backupCreated || value.oldRecord === undefined)
|
|
1436
|
+
|| value.operation === "restore" && value.oldRecord !== undefined
|
|
1437
|
+
&& (!isSameOptionalResolvedPath(value.backupPath, value.oldRecord.backupPath)
|
|
1438
|
+
|| value.deleteBackupOnCommit !== (value.oldRecord.backupPath !== undefined))
|
|
1439
|
+
|| value.backupCreated && (!value.targetExisted || value.backupPath === undefined)
|
|
1440
|
+
|| value.deleteBackupOnCommit && value.backupPath === undefined
|
|
1441
|
+
|| value.oldRecord !== undefined && (!value.oldRecord || typeof value.oldRecord !== "object"))
|
|
1442
|
+
throw new Error("invalid-transaction");
|
|
1443
|
+
if (value.oldRecord !== undefined)
|
|
1444
|
+
assertDeploymentRecordSafe(projectPath, value.packageName, value.oldRecord);
|
|
1445
|
+
return value;
|
|
1446
|
+
}
|
|
1447
|
+
catch (error) {
|
|
1448
|
+
const code = error?.code;
|
|
1449
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
1450
|
+
return undefined;
|
|
1451
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_TRANSACTION_INVALID", `Package deployment transaction is invalid or unreadable: ${path}`);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
async function writeDeploymentTransaction(projectPath, transaction) {
|
|
1455
|
+
await atomicWriteFile(deploymentTransactionPath(projectPath), `${JSON.stringify(transaction, null, 2)}\n`);
|
|
1456
|
+
const observed = await readDeploymentTransaction(projectPath);
|
|
1457
|
+
if (!observed || observed.packageName !== transaction.packageName || observed.phase !== transaction.phase
|
|
1458
|
+
|| observed.operation !== transaction.operation || observed.targetPath !== transaction.targetPath)
|
|
1459
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_TRANSACTION_WRITE_FAILED", "Package deployment transaction readback does not match the written phase.");
|
|
1460
|
+
}
|
|
1461
|
+
async function clearDeploymentTransaction(projectPath) {
|
|
1462
|
+
const path = deploymentTransactionPath(projectPath);
|
|
1463
|
+
await rm(path, { force: true });
|
|
1464
|
+
await assertPathMissing(path, "PACKAGE_DEPLOYMENT_TRANSACTION_CLEANUP_FAILED");
|
|
1465
|
+
}
|
|
1466
|
+
/** 独占deployment lease下按固定marker恢复:commit只清理,其他phase一律回滚到旧target和旧record。 */
|
|
1467
|
+
async function recoverPackageDeploymentTransaction(projectPath) {
|
|
1468
|
+
const transaction = await readDeploymentTransaction(projectPath);
|
|
1469
|
+
if (!transaction)
|
|
1470
|
+
return;
|
|
1471
|
+
if (transaction.phase === "commit_proven") {
|
|
1472
|
+
if (transaction.deleteBackupOnCommit && transaction.backupPath)
|
|
1473
|
+
await removePackagePublishDirectory(transaction.backupPath, "PACKAGE_DEPLOYMENT_CLEANUP_FAILED");
|
|
1474
|
+
await removePackagePublishDirectory(dirname(transaction.stagingPath), "PACKAGE_DEPLOYMENT_CLEANUP_FAILED");
|
|
1475
|
+
await clearDeploymentTransaction(projectPath);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
const rollbackExists = await directoryExists(transaction.rollbackPath);
|
|
1479
|
+
const targetExists = await directoryExists(transaction.targetPath);
|
|
1480
|
+
if (rollbackExists) {
|
|
1481
|
+
if (targetExists)
|
|
1482
|
+
await removePackagePublishDirectory(transaction.targetPath, "PACKAGE_DEPLOYMENT_ROLLBACK_FAILED");
|
|
1483
|
+
await rename(transaction.rollbackPath, transaction.targetPath);
|
|
1484
|
+
if (!await directoryExists(transaction.targetPath) || await directoryExists(transaction.rollbackPath))
|
|
1485
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_ROLLBACK_FAILED", "Package deployment target rollback could not be proven.");
|
|
1486
|
+
}
|
|
1487
|
+
else if (!transaction.targetExisted && targetExists) {
|
|
1488
|
+
await removePackagePublishDirectory(transaction.targetPath, "PACKAGE_DEPLOYMENT_ROLLBACK_FAILED");
|
|
1489
|
+
}
|
|
1490
|
+
else if (transaction.targetExisted && !targetExists) {
|
|
1491
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_ROLLBACK_FAILED", "Package deployment lost both its prior target and rollback copy.");
|
|
1492
|
+
}
|
|
1493
|
+
const metadataPath = deploymentMetadataPath(projectPath);
|
|
1494
|
+
const metadata = await readDeploymentMetadata(metadataPath);
|
|
1495
|
+
if (transaction.oldRecord)
|
|
1496
|
+
metadata[transaction.packageName] = transaction.oldRecord;
|
|
1497
|
+
else
|
|
1498
|
+
delete metadata[transaction.packageName];
|
|
1499
|
+
await writeDeploymentMetadata(metadataPath, metadata);
|
|
1500
|
+
const restored = await readDeploymentMetadata(metadataPath);
|
|
1501
|
+
if (transaction.oldRecord ? !restored[transaction.packageName] || restored[transaction.packageName].sourceHash !== transaction.oldRecord.sourceHash : !!restored[transaction.packageName])
|
|
1502
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_ROLLBACK_FAILED", "Package deployment metadata rollback could not be proven.");
|
|
1503
|
+
if (transaction.backupCreated && transaction.backupPath)
|
|
1504
|
+
await removePackagePublishDirectory(transaction.backupPath, "PACKAGE_DEPLOYMENT_ROLLBACK_FAILED");
|
|
1505
|
+
await removePackagePublishDirectory(dirname(transaction.stagingPath), "PACKAGE_DEPLOYMENT_ROLLBACK_FAILED");
|
|
1506
|
+
await clearDeploymentTransaction(projectPath);
|
|
1507
|
+
}
|
|
1508
|
+
function deploymentBackupPath(projectPath, packageName, deployedAt) {
|
|
1509
|
+
const safeName = packageName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1510
|
+
const stamp = deployedAt.toISOString().replace(/[:.]/g, "-");
|
|
1511
|
+
return join(deploymentRootPath(projectPath), `${safeName}-${stamp}`);
|
|
1512
|
+
}
|
|
1513
|
+
function isCanonicalDeploymentBackupPath(projectPath, packageName, backupPath) {
|
|
1514
|
+
const root = resolve(deploymentRootPath(projectPath));
|
|
1515
|
+
const resolvedBackup = resolve(backupPath);
|
|
1516
|
+
const safeName = packageName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1517
|
+
const expectedName = new RegExp(`^${escapeRegExp(safeName)}-\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}-\\d{3}Z$`);
|
|
1518
|
+
return isSameResolvedPath(dirname(resolvedBackup), root) && expectedName.test(basename(resolvedBackup));
|
|
1519
|
+
}
|
|
1520
|
+
/** 校验部署 metadata 只指向当前项目内允许被 restore 修改的位置。 */
|
|
1521
|
+
function assertDeploymentRecordSafe(projectPath, packageName, record) {
|
|
1522
|
+
if (!record || typeof record !== "object" || record.packageName !== packageName || typeof record.packageVersion !== "string"
|
|
1523
|
+
|| typeof record.sourcePath !== "string" || !isSha256(record.sourceHash) || typeof record.targetPath !== "string"
|
|
1524
|
+
|| typeof record.deployedAt !== "string" || (record.backupPath !== undefined && typeof record.backupPath !== "string")
|
|
1525
|
+
|| (record.backupPackageVersion !== undefined && typeof record.backupPackageVersion !== "string")
|
|
1526
|
+
|| (record.backupPath !== undefined) !== (typeof record.backupPackageVersion === "string" && record.backupPackageVersion.length > 0)) {
|
|
1527
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_METADATA_INVALID", `Package deployment metadata is invalid for ${packageName}.`, [
|
|
1528
|
+
"Inspect or remove Packages/.unitycli_deploy_backups/unitycli.package-deployments.json before retrying."
|
|
1529
|
+
]);
|
|
1530
|
+
}
|
|
1531
|
+
const expectedTargetPath = join(resolve(projectPath), "Packages", packageName);
|
|
1532
|
+
if (!isSameResolvedPath(record.targetPath, expectedTargetPath)) {
|
|
1533
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_METADATA_UNSAFE", `Package deployment metadata target path is not the current project's Packages/${packageName}: ${record.targetPath}`, [
|
|
1534
|
+
"Inspect or remove Packages/.unitycli_deploy_backups/unitycli.package-deployments.json before retrying.",
|
|
1535
|
+
"Run package.deploy again from the current project if the project was moved."
|
|
1536
|
+
]);
|
|
1537
|
+
}
|
|
1538
|
+
if (record.backupPath !== undefined && !isCanonicalDeploymentBackupPath(projectPath, packageName, record.backupPath)) {
|
|
1539
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_METADATA_UNSAFE", `Package deployment metadata backup path is not the canonical direct backup for ${packageName}: ${record.backupPath}`, [
|
|
1540
|
+
"Inspect or remove Packages/.unitycli_deploy_backups/unitycli.package-deployments.json before retrying.",
|
|
1541
|
+
"Do not edit package.deploy metadata by hand unless every path stays inside the current project."
|
|
1542
|
+
]);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
function isSameResolvedPath(left, right) {
|
|
1546
|
+
return relative(resolve(left), resolve(right)) === "";
|
|
1547
|
+
}
|
|
1548
|
+
/** 可选路径只有同时缺失或解析后相等才属于同一 frozen plan。 */
|
|
1549
|
+
function isSameOptionalResolvedPath(left, right) {
|
|
1550
|
+
if (left === undefined || right === undefined)
|
|
1551
|
+
return left === right;
|
|
1552
|
+
return isSameResolvedPath(left, right);
|
|
1553
|
+
}
|
|
1554
|
+
function isPathInside(rootPath, candidatePath) {
|
|
1555
|
+
const relativePath = relative(resolve(rootPath), resolve(candidatePath));
|
|
1556
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
|
|
1557
|
+
}
|
|
1558
|
+
async function readDeploymentMetadata(path) {
|
|
1559
|
+
try {
|
|
1560
|
+
const data = JSON.parse(stripJsonBom(await readFile(path, "utf8")));
|
|
1561
|
+
if (!data || typeof data !== "object" || data.version !== 1 || !isPlainObject(data.deployments))
|
|
1562
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_METADATA_INVALID", `Package deployment metadata requires version 1 and an object deployments map: ${path}`);
|
|
1563
|
+
const deployments = data.deployments;
|
|
1564
|
+
const projectPath = dirname(dirname(dirname(path)));
|
|
1565
|
+
const backupOwners = new Map();
|
|
1566
|
+
for (const [packageName, record] of Object.entries(deployments)) {
|
|
1567
|
+
assertSafePackageDirectoryName(packageName);
|
|
1568
|
+
assertDeploymentRecordSafe(projectPath, packageName, record);
|
|
1569
|
+
if (record.backupPath === undefined)
|
|
1570
|
+
continue;
|
|
1571
|
+
const identity = resolve(record.backupPath).toLowerCase();
|
|
1572
|
+
const previousOwner = backupOwners.get(identity);
|
|
1573
|
+
if (previousOwner)
|
|
1574
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_METADATA_UNSAFE", `Packages ${previousOwner} and ${packageName} reference the same deployment backup.`);
|
|
1575
|
+
backupOwners.set(identity, packageName);
|
|
1576
|
+
}
|
|
1577
|
+
return deployments;
|
|
1578
|
+
}
|
|
1579
|
+
catch (error) {
|
|
1580
|
+
const nodeError = error;
|
|
1581
|
+
if (nodeError.code === "ENOENT")
|
|
1582
|
+
return {};
|
|
1583
|
+
if (error instanceof SyntaxError) {
|
|
1584
|
+
throw new PackageManagerError("PACKAGE_DEPLOYMENT_METADATA_INVALID", `Package deployment metadata is invalid JSON: ${path}`, [
|
|
1585
|
+
"Inspect or remove the metadata file before running package.restore."
|
|
1586
|
+
]);
|
|
1587
|
+
}
|
|
1588
|
+
throw error;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
async function writeDeploymentMetadata(path, deployments) {
|
|
1592
|
+
const metadata = { version: 1, deployments };
|
|
1593
|
+
await atomicWriteFile(path, `${JSON.stringify(metadata, null, 2)}\n`);
|
|
1594
|
+
}
|
|
1595
|
+
async function pathExists(path) {
|
|
1596
|
+
try {
|
|
1597
|
+
await stat(path);
|
|
1598
|
+
return true;
|
|
1599
|
+
}
|
|
1600
|
+
catch (error) {
|
|
1601
|
+
const code = error?.code;
|
|
1602
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
1603
|
+
return false;
|
|
1604
|
+
throw error;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
async function directoryExists(path) {
|
|
1608
|
+
try {
|
|
1609
|
+
return (await stat(path)).isDirectory();
|
|
1610
|
+
}
|
|
1611
|
+
catch (error) {
|
|
1612
|
+
const code = error?.code;
|
|
1613
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
1614
|
+
return false;
|
|
1615
|
+
throw new PackageManagerError("INIT_PATH_OBSERVATION_FAILED", `Unity package directory cannot be observed: ${path}`);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
async function countPackageFiles(sourcePath) {
|
|
1619
|
+
return (await listFilesRecursive(sourcePath)).length;
|
|
1620
|
+
}
|
|
1621
|
+
async function listFilesRecursive(dir) {
|
|
1622
|
+
const files = [];
|
|
1623
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1624
|
+
for (const entry of entries) {
|
|
1625
|
+
const fullPath = join(dir, entry.name);
|
|
1626
|
+
if (entry.isDirectory()) {
|
|
1627
|
+
files.push(...await listFilesRecursive(fullPath));
|
|
1628
|
+
}
|
|
1629
|
+
else if (entry.isFile()) {
|
|
1630
|
+
files.push(fullPath);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
return files;
|
|
1634
|
+
}
|
|
1635
|
+
function assertSafePackageDirectoryName(packageName) {
|
|
1636
|
+
const firstSegment = packageName.split(".", 1)[0].toLowerCase();
|
|
1637
|
+
const reserved = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(firstSegment);
|
|
1638
|
+
const canonical = /^[a-z0-9][a-z0-9_-]*(?:\.[a-z0-9][a-z0-9_-]*)*$/.test(packageName);
|
|
1639
|
+
if (!canonical || reserved || packageName.toLowerCase() === deploymentBackupDirName.toLowerCase()) {
|
|
1640
|
+
throw new PackageManagerError("PACKAGE_NAME_INVALID", `Package name is not safe for a Packages/<name> directory: ${packageName}`, [
|
|
1641
|
+
"Use a valid Unity package name such as com.example.tool."
|
|
1642
|
+
]);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
function assertDeploySourceOutsideProjectPackages(sourcePath, projectPath) {
|
|
1646
|
+
const packagesPath = join(resolve(projectPath), "Packages");
|
|
1647
|
+
const source = resolve(sourcePath);
|
|
1648
|
+
const relativeToPackages = relative(packagesPath, source);
|
|
1649
|
+
if (!relativeToPackages || (!relativeToPackages.startsWith("..") && !isAbsolute(relativeToPackages))) {
|
|
1650
|
+
throw new PackageManagerError("PACKAGE_DEPLOY_SOURCE_INSIDE_PROJECT", "package.deploy source must be outside the target project's Packages directory.", [
|
|
1651
|
+
"Choose a separate local package source directory, not the already-installed package directory.",
|
|
1652
|
+
"Use package.restore to revert a previous package.deploy operation."
|
|
1653
|
+
]);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
async function readManifest(path, allowMissing) {
|
|
1657
|
+
try {
|
|
1658
|
+
const manifest = JSON.parse(stripJsonBom(await readFile(path, "utf8")));
|
|
1659
|
+
if (!isPlainObject(manifest) || !isPlainObject(manifest.dependencies)
|
|
1660
|
+
|| !Object.values(manifest.dependencies).every((value) => typeof value === "string")) {
|
|
1661
|
+
throw new PackageManagerError("MANIFEST_INVALID", `Unity manifest requires an object root with a string-valued dependencies object: ${path}`);
|
|
1662
|
+
}
|
|
1663
|
+
return manifest;
|
|
1664
|
+
}
|
|
1665
|
+
catch (error) {
|
|
1666
|
+
const nodeError = error;
|
|
1667
|
+
if (nodeError.code === "ENOENT") {
|
|
1668
|
+
throw new PackageManagerError("MANIFEST_NOT_FOUND", `Unity manifest not found: ${path}`, ["Run the command from a Unity project root or pass --project <path>. Package write commands do not create a new Unity manifest implicitly."]);
|
|
1669
|
+
}
|
|
1670
|
+
if (error instanceof SyntaxError) {
|
|
1671
|
+
throw new PackageManagerError("MANIFEST_INVALID", `Unity manifest is invalid JSON: ${path}`);
|
|
1672
|
+
}
|
|
1673
|
+
throw error;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
function isPlainObject(value) {
|
|
1677
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1678
|
+
}
|
|
1679
|
+
async function readLock(path) {
|
|
1680
|
+
try {
|
|
1681
|
+
return JSON.parse(stripJsonBom(await readFile(path, "utf8")));
|
|
1682
|
+
}
|
|
1683
|
+
catch (error) {
|
|
1684
|
+
const nodeError = error;
|
|
1685
|
+
if (nodeError.code === "ENOENT") {
|
|
1686
|
+
return { dependencies: {} };
|
|
1687
|
+
}
|
|
1688
|
+
if (error instanceof SyntaxError) {
|
|
1689
|
+
throw new PackageManagerError("LOCK_INVALID", `Unity packages lock is invalid JSON: ${path}`);
|
|
1690
|
+
}
|
|
1691
|
+
throw error;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
async function writeManifest(path, manifest) {
|
|
1695
|
+
await atomicWriteFile(path, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
1696
|
+
}
|
|
1697
|
+
/** 临时文件名携带owner启动身份与内容摘要;dead owner残留在下次写入前精确回收,数量不随历史增长。 */
|
|
1698
|
+
async function atomicWriteFile(path, content) {
|
|
1699
|
+
const parent = dirname(path);
|
|
1700
|
+
const prefix = `.${basename(path)}.tmp-`;
|
|
1701
|
+
await mkdir(parent, { recursive: true });
|
|
1702
|
+
for (const entry of await readdir(parent, { withFileTypes: true })) {
|
|
1703
|
+
if (!entry.isFile() || !entry.name.startsWith(prefix))
|
|
1704
|
+
continue;
|
|
1705
|
+
const match = new RegExp(`^\\.${escapeRegExp(basename(path))}\\.tmp-(\\d+)-([0-9a-f]{16})-[0-9a-f]{64}$`, "i").exec(entry.name);
|
|
1706
|
+
const stalePath = join(parent, entry.name);
|
|
1707
|
+
const sameProcessStale = !!match && Number(match[1]) === process.pid && !activeAtomicWriteCandidates.has(stalePath);
|
|
1708
|
+
if (!match || await isCandidateProcessActive(Number(match[1]), match[2]) && !sameProcessStale)
|
|
1709
|
+
throw new PackageManagerError("ATOMIC_WRITE_BUSY", `Another writer owns temporary metadata for ${path}.`);
|
|
1710
|
+
await rm(stalePath, { force: false });
|
|
1711
|
+
await assertPathMissing(stalePath, "ATOMIC_WRITE_RECOVERY_FAILED");
|
|
1712
|
+
}
|
|
1713
|
+
const currentProcessStartIdentity = await processStartIdentity(process.pid);
|
|
1714
|
+
if (!currentProcessStartIdentity)
|
|
1715
|
+
throw new PackageManagerError("PROCESS_IDENTITY_UNAVAILABLE", `Current process start identity is unavailable before writing ${path}.`);
|
|
1716
|
+
const startHash = createHash("sha256").update(currentProcessStartIdentity).digest("hex").slice(0, 16);
|
|
1717
|
+
const contentHash = createHash("sha256").update(content, "utf8").digest("hex");
|
|
1718
|
+
const temporaryPath = join(parent, `${prefix}${process.pid}-${startHash}-${contentHash}`);
|
|
1719
|
+
activeAtomicWriteCandidates.add(temporaryPath);
|
|
1720
|
+
try {
|
|
1721
|
+
const handle = await open(temporaryPath, "wx", 0o600);
|
|
1722
|
+
try {
|
|
1723
|
+
await handle.writeFile(content, "utf8");
|
|
1724
|
+
await handle.sync();
|
|
1725
|
+
}
|
|
1726
|
+
finally {
|
|
1727
|
+
await handle.close();
|
|
1728
|
+
}
|
|
1729
|
+
if (createHash("sha256").update(await readFile(temporaryPath, "utf8"), "utf8").digest("hex") !== contentHash)
|
|
1730
|
+
throw new PackageManagerError("ATOMIC_WRITE_READBACK_FAILED", `Temporary metadata content changed before publish: ${path}`);
|
|
1731
|
+
await rename(temporaryPath, path);
|
|
1732
|
+
}
|
|
1733
|
+
finally {
|
|
1734
|
+
activeAtomicWriteCandidates.delete(temporaryPath);
|
|
1735
|
+
if (await pathExists(temporaryPath)) {
|
|
1736
|
+
await rm(temporaryPath, { force: false });
|
|
1737
|
+
await assertPathMissing(temporaryPath, "ATOMIC_WRITE_CLEANUP_FAILED");
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
function escapeRegExp(value) {
|
|
1742
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1743
|
+
}
|
|
1744
|
+
/** manifest 写入后必须以磁盘重读结果确认,避免把请求或写前对象误报为终态。 */
|
|
1745
|
+
function manifestReadbackMismatch(manifestPath, detail) {
|
|
1746
|
+
return new PackageManagerError("MANIFEST_READBACK_MISMATCH", `Manifest write could not be verified: ${detail}.`, [
|
|
1747
|
+
"Inspect Packages/manifest.json and retry after resolving concurrent manifest writes."
|
|
1748
|
+
]);
|
|
1749
|
+
}
|
|
1750
|
+
function stripJsonBom(value) {
|
|
1751
|
+
return value.charCodeAt(0) === 0xfeff ? value.slice(1) : value;
|
|
1752
|
+
}
|
|
1753
|
+
function toPackageEntry(name, requestedVersion, locked) {
|
|
1754
|
+
return {
|
|
1755
|
+
name,
|
|
1756
|
+
requestedVersion,
|
|
1757
|
+
lockedVersion: locked?.version,
|
|
1758
|
+
source: locked?.source,
|
|
1759
|
+
url: locked?.url,
|
|
1760
|
+
hash: locked?.hash,
|
|
1761
|
+
dependencies: locked?.dependencies
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
/** 比较 Unity registry 常见数字版本;git/file/custom spec 无法可靠比较时不拦截。 */
|
|
1765
|
+
function isPackageVersionDowngrade(candidate, current) {
|
|
1766
|
+
const candidateParts = parseComparableVersion(candidate);
|
|
1767
|
+
const currentParts = parseComparableVersion(current);
|
|
1768
|
+
if (!candidateParts || !currentParts) {
|
|
1769
|
+
return false;
|
|
1770
|
+
}
|
|
1771
|
+
const length = Math.max(candidateParts.length, currentParts.length);
|
|
1772
|
+
for (let index = 0; index < length; index += 1) {
|
|
1773
|
+
const left = candidateParts[index] ?? 0;
|
|
1774
|
+
const right = currentParts[index] ?? 0;
|
|
1775
|
+
if (left < right)
|
|
1776
|
+
return true;
|
|
1777
|
+
if (left > right)
|
|
1778
|
+
return false;
|
|
1779
|
+
}
|
|
1780
|
+
return false;
|
|
1781
|
+
}
|
|
1782
|
+
/** 解析可安全比较的数字版本;非 registry 版本返回 undefined 交给 Unity 处理。 */
|
|
1783
|
+
function parseComparableVersion(version) {
|
|
1784
|
+
const normalized = version.trim().replace(/^v/i, "");
|
|
1785
|
+
if (!/^\d+(?:\.\d+)*(?:[-+].*)?$/.test(normalized)) {
|
|
1786
|
+
return undefined;
|
|
1787
|
+
}
|
|
1788
|
+
return normalized.split(/[+-]/, 1)[0].split(".").map((part) => Number(part));
|
|
1789
|
+
}
|
|
1790
|
+
/** 从 packages-lock.json 中找出直接依赖目标包的 locked package。 */
|
|
1791
|
+
function packageDependents(lock, packageName) {
|
|
1792
|
+
return Object.entries(lock.dependencies ?? {})
|
|
1793
|
+
.filter(([name, entry]) => name !== packageName && entry.dependencies && Object.prototype.hasOwnProperty.call(entry.dependencies, packageName))
|
|
1794
|
+
.map(([name, entry]) => ({ name, requiredVersion: entry.dependencies?.[packageName] }))
|
|
1795
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
1796
|
+
}
|
|
1797
|
+
/** 根据 scoped registry scopes 找出仍会由该 registry 覆盖的 manifest 包。 */
|
|
1798
|
+
function packagesUsingScopes(manifest, scopes) {
|
|
1799
|
+
const names = Object.keys(manifest.dependencies ?? {});
|
|
1800
|
+
return names.filter((name) => scopes.some((scope) => packageMatchesScope(name, scope))).sort();
|
|
1801
|
+
}
|
|
1802
|
+
/** Unity scoped registry scope 按完整包名或点分前缀匹配。 */
|
|
1803
|
+
function packageMatchesScope(packageName, scope) {
|
|
1804
|
+
return packageName === scope || packageName.startsWith(`${scope}.`);
|
|
1805
|
+
}
|
|
1806
|
+
function normalizeScopedRegistries(scopedRegistries) {
|
|
1807
|
+
if (!Array.isArray(scopedRegistries)) {
|
|
1808
|
+
return [];
|
|
1809
|
+
}
|
|
1810
|
+
return scopedRegistries.filter((registry) => {
|
|
1811
|
+
return typeof registry?.name === "string" && typeof registry.url === "string" && Array.isArray(registry.scopes);
|
|
1812
|
+
}).map((registry) => ({ name: registry.name, url: registry.url, scopes: uniqueScopes(registry.scopes.map(String)) }));
|
|
1813
|
+
}
|
|
1814
|
+
function uniqueScopes(scopes) {
|
|
1815
|
+
return [...new Set(scopes.map((scope) => scope.trim()).filter(Boolean))];
|
|
1816
|
+
}
|
|
1817
|
+
function sameScopes(left, right) {
|
|
1818
|
+
return left.length === right.length && left.every((scope, index) => scope === right[index]);
|
|
1819
|
+
}
|
|
1820
|
+
function normalizeRelativePath(path) {
|
|
1821
|
+
return path.split("\\").join("/");
|
|
1822
|
+
}
|
|
1823
|
+
function pathHelpersFor(firstPath, secondPath) {
|
|
1824
|
+
return hasWindowsDrivePath(firstPath) || hasWindowsDrivePath(secondPath) ? win32 : { join, relative, resolve };
|
|
1825
|
+
}
|
|
1826
|
+
function hasWindowsDrivePath(path) {
|
|
1827
|
+
return /^[A-Za-z]:[\\/]/.test(path);
|
|
1828
|
+
}
|
|
1829
|
+
function isAbsoluteDependencyPath(path) {
|
|
1830
|
+
return path.startsWith("/") || /^[A-Za-z]:\//.test(path);
|
|
1831
|
+
}
|
|
1832
|
+
//# sourceMappingURL=package-manager.js.map
|