@skaile/workspaces 5.9.0 → 5.11.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/CHANGELOG.md +145 -0
- package/README.md +12 -0
- package/dist/{asset-feeds-XMKKUUK6.js → asset-feeds-CXGQIDFR.js} +19 -19
- package/dist/{asset-feeds-XMKKUUK6.js.map → asset-feeds-CXGQIDFR.js.map} +1 -1
- package/dist/asset-manager/catalog-deployer.js +8 -8
- package/dist/asset-manager/index.js +14 -14
- package/dist/asset-manager/installer.js +10 -10
- package/dist/asset-manager/renderers.js +9 -9
- package/dist/asset-manager/src/index.d.ts +400 -35
- package/dist/asset-manager/src/index.d.ts.map +1 -1
- package/dist/asset-manager/src/install-agent.d.ts +3 -1
- package/dist/asset-manager/src/install-agent.d.ts.map +1 -1
- package/dist/asset-manager/src/install-instances.d.ts +31 -1
- package/dist/asset-manager/src/install-instances.d.ts.map +1 -1
- package/dist/asset-manager/src/installer.d.ts +43 -3
- package/dist/asset-manager/src/installer.d.ts.map +1 -1
- package/dist/asset-manager/src/renderers.d.ts +4 -5
- package/dist/asset-manager/src/renderers.d.ts.map +1 -1
- package/dist/bridge/drivers/claude-sdk.js +11 -11
- package/dist/bridge/drivers/codex.js +11 -11
- package/dist/bridge/drivers/continue.js +10 -10
- package/dist/bridge/drivers/echo.js +2 -2
- package/dist/bridge/drivers/gemini.js +7 -7
- package/dist/bridge/drivers/goose.js +10 -10
- package/dist/bridge/drivers/omp.js +19 -3
- package/dist/bridge/drivers/omp.js.map +1 -1
- package/dist/bridge/drivers/opencode.js +10 -10
- package/dist/bridge/drivers/qwen.js +7 -7
- package/dist/bridge/index.js +12 -12
- package/dist/bridge/src/drivers/omp.d.ts.map +1 -1
- package/dist/{chunk-4MQS7CAF.js → chunk-2F5AWQX5.js} +437 -175
- package/dist/chunk-2F5AWQX5.js.map +1 -0
- package/dist/{chunk-UM7RIAHO.js → chunk-3SM2DL2T.js} +8 -8
- package/dist/{chunk-UM7RIAHO.js.map → chunk-3SM2DL2T.js.map} +1 -1
- package/dist/{chunk-5NNNV76P.js → chunk-47U4GZ4M.js} +2 -2
- package/dist/{chunk-5NNNV76P.js.map → chunk-47U4GZ4M.js.map} +1 -1
- package/dist/{chunk-TTRYE6AC.js → chunk-4A4OD24G.js} +9 -9
- package/dist/{chunk-TTRYE6AC.js.map → chunk-4A4OD24G.js.map} +1 -1
- package/dist/{chunk-ZTBAYUDY.js → chunk-6O2X3GPW.js} +2 -2
- package/dist/{chunk-ZTBAYUDY.js.map → chunk-6O2X3GPW.js.map} +1 -1
- package/dist/{chunk-JAKOMFC6.js → chunk-ALEYHUC5.js} +114 -46
- package/dist/chunk-ALEYHUC5.js.map +1 -0
- package/dist/{chunk-QVNRCDTV.js → chunk-BNMZVPYA.js} +7 -7
- package/dist/chunk-BNMZVPYA.js.map +1 -0
- package/dist/{chunk-2FRUUDZY.js → chunk-CAHB6MHA.js} +5 -5
- package/dist/{chunk-2FRUUDZY.js.map → chunk-CAHB6MHA.js.map} +1 -1
- package/dist/{chunk-GKMZ6THE.js → chunk-CJERAGFY.js} +44 -33
- package/dist/chunk-CJERAGFY.js.map +1 -0
- package/dist/{chunk-CKQEIY43.js → chunk-COH6FQAG.js} +4 -4
- package/dist/{chunk-CKQEIY43.js.map → chunk-COH6FQAG.js.map} +1 -1
- package/dist/{chunk-UO7WPLDY.js → chunk-CPWLNZGJ.js} +3 -3
- package/dist/{chunk-UO7WPLDY.js.map → chunk-CPWLNZGJ.js.map} +1 -1
- package/dist/{chunk-MGGFBZWA.js → chunk-D3B36CYC.js} +4 -3
- package/dist/chunk-D3B36CYC.js.map +1 -0
- package/dist/{chunk-UMVKJM4Q.js → chunk-DALV4HHP.js} +18 -3
- package/dist/chunk-DALV4HHP.js.map +1 -0
- package/dist/{chunk-IMKSM5NL.js → chunk-DZMNFCWB.js} +1447 -199
- package/dist/chunk-DZMNFCWB.js.map +1 -0
- package/dist/{chunk-GB7WDMH2.js → chunk-EDYHEMJA.js} +4 -4
- package/dist/{chunk-GB7WDMH2.js.map → chunk-EDYHEMJA.js.map} +1 -1
- package/dist/{chunk-YSHVQGUS.js → chunk-FH7W6TJ4.js} +164 -41
- package/dist/chunk-FH7W6TJ4.js.map +1 -0
- package/dist/{chunk-W5DWWE4Y.js → chunk-G4FFOV3N.js} +4 -4
- package/dist/{chunk-W5DWWE4Y.js.map → chunk-G4FFOV3N.js.map} +1 -1
- package/dist/{chunk-2LWYNQK7.js → chunk-GCHPUTA3.js} +3 -3
- package/dist/{chunk-2LWYNQK7.js.map → chunk-GCHPUTA3.js.map} +1 -1
- package/dist/{chunk-JZ5EHBPI.js → chunk-GV4DZ6MT.js} +20 -4
- package/dist/chunk-GV4DZ6MT.js.map +1 -0
- package/dist/{chunk-S53LOR7B.js → chunk-HCEVNYSH.js} +3 -3
- package/dist/{chunk-S53LOR7B.js.map → chunk-HCEVNYSH.js.map} +1 -1
- package/dist/{chunk-EF7NPDDU.js → chunk-HZXUXHIV.js} +31 -17
- package/dist/chunk-HZXUXHIV.js.map +1 -0
- package/dist/{chunk-IR33D6EP.js → chunk-IER3KIPH.js} +5 -5
- package/dist/{chunk-IR33D6EP.js.map → chunk-IER3KIPH.js.map} +1 -1
- package/dist/{chunk-Y7QA2OLD.js → chunk-J4RBUFGM.js} +22 -20
- package/dist/chunk-J4RBUFGM.js.map +1 -0
- package/dist/{chunk-J7U3PIOE.js → chunk-M3B6EIJD.js} +4 -4
- package/dist/{chunk-J7U3PIOE.js.map → chunk-M3B6EIJD.js.map} +1 -1
- package/dist/{chunk-DNZYOKOJ.js → chunk-MZG34MBZ.js} +3 -3
- package/dist/{chunk-DNZYOKOJ.js.map → chunk-MZG34MBZ.js.map} +1 -1
- package/dist/{chunk-V2DXZYBB.js → chunk-NWDSLXOX.js} +3 -3
- package/dist/{chunk-V2DXZYBB.js.map → chunk-NWDSLXOX.js.map} +1 -1
- package/dist/{chunk-36AFURKZ.js → chunk-PBBPZYVG.js} +61 -12
- package/dist/chunk-PBBPZYVG.js.map +1 -0
- package/dist/{chunk-P67WJVP4.js → chunk-PQFHMQHQ.js} +10 -9
- package/dist/chunk-PQFHMQHQ.js.map +1 -0
- package/dist/{chunk-UAROCOXF.js → chunk-QVFAS5IO.js} +3 -3
- package/dist/{chunk-UAROCOXF.js.map → chunk-QVFAS5IO.js.map} +1 -1
- package/dist/{chunk-K3CVFRVS.js → chunk-QZNJOHN6.js} +3 -3
- package/dist/{chunk-K3CVFRVS.js.map → chunk-QZNJOHN6.js.map} +1 -1
- package/dist/{chunk-FVJK4QWM.js → chunk-R7NHYEJF.js} +4 -4
- package/dist/{chunk-FVJK4QWM.js.map → chunk-R7NHYEJF.js.map} +1 -1
- package/dist/{chunk-GXLQLPE5.js → chunk-RYYLMBA2.js} +5 -5
- package/dist/{chunk-GXLQLPE5.js.map → chunk-RYYLMBA2.js.map} +1 -1
- package/dist/{chunk-Y4N2NUZD.js → chunk-S3O3GKB7.js} +4 -4
- package/dist/{chunk-Y4N2NUZD.js.map → chunk-S3O3GKB7.js.map} +1 -1
- package/dist/{chunk-GTILM2CI.js → chunk-SBSA3IQA.js} +43 -12
- package/dist/chunk-SBSA3IQA.js.map +1 -0
- package/dist/{chunk-4PTSR4UU.js → chunk-T42WRQ4O.js} +3 -3
- package/dist/{chunk-4PTSR4UU.js.map → chunk-T42WRQ4O.js.map} +1 -1
- package/dist/{chunk-MR5HBOUF.js → chunk-T7CTHHG7.js} +3 -3
- package/dist/{chunk-MR5HBOUF.js.map → chunk-T7CTHHG7.js.map} +1 -1
- package/dist/{chunk-FZNG4V4J.js → chunk-TCHRMVTO.js} +4 -4
- package/dist/{chunk-FZNG4V4J.js.map → chunk-TCHRMVTO.js.map} +1 -1
- package/dist/{chunk-LAMCXQGA.js → chunk-VLTQGE2E.js} +75 -25
- package/dist/chunk-VLTQGE2E.js.map +1 -0
- package/dist/{chunk-7AI3Q5AQ.js → chunk-WSBKB4CJ.js} +71 -24
- package/dist/chunk-WSBKB4CJ.js.map +1 -0
- package/dist/{chunk-CGT45WN7.js → chunk-X77SUM4P.js} +3 -3
- package/dist/{chunk-CGT45WN7.js.map → chunk-X77SUM4P.js.map} +1 -1
- package/dist/{chunk-SCOT677Q.js → chunk-XDKH7XSA.js} +5 -5
- package/dist/{chunk-SCOT677Q.js.map → chunk-XDKH7XSA.js.map} +1 -1
- package/dist/{chunk-QVEKLSEH.js → chunk-XUSALIEI.js} +6 -6
- package/dist/{chunk-QVEKLSEH.js.map → chunk-XUSALIEI.js.map} +1 -1
- package/dist/{chunk-FYXZRVID.js → chunk-Y3HTGHJC.js} +3 -3
- package/dist/{chunk-FYXZRVID.js.map → chunk-Y3HTGHJC.js.map} +1 -1
- package/dist/chunk-YOH2L76X.js +505 -0
- package/dist/chunk-YOH2L76X.js.map +1 -0
- package/dist/{chunk-B6GIW55K.js → chunk-ZCAJZ4B2.js} +7 -4
- package/dist/chunk-ZCAJZ4B2.js.map +1 -0
- package/dist/{chunk-BTE2V4IW.js → chunk-ZPWTOUTK.js} +76 -7
- package/dist/{chunk-BTE2V4IW.js.map → chunk-ZPWTOUTK.js.map} +1 -1
- package/dist/cli/index.js +1034 -794
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/src/commands/asset-cmd.d.ts.map +1 -1
- package/dist/cli/src/commands/catalog.d.ts +8 -5
- package/dist/cli/src/commands/catalog.d.ts.map +1 -1
- package/dist/cli/src/commands/diff.d.ts +4 -2
- package/dist/cli/src/commands/diff.d.ts.map +1 -1
- package/dist/cli/src/commands/library-cmd.d.ts.map +1 -1
- package/dist/cli/src/commands/library-index.d.ts +34 -2
- package/dist/cli/src/commands/library-index.d.ts.map +1 -1
- package/dist/cli/src/commands/library-publish.d.ts.map +1 -1
- package/dist/cli/src/commands/manage.d.ts +12 -5
- package/dist/cli/src/commands/manage.d.ts.map +1 -1
- package/dist/cli/src/commands/migrate-assets.d.ts.map +1 -1
- package/dist/cli/src/commands/outdated.d.ts +7 -5
- package/dist/cli/src/commands/outdated.d.ts.map +1 -1
- package/dist/cli/src/commands/project.d.ts +13 -8
- package/dist/cli/src/commands/project.d.ts.map +1 -1
- package/dist/cli/src/commands/run.d.ts.map +1 -1
- package/dist/cli/src/commands/source.d.ts +30 -1
- package/dist/cli/src/commands/source.d.ts.map +1 -1
- package/dist/cli/src/commands/update.d.ts.map +1 -1
- package/dist/cli/src/commands/validate.d.ts.map +1 -1
- package/dist/cli/src/commands/why.d.ts +1 -0
- package/dist/cli/src/commands/why.d.ts.map +1 -1
- package/dist/cli/src/helpers.d.ts +12 -0
- package/dist/cli/src/helpers.d.ts.map +1 -1
- package/dist/cli/src/skill-checks.d.ts +57 -4
- package/dist/cli/src/skill-checks.d.ts.map +1 -1
- package/dist/connectors/config.js +5 -5
- package/dist/connectors/index.js +16 -16
- package/dist/{connectors-C7UQ73BK.js → connectors-4QWAKPHB.js} +19 -19
- package/dist/{connectors-C7UQ73BK.js.map → connectors-4QWAKPHB.js.map} +1 -1
- package/dist/core/index.js +7 -7
- package/dist/core/logging.js +1 -1
- package/dist/core/manifest.js +2 -2
- package/dist/core/models.js +1 -1
- package/dist/core/publish-manifest.js +2 -2
- package/dist/core/ref.js +1 -1
- package/dist/core/runtime-assets.js +5 -5
- package/dist/core/src/index.d.ts +2 -1
- package/dist/core/src/index.d.ts.map +1 -1
- package/dist/core/src/lock.d.ts +6 -2
- package/dist/core/src/lock.d.ts.map +1 -1
- package/dist/core/src/logging/sinks/sqlite-runtime.d.ts.map +1 -1
- package/dist/core/src/manifest.d.ts.map +1 -1
- package/dist/core/src/models.d.ts +22 -6
- package/dist/core/src/models.d.ts.map +1 -1
- package/dist/core/src/publish-manifest.d.ts +29 -3
- package/dist/core/src/publish-manifest.d.ts.map +1 -1
- package/dist/core/src/repo-manager.d.ts.map +1 -1
- package/dist/core/src/requirements.d.ts +26 -0
- package/dist/core/src/requirements.d.ts.map +1 -0
- package/dist/core/src/walker.d.ts.map +1 -1
- package/dist/core/src/workspace-config.d.ts +22 -1
- package/dist/core/src/workspace-config.d.ts.map +1 -1
- package/dist/core/src/workspace-yaml-editor.d.ts +4 -1
- package/dist/core/src/workspace-yaml-editor.d.ts.map +1 -1
- package/dist/core/workspace-config.js +4 -4
- package/dist/deploy/index.js +7 -7
- package/dist/discovery/index.js +5 -5
- package/dist/discovery/src/builtin-providers.d.ts.map +1 -1
- package/dist/discovery/src/discover-manifest.d.ts.map +1 -1
- package/dist/discovery/src/discover.d.ts.map +1 -1
- package/dist/discovery/src/source-config.d.ts.map +1 -1
- package/dist/discovery/src/tree-entries.d.ts.map +1 -1
- package/dist/{ensure-sources-SRC2DVR5.js → ensure-sources-2RONZKCU.js} +17 -17
- package/dist/{ensure-sources-SRC2DVR5.js.map → ensure-sources-2RONZKCU.js.map} +1 -1
- package/dist/factory-assets/connectors/box.js +16 -16
- package/dist/factory-assets/connectors/deploy.js +16 -16
- package/dist/factory-assets/connectors/devserver.js +16 -16
- package/dist/factory-assets/connectors/flow/adapter.js +16 -16
- package/dist/factory-assets/connectors/flow/contract/normalize.d.ts.map +1 -1
- package/dist/factory-assets/connectors/flow/contract.js +1 -1
- package/dist/factory-assets/connectors/flow/engine.js +9 -9
- package/dist/factory-assets/connectors/flow/run-flow.js +18 -18
- package/dist/factory-assets/connectors/flow.js +16 -16
- package/dist/factory-assets/connectors/git.js +16 -16
- package/dist/factory-assets/connectors/gmail.js +16 -16
- package/dist/factory-assets/connectors/googledrive.js +16 -16
- package/dist/factory-assets/connectors/local.js +16 -16
- package/dist/factory-assets/connectors/mattermost.js +16 -16
- package/dist/factory-assets/connectors/memory.js +16 -16
- package/dist/factory-assets/connectors/minio.js +16 -16
- package/dist/factory-assets/connectors/postgres.js +16 -16
- package/dist/factory-assets/connectors/s3.js +16 -16
- package/dist/factory-assets/connectors/sharepoint.js +16 -16
- package/dist/factory-assets/connectors/sqlite.js +16 -16
- package/dist/factory-assets/connectors/static-server.js +16 -16
- package/dist/factory-assets/connectors/tunnel.js +16 -16
- package/dist/factory-assets/connectors/webdav.js +16 -16
- package/dist/factory-assets/connectors/xstate-store.js +16 -16
- package/dist/factory-assets/connectors/xstate.js +16 -16
- package/dist/factory-assets/skills/authoring-skaile-manifests/SKILL.md +9 -3
- package/dist/factory-assets/skills/authoring-skaile-manifests/references/dependency-standard.md +4 -2
- package/dist/{flows-CBYRSAGS.js → flows-EQF2CH4U.js} +12 -12
- package/dist/{flows-CBYRSAGS.js.map → flows-EQF2CH4U.js.map} +1 -1
- package/dist/{helpers-XAEKCZKT.js → helpers-CFK6YP6R.js} +17 -17
- package/dist/{helpers-XAEKCZKT.js.map → helpers-CFK6YP6R.js.map} +1 -1
- package/dist/library/index.js +11 -11
- package/dist/library/install.js +3 -3
- package/dist/library/src/index.d.ts +2 -1
- package/dist/library/src/index.d.ts.map +1 -1
- package/dist/library/src/local/library-dir.d.ts +68 -0
- package/dist/library/src/local/library-dir.d.ts.map +1 -0
- package/dist/library/src/local/library-manager.d.ts +10 -1
- package/dist/library/src/local/library-manager.d.ts.map +1 -1
- package/dist/library/src/local/library.d.ts +2 -2
- package/dist/library/src/local/library.d.ts.map +1 -1
- package/dist/library/src/local/local-catalog-source.d.ts +9 -0
- package/dist/library/src/local/local-catalog-source.d.ts.map +1 -1
- package/dist/library/src/manifest-writer.d.ts +55 -4
- package/dist/library/src/manifest-writer.d.ts.map +1 -1
- package/dist/library/src/remote/cache.d.ts +18 -1
- package/dist/library/src/remote/cache.d.ts.map +1 -1
- package/dist/library/src/remote/remote-catalog-source.d.ts.map +1 -1
- package/dist/library/src/remote/rest-catalog-source.d.ts.map +1 -1
- package/dist/library/src/sync/git-driver.d.ts +7 -6
- package/dist/library/src/sync/git-driver.d.ts.map +1 -1
- package/dist/{library-publish-VJSWMOLH.js → library-publish-53UTB3MB.js} +151 -42
- package/dist/library-publish-53UTB3MB.js.map +1 -0
- package/dist/open-library-BOSPJVID.js +40 -0
- package/dist/{open-library-DB2PNL2X.js.map → open-library-BOSPJVID.js.map} +1 -1
- package/dist/open-registry-4FI7HKET.js +32 -0
- package/dist/{open-registry-ZUZJ7MS7.js.map → open-registry-4FI7HKET.js.map} +1 -1
- package/dist/paths-SUS4S4YT.js +22 -0
- package/dist/{paths-FQ64OLJG.js.map → paths-SUS4S4YT.js.map} +1 -1
- package/dist/{plugin-store-MPAMD5OT.js → plugin-store-AGQDMING.js} +9 -9
- package/dist/{plugin-store-MPAMD5OT.js.map → plugin-store-AGQDMING.js.map} +1 -1
- package/dist/project-I55A4O2T.js +41 -0
- package/dist/{project-I6NX3NFB.js.map → project-I55A4O2T.js.map} +1 -1
- package/dist/resolver/index.js +8 -8
- package/dist/runner/index.js +30 -30
- package/dist/runner/src/agent.d.ts.map +1 -1
- package/dist/runner/src/serve.d.ts.map +1 -1
- package/dist/runner/src/usage-limit-turn-hold.d.ts +51 -0
- package/dist/runner/src/usage-limit-turn-hold.d.ts.map +1 -0
- package/dist/sdk/asset-manager.js +14 -14
- package/dist/sdk/bridge.js +12 -12
- package/dist/sdk/core.js +7 -7
- package/dist/sdk/flow.js +9 -9
- package/dist/sdk/index.js +30 -30
- package/dist/sdk/resolver.js +8 -8
- package/dist/sdk/runner.js +30 -30
- package/dist/{setup-B7JUHYJW.js → setup-7DQJKHUI.js} +22 -22
- package/dist/{setup-B7JUHYJW.js.map → setup-7DQJKHUI.js.map} +1 -1
- package/dist/{skills-TVO26Y4V.js → skills-NUEN5NDX.js} +13 -13
- package/dist/{skills-TVO26Y4V.js.map → skills-NUEN5NDX.js.map} +1 -1
- package/dist/source-G2LV5LVD.js +41 -0
- package/dist/{source-PJBMDCKX.js.map → source-G2LV5LVD.js.map} +1 -1
- package/dist/{source-manifest-publish-LLJWXGID.js → source-manifest-publish-6RM7SWLZ.js} +24 -24
- package/dist/{source-manifest-publish-LLJWXGID.js.map → source-manifest-publish-6RM7SWLZ.js.map} +1 -1
- package/dist/tui/index.js +61 -41
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/src/repl.d.ts.map +1 -1
- package/dist/types/manifests.js +1 -1
- package/dist/types/src/manifests/agent.d.ts +2 -0
- package/dist/types/src/manifests/agent.d.ts.map +1 -1
- package/dist/types/src/manifests/contract.d.ts +2 -0
- package/dist/types/src/manifests/contract.d.ts.map +1 -1
- package/dist/types/src/manifests/index.d.ts +3 -3
- package/dist/types/src/manifests/index.d.ts.map +1 -1
- package/dist/types/src/manifests/preset.d.ts +1 -1
- package/dist/types/src/manifests/prompt.d.ts +2 -0
- package/dist/types/src/manifests/prompt.d.ts.map +1 -1
- package/dist/workspace-plugin/adapters/mcp.js +23 -23
- package/dist/workspace-plugin/adapters/omp.js +14 -14
- package/dist/workspace-plugin/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-36AFURKZ.js.map +0 -1
- package/dist/chunk-4MQS7CAF.js.map +0 -1
- package/dist/chunk-7AI3Q5AQ.js.map +0 -1
- package/dist/chunk-B6GIW55K.js.map +0 -1
- package/dist/chunk-EF7NPDDU.js.map +0 -1
- package/dist/chunk-GKMZ6THE.js.map +0 -1
- package/dist/chunk-GTILM2CI.js.map +0 -1
- package/dist/chunk-IMKSM5NL.js.map +0 -1
- package/dist/chunk-JAKOMFC6.js.map +0 -1
- package/dist/chunk-JZ5EHBPI.js.map +0 -1
- package/dist/chunk-LAMCXQGA.js.map +0 -1
- package/dist/chunk-MGGFBZWA.js.map +0 -1
- package/dist/chunk-P67WJVP4.js.map +0 -1
- package/dist/chunk-QVNRCDTV.js.map +0 -1
- package/dist/chunk-UMVKJM4Q.js.map +0 -1
- package/dist/chunk-Y7QA2OLD.js.map +0 -1
- package/dist/chunk-YSHVQGUS.js.map +0 -1
- package/dist/chunk-ZAOGFXDO.js +0 -112
- package/dist/chunk-ZAOGFXDO.js.map +0 -1
- package/dist/library-publish-VJSWMOLH.js.map +0 -1
- package/dist/open-library-DB2PNL2X.js +0 -40
- package/dist/open-registry-ZUZJ7MS7.js +0 -32
- package/dist/paths-FQ64OLJG.js +0 -22
- package/dist/project-I6NX3NFB.js +0 -41
- package/dist/source-PJBMDCKX.js +0 -41
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { getStoreManifestsDir } from './chunk-
|
|
2
|
-
import { readManifestSourceConfig, isManifestMode, discoverAssetsInTree } from './chunk-
|
|
3
|
-
import {
|
|
1
|
+
import { unregisteredLibraryNames, renamedLibraryName, scanUnlistedAssets, getStoreManifestsDir, missingLibraryDirMessage } from './chunk-2F5AWQX5.js';
|
|
2
|
+
import { readManifestSourceConfig, isManifestMode, discoverAssetsInTree } from './chunk-J4RBUFGM.js';
|
|
3
|
+
import { installTargetOf, canonicalEntryRef, deployAll, removeAsset, diffDeployedAgent, deployedAssetDrift, createScaffold, installAgent } from './chunk-FH7W6TJ4.js';
|
|
4
4
|
import { appendHistory, getRecentHistory, clearHistory } from './chunk-KTBKW2FI.js';
|
|
5
5
|
import { commitChanges } from './chunk-UQ6LFBPZ.js';
|
|
6
|
-
import { driverTargetSupportsAgents, CODEX_IDENTITY_DIR } from './chunk-
|
|
7
|
-
import { resolveConfiguredDriverTarget, GLOBAL_INSTALL_DIR_NAME, resolveLibraryDir, buildLibraryLockEntries, buildLockFile, writeLock, readLock, verifyLock, mergeLockFile, extractForPatch, generatePatch, savePatch, applyPatch } from './chunk-
|
|
8
|
-
import { resolveRuntimeAssets, factoryAssetRoots } from './chunk-
|
|
6
|
+
import { driverTargetSupportsAgents, CODEX_IDENTITY_DIR } from './chunk-HZXUXHIV.js';
|
|
7
|
+
import { resolveConfiguredDriverTarget, GLOBAL_INSTALL_DIR_NAME, resolveLibraryDir, buildLibraryLockEntries, buildLockFile, writeLock, readLock, verifyLock, mergeLockFile, extractForPatch, generatePatch, savePatch, applyPatch, computeHash } from './chunk-PBBPZYVG.js';
|
|
8
|
+
import { resolveRuntimeAssets, factoryAssetRoots } from './chunk-EDYHEMJA.js';
|
|
9
9
|
import { deployedBase, deployedDir, isDeployed, deployedPaths } from './chunk-PMECEZK3.js';
|
|
10
|
-
import { resolveSkWorkspaceConfig, effectiveAgentInstances,
|
|
10
|
+
import { resolveSkWorkspaceConfig, effectiveAgentInstances, loadMcpServerDeclarations, resolveGlobalHarnesses, resolveGlobalSkWorkspaceConfig, getGlobalCacheDir, sourceCacheKey, getRepoCommit, matchPin, buildProvenanceIndex, coerceVersion, sourceShortName, resolveAll, ensureRepo, scanRepo, SKAILE_YAML_DEFAULT, checkRepoStatus, resolveAsset, readLinks, MAX_PRESET_DEPTH, PresetDepthError, DEFAULT_AGENT_INSTANCE_ID, resolveAgentDefinitionDir, storeManifestSlug } from './chunk-ALEYHUC5.js';
|
|
11
11
|
import { globalSkaileDir, globalCacheRoot, migrateLegacyDir } from './chunk-L5VSIPQD.js';
|
|
12
12
|
import { DRIVER_TARGETS } from './chunk-CPSIPIX7.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { loadSkPublishManifest } from './chunk-ZPWTOUTK.js';
|
|
14
|
+
import { scanDirectory, parseRequires, dedupeRequirements } from './chunk-SBSA3IQA.js';
|
|
15
|
+
import { parseAssetRef, parseAssetRefLoose, assetRefToStr, slugifyAssetName, depToStr } from './chunk-BNMZVPYA.js';
|
|
15
16
|
import { spawnSync } from 'child_process';
|
|
16
17
|
import { existsSync, rmSync, readdirSync, lstatSync, unlinkSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
17
|
-
import path, {
|
|
18
|
+
import path, { resolve, dirname, sep, join, relative, posix } from 'path';
|
|
18
19
|
import { gt } from 'semver';
|
|
19
20
|
import { stringify, parse } from 'yaml';
|
|
20
21
|
import fs from 'fs/promises';
|
|
@@ -36,9 +37,76 @@ function writeLedger(projectDir, ledger) {
|
|
|
36
37
|
writeFileSync(p, `${JSON.stringify(ledger, null, 2)}
|
|
37
38
|
`, "utf-8");
|
|
38
39
|
}
|
|
40
|
+
function bindInstance(projectDir, id, instance) {
|
|
41
|
+
const errors = [];
|
|
42
|
+
let srcDir;
|
|
43
|
+
try {
|
|
44
|
+
srcDir = resolveAgentDefinitionDir(projectDir, instance.ref);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
errors.push(`agent instance "${id}": ${err instanceof Error ? err.message : String(err)}`);
|
|
47
|
+
}
|
|
48
|
+
if (srcDir !== void 0 && !existsSync(join(srcDir, "agent.yaml"))) {
|
|
49
|
+
errors.push(
|
|
50
|
+
`agent instance "${id}": ${instance.ref} resolves to ${srcDir}, which has no agent.yaml`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return { srcDir, errors };
|
|
54
|
+
}
|
|
55
|
+
function unboundAgentInstances(projectDir, driverTarget) {
|
|
56
|
+
if (!driverTargetSupportsAgents(driverTarget)) return [];
|
|
57
|
+
let instances;
|
|
58
|
+
try {
|
|
59
|
+
instances = effectiveAgentInstances(resolveSkWorkspaceConfig(projectDir));
|
|
60
|
+
} catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const [id, instance] of Object.entries(instances)) {
|
|
65
|
+
const { errors } = bindInstance(projectDir, id, instance);
|
|
66
|
+
if (errors.length > 0) out.push({ id, ref: instance.ref, error: errors.join("; ") });
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function removeAgentInstanceRenderings(opts) {
|
|
71
|
+
const { projectDir, driverTarget, ids } = opts;
|
|
72
|
+
if (!driverTargetSupportsAgents(driverTarget) || ids.length === 0) return [];
|
|
73
|
+
const ledger = readLedger(projectDir);
|
|
74
|
+
const previous = ledger.targets[driverTarget];
|
|
75
|
+
if (!previous) return [];
|
|
76
|
+
const gone = new Set(ids);
|
|
77
|
+
const claimed = new Set(
|
|
78
|
+
Object.entries(previous).filter(([id]) => !gone.has(id)).flatMap(([, files]) => files)
|
|
79
|
+
);
|
|
80
|
+
const agentDeployDir = deployedBase("agent", driverTarget, false, projectDir);
|
|
81
|
+
const removable = [resolve(agentDeployDir), resolve(dirname(agentDeployDir), CODEX_IDENTITY_DIR)];
|
|
82
|
+
const root = resolve(projectDir);
|
|
83
|
+
const removed = [];
|
|
84
|
+
for (const id of gone) {
|
|
85
|
+
for (const rel of previous[id] ?? []) {
|
|
86
|
+
if (claimed.has(rel)) continue;
|
|
87
|
+
const abs = resolve(root, rel);
|
|
88
|
+
if (!removable.some((dir) => abs.startsWith(dir + sep)) || !existsSync(abs)) continue;
|
|
89
|
+
rmSync(abs, { recursive: true, force: true });
|
|
90
|
+
removed.push(rel);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const owned = Object.fromEntries(Object.entries(previous).filter(([id]) => !gone.has(id)));
|
|
94
|
+
const nextTargets = { ...ledger.targets };
|
|
95
|
+
if (Object.keys(owned).length > 0) nextTargets[driverTarget] = owned;
|
|
96
|
+
else delete nextTargets[driverTarget];
|
|
97
|
+
if (JSON.stringify(nextTargets) !== JSON.stringify(ledger.targets)) {
|
|
98
|
+
writeLedger(projectDir, { version: 1, targets: nextTargets });
|
|
99
|
+
}
|
|
100
|
+
return removed;
|
|
101
|
+
}
|
|
39
102
|
function installAgentInstances(opts) {
|
|
40
103
|
const { projectDir, driverTarget, log } = opts;
|
|
41
|
-
const result = {
|
|
104
|
+
const result = {
|
|
105
|
+
instances: [],
|
|
106
|
+
removed: [],
|
|
107
|
+
warnings: [],
|
|
108
|
+
errors: []
|
|
109
|
+
};
|
|
42
110
|
if (!driverTargetSupportsAgents(driverTarget)) return result;
|
|
43
111
|
const wsConfig = opts.wsConfig ?? resolveSkWorkspaceConfig(projectDir);
|
|
44
112
|
const instances = effectiveAgentInstances(wsConfig);
|
|
@@ -48,20 +116,9 @@ function installAgentInstances(opts) {
|
|
|
48
116
|
const previous = ledger.targets[driverTarget] ?? {};
|
|
49
117
|
const owned = {};
|
|
50
118
|
for (const [id, instance] of Object.entries(instances)) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
} catch (err) {
|
|
55
|
-
result.warnings.push(
|
|
56
|
-
`agent instance "${id}": ${err instanceof Error ? err.message : String(err)}`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
if (srcDir === void 0 || !existsSync(join(srcDir, "agent.yaml"))) {
|
|
60
|
-
if (srcDir !== void 0) {
|
|
61
|
-
result.warnings.push(
|
|
62
|
-
`agent instance "${id}": ${instance.ref} resolves to ${srcDir}, which has no agent.yaml`
|
|
63
|
-
);
|
|
64
|
-
}
|
|
119
|
+
const { srcDir, errors } = bindInstance(projectDir, id, instance);
|
|
120
|
+
result.errors.push(...errors);
|
|
121
|
+
if (srcDir === void 0 || errors.length > 0) {
|
|
65
122
|
if (previous[id]) owned[id] = previous[id];
|
|
66
123
|
continue;
|
|
67
124
|
}
|
|
@@ -273,7 +330,30 @@ async function renderMcpServerConfig(projectDir, driverTarget, opts) {
|
|
|
273
330
|
|
|
274
331
|
// asset-manager/src/index.ts
|
|
275
332
|
function normalizeRepoUrl(url) {
|
|
276
|
-
return url.trim().replace(/^git@([^:]+):/, "https://$1/").replace(/^[a-z]+:\/\//i, "").replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase();
|
|
333
|
+
return url.trim().replace(/^git@([^:]+):/, "https://$1/").replace(/^[a-z]+:\/\//i, "").replace(/\/+$/, "").replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase();
|
|
334
|
+
}
|
|
335
|
+
function withoutUrlSecrets(url) {
|
|
336
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) return url;
|
|
337
|
+
try {
|
|
338
|
+
const parsed = new URL(url);
|
|
339
|
+
if (parsed.protocol === "http:" || parsed.protocol === "https:" || parsed.password) {
|
|
340
|
+
parsed.username = "";
|
|
341
|
+
}
|
|
342
|
+
parsed.password = "";
|
|
343
|
+
parsed.search = "";
|
|
344
|
+
parsed.hash = "";
|
|
345
|
+
return parsed.toString();
|
|
346
|
+
} catch {
|
|
347
|
+
return url.replace(/[?#].*$/, "").replace(/^([a-z][a-z0-9+.-]*:\/\/).*@/i, "$1");
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function libraryRemoteUrl(library) {
|
|
351
|
+
const config = library.backendConfig;
|
|
352
|
+
if (library.backend !== "git" || !("url" in config) || typeof config.url !== "string") {
|
|
353
|
+
return void 0;
|
|
354
|
+
}
|
|
355
|
+
const url = config.url.trim();
|
|
356
|
+
return url ? withoutUrlSecrets(url) : void 0;
|
|
277
357
|
}
|
|
278
358
|
function storeOverlayPathForUrl(url) {
|
|
279
359
|
return join(getStoreManifestsDir(), `${storeManifestSlug(url)}.yaml`);
|
|
@@ -375,6 +455,26 @@ function libraryDiskHashes(libPath, cache, registry) {
|
|
|
375
455
|
}
|
|
376
456
|
return hashes;
|
|
377
457
|
}
|
|
458
|
+
function manifestFileHasAssetsArray(libPath) {
|
|
459
|
+
try {
|
|
460
|
+
const doc = parse(readFileSync(join(libPath, "skaile.manifest.yaml"), "utf8"));
|
|
461
|
+
return Array.isArray(doc?.assets);
|
|
462
|
+
} catch {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function isUnlistedLibraryAsset(entry, libPath) {
|
|
467
|
+
const manifest = loadSkPublishManifest(libPath);
|
|
468
|
+
const listed = manifest?.assets ?? [];
|
|
469
|
+
if (!manifest || manifest.source) return false;
|
|
470
|
+
if (listed.length === 0 && !manifestFileHasAssetsArray(libPath)) return false;
|
|
471
|
+
const file = resolve(entry.source);
|
|
472
|
+
return !listed.some((a) => {
|
|
473
|
+
if (!a.root || a.source) return false;
|
|
474
|
+
const root = resolve(libPath, a.root);
|
|
475
|
+
return file === root || file.startsWith(root + sep);
|
|
476
|
+
});
|
|
477
|
+
}
|
|
378
478
|
function withLibraryFiles(entry, libPath, cache, registry) {
|
|
379
479
|
let byManifest = cache.get(libPath);
|
|
380
480
|
if (!byManifest) {
|
|
@@ -539,6 +639,10 @@ function keptLockEntries(prev, missing) {
|
|
|
539
639
|
}
|
|
540
640
|
return out;
|
|
541
641
|
}
|
|
642
|
+
function presetsLockSlice(presets) {
|
|
643
|
+
const refs = [...new Set(presets.map((p) => p.ref))].sort();
|
|
644
|
+
return refs.length > 0 ? { presets: refs } : {};
|
|
645
|
+
}
|
|
542
646
|
function withKeptLockEntries(lock, kept) {
|
|
543
647
|
if (Object.keys(kept.assets).length === 0) return lock;
|
|
544
648
|
const sources = new Map(lock.sources.map((s) => [s.url, s]));
|
|
@@ -567,8 +671,45 @@ function relockedRefs(prev, next) {
|
|
|
567
671
|
}
|
|
568
672
|
return out;
|
|
569
673
|
}
|
|
570
|
-
function
|
|
571
|
-
|
|
674
|
+
function libraryMoves(prev, next) {
|
|
675
|
+
if (!prev) return [];
|
|
676
|
+
const out = [];
|
|
677
|
+
for (const [key, entry] of Object.entries(next)) {
|
|
678
|
+
const to = entry.library;
|
|
679
|
+
if (!to || to.installed) continue;
|
|
680
|
+
const id = parseCanonicalLockKey(key);
|
|
681
|
+
for (const [prevKey, before] of Object.entries(prev.assets)) {
|
|
682
|
+
const o = parseCanonicalLockKey(prevKey);
|
|
683
|
+
if (o.kind !== id.kind || o.name !== id.name || o.publisher !== id.publisher) continue;
|
|
684
|
+
const from = before.library;
|
|
685
|
+
if (!from || from.installed || from.name === to.name) continue;
|
|
686
|
+
out.push({ ref: `${id.kind}:@${id.publisher}/${id.name}`, from: from.name, to: to.name });
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return out;
|
|
691
|
+
}
|
|
692
|
+
function unregisteredLibraryFix(origin, ref) {
|
|
693
|
+
const register = origin.url ? `\`skaile library init ${origin.name} --git ${origin.url}\` (no registered library has the remote ${origin.url} either)` : `\`skaile library init ${origin.name} --path <dir>\``;
|
|
694
|
+
return `register library "${origin.name}" again with ${register}, or drop the dependency with \`skaile remove ${ref}\``;
|
|
695
|
+
}
|
|
696
|
+
function declaredNoHolderReason(ref) {
|
|
697
|
+
return `is declared but no library or source holds it \u2014 register the library that holds it with \`skaile library init <name> --path <dir>\`, or drop it with \`skaile remove ${ref.split("#")[0]}\``;
|
|
698
|
+
}
|
|
699
|
+
var REGISTER_HOLDER = "register the library that holds it with `skaile library init <name> --path <dir>`";
|
|
700
|
+
function requirementNoHolderFix(asset) {
|
|
701
|
+
return `${REGISTER_HOLDER}, or drop ${asset} with \`skaile remove ${asset}\``;
|
|
702
|
+
}
|
|
703
|
+
function sameAsset(ref, want) {
|
|
704
|
+
const p = parseRefTolerant(ref);
|
|
705
|
+
return p !== null && p.kind === want.kind && p.name === want.name && (!want.publisher || !p.publisher || p.publisher === want.publisher);
|
|
706
|
+
}
|
|
707
|
+
function lockedLibraryProblem(lookup, origin, ref) {
|
|
708
|
+
return lookup.state === "dir-missing" ? missingLibraryDirMessage({ name: origin.name, path: lookup.path }) : `library "${origin.name}" is not registered \u2014 renamed or removed? ${unregisteredLibraryFix(origin, ref)}`;
|
|
709
|
+
}
|
|
710
|
+
function gapIdentity(ref) {
|
|
711
|
+
const p = parseRefTolerant(ref);
|
|
712
|
+
return p ? `${p.kind}:${p.name}` : ref;
|
|
572
713
|
}
|
|
573
714
|
function memberIdentity(ref) {
|
|
574
715
|
const parsed = parseRefTolerant(ref.trim());
|
|
@@ -726,13 +867,15 @@ var AssetManager = class {
|
|
|
726
867
|
}
|
|
727
868
|
/** Deploy options for one harness. In global mode, `driverTarget` is the
|
|
728
869
|
* fan-out target (not the constructor's `driverTarget`, which is project-only). */
|
|
729
|
-
deployOptsFor(target, link) {
|
|
870
|
+
deployOptsFor(target, link, onSkippedSymlink, onEnvFile) {
|
|
730
871
|
return {
|
|
731
872
|
registry: this.registry,
|
|
732
873
|
driverTarget: target,
|
|
733
874
|
global: this.global,
|
|
734
875
|
cwd: this.projectDir,
|
|
735
|
-
link: link === true
|
|
876
|
+
link: link === true,
|
|
877
|
+
...onSkippedSymlink ? { onSkippedSymlink: (entry, p) => onSkippedSymlink(canonicalEntryRef(entry), p) } : {},
|
|
878
|
+
...onEnvFile ? { onEnvFile: (entry, p) => onEnvFile(canonicalEntryRef(entry), p) } : {}
|
|
736
879
|
};
|
|
737
880
|
}
|
|
738
881
|
/**
|
|
@@ -759,18 +902,27 @@ var AssetManager = class {
|
|
|
759
902
|
* Render every agent instance of `skaile.yaml` into the driver target's native
|
|
760
903
|
* agent files (`.claude/agents/<id>.md`, …) and prune the renderings of
|
|
761
904
|
* instances that left the config. Runs after the skills deploy, so composed
|
|
762
|
-
* skills resolve. Project scope only.
|
|
763
|
-
* is warned and swallowed.
|
|
905
|
+
* skills resolve. Project scope only. An instance whose `ref` resolves to no agent is
|
|
906
|
+
* reported in `agentInstanceErrors`; any other render error is warned and swallowed.
|
|
764
907
|
*/
|
|
765
|
-
renderAgentInstances() {
|
|
908
|
+
async renderAgentInstances() {
|
|
766
909
|
if (this.global) return {};
|
|
767
910
|
try {
|
|
768
911
|
const result = installAgentInstances({
|
|
769
912
|
projectDir: this.projectDir,
|
|
770
913
|
driverTarget: this.driverTarget
|
|
771
914
|
});
|
|
772
|
-
for (const
|
|
773
|
-
|
|
915
|
+
for (const u of unboundAgentInstances(this.projectDir, this.driverTarget)) {
|
|
916
|
+
if (!u.ref.startsWith("agent:")) continue;
|
|
917
|
+
const at = result.errors.findIndex((e) => e.startsWith(`agent instance "${u.id}":`));
|
|
918
|
+
if (at >= 0) result.errors[at] += ` \u2014 ${(await this.unboundAgentAdvice(u.ref)).advice}`;
|
|
919
|
+
}
|
|
920
|
+
for (const w of [...result.warnings, ...result.errors]) console.warn(`skaile: ${w}`);
|
|
921
|
+
return {
|
|
922
|
+
agentInstances: result.instances,
|
|
923
|
+
agentFilesRemoved: result.removed,
|
|
924
|
+
...result.errors.length > 0 ? { agentInstanceErrors: result.errors } : {}
|
|
925
|
+
};
|
|
774
926
|
} catch (err) {
|
|
775
927
|
console.warn(
|
|
776
928
|
`skaile: could not render agent instances for ${this.driverTarget}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -902,6 +1054,11 @@ var AssetManager = class {
|
|
|
902
1054
|
*
|
|
903
1055
|
* @param opts.quiet - Skip the cross-library collision warning (a lookup that only
|
|
904
1056
|
* builds an error hint must not warn).
|
|
1057
|
+
* @param opts.library - Resolve only from this library (the one a lock entry names), not
|
|
1058
|
+
* from every registered one. Never warns: no choice is made between libraries.
|
|
1059
|
+
* @param opts.lockedLibraries - `kind:publisher/name` → the library its lock entry names
|
|
1060
|
+
* ({@link AssetManager.lockedLibraryNames}). A ref listed here resolves from that library
|
|
1061
|
+
* when it holds a match, whatever the default library is.
|
|
905
1062
|
* @returns the matched deploy entries, the set of `missing` refs covered, and
|
|
906
1063
|
* each entry's originating library name.
|
|
907
1064
|
*/
|
|
@@ -914,6 +1071,7 @@ var AssetManager = class {
|
|
|
914
1071
|
const { byKey, isDefaultLib, libraryDir } = scan;
|
|
915
1072
|
const entries = [];
|
|
916
1073
|
const fileSets = /* @__PURE__ */ new Map();
|
|
1074
|
+
let snap;
|
|
917
1075
|
const allCandidates = [...byKey.values()].flat();
|
|
918
1076
|
for (const ref of missing) {
|
|
919
1077
|
const parsed = parseRefTolerant(ref);
|
|
@@ -921,21 +1079,54 @@ var AssetManager = class {
|
|
|
921
1079
|
const hash = ref.indexOf("#");
|
|
922
1080
|
const pin = parsed.pin ?? (hash >= 0 ? ref.slice(hash + 1) : void 0);
|
|
923
1081
|
const key = parsed.publisher ? `${parsed.kind}:@${parsed.publisher}/${parsed.name}` : void 0;
|
|
924
|
-
const
|
|
925
|
-
|
|
1082
|
+
const scanned = (key ? byKey.get(key) ?? [] : allCandidates.filter(
|
|
1083
|
+
(c) => c.entry.kind === parsed.kind && c.entry.name === parsed.name
|
|
1084
|
+
)).filter((c) => !opts.library || c.library === opts.library);
|
|
1085
|
+
const pool = [];
|
|
1086
|
+
for (const c of scanned) {
|
|
1087
|
+
if (c.entry.version) {
|
|
1088
|
+
pool.push(c);
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
if (snap === void 0) snap = await this.loadLibraryIndex();
|
|
1092
|
+
const def = newestLibraryDef(snap, c.library, {
|
|
1093
|
+
kind: c.entry.kind,
|
|
1094
|
+
publisher: c.entry.publisher ?? "",
|
|
1095
|
+
name: c.entry.name
|
|
1096
|
+
});
|
|
1097
|
+
pool.push(def?.version ? { ...c, entry: { ...c.entry, version: def.version } } : c);
|
|
1098
|
+
}
|
|
1099
|
+
let candidates = pool.filter((c) => !pin || matchPin(pin, c.entry.version ?? ""));
|
|
1100
|
+
let lockedLibrary;
|
|
1101
|
+
if (parsed.publisher) {
|
|
1102
|
+
lockedLibrary = opts.lockedLibraries?.get(
|
|
1103
|
+
`${parsed.kind}:${parsed.publisher.replace(/^@/, "")}/${parsed.name}`
|
|
1104
|
+
);
|
|
1105
|
+
} else {
|
|
1106
|
+
const same = [...opts.lockedLibraries ?? []].filter(([k]) => {
|
|
1107
|
+
const slash = k.indexOf("/");
|
|
1108
|
+
return k.startsWith(`${parsed.kind}:`) && k.slice(slash + 1) === parsed.name;
|
|
1109
|
+
});
|
|
1110
|
+
if (same.length === 1) lockedLibrary = same[0][1];
|
|
1111
|
+
}
|
|
1112
|
+
if (lockedLibrary) {
|
|
1113
|
+
const inLocked = candidates.filter((c) => c.library === lockedLibrary);
|
|
1114
|
+
if (inLocked.length > 0) candidates = inLocked;
|
|
1115
|
+
}
|
|
926
1116
|
if (candidates.length === 0) continue;
|
|
927
1117
|
candidates.sort(libraryPreference(isDefaultLib));
|
|
928
1118
|
const hit = candidates[0];
|
|
929
|
-
if (!opts.quiet) {
|
|
1119
|
+
if (!opts.quiet && !opts.library) {
|
|
930
1120
|
this.warnLibraryCollision(
|
|
931
1121
|
`${hit.entry.kind}:@${hit.entry.publisher}/${hit.entry.name}`,
|
|
932
1122
|
candidates,
|
|
933
1123
|
hit
|
|
934
1124
|
);
|
|
935
1125
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1126
|
+
const entry = hit.entry;
|
|
1127
|
+
withLibraryFiles(entry, libraryDir.get(hit.library), fileSets, this.registry);
|
|
1128
|
+
entries.push(entry);
|
|
1129
|
+
libraryOf.set(entry, hit.library);
|
|
939
1130
|
resolved.add(ref);
|
|
940
1131
|
}
|
|
941
1132
|
return { entries, resolved, libraryOf };
|
|
@@ -955,11 +1146,12 @@ var AssetManager = class {
|
|
|
955
1146
|
if (existsSync(l.path)) roots.push({ name: l.name, dir: l.path, isDefault: l.isDefault });
|
|
956
1147
|
}
|
|
957
1148
|
const registered = new Set(roots.map((r) => r.name));
|
|
1149
|
+
const unregistered = unregisteredLibraryNames();
|
|
958
1150
|
try {
|
|
959
1151
|
const librariesRoot = resolveLibraryDir();
|
|
960
1152
|
if (existsSync(librariesRoot)) {
|
|
961
1153
|
for (const dirent of readdirSync(librariesRoot, { withFileTypes: true })) {
|
|
962
|
-
if (dirent.isDirectory() && dirent.name !== GLOBAL_INSTALL_DIR_NAME && !registered.has(dirent.name)) {
|
|
1154
|
+
if (dirent.isDirectory() && dirent.name !== GLOBAL_INSTALL_DIR_NAME && !registered.has(dirent.name) && !unregistered.has(dirent.name)) {
|
|
963
1155
|
roots.push({
|
|
964
1156
|
name: dirent.name,
|
|
965
1157
|
dir: join(librariesRoot, dirent.name),
|
|
@@ -1012,10 +1204,117 @@ var AssetManager = class {
|
|
|
1012
1204
|
}
|
|
1013
1205
|
return out;
|
|
1014
1206
|
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Every asset a registered library holds, read from the library directories themselves
|
|
1209
|
+
* (not the local index, which keeps one row per ref and version and loses a ref when
|
|
1210
|
+
* another library re-publishes it). One row per holder, all versions.
|
|
1211
|
+
*
|
|
1212
|
+
* @returns `[]` when no library holds an asset. Never warns.
|
|
1213
|
+
*/
|
|
1214
|
+
async libraryAssets() {
|
|
1215
|
+
const scan = await this.scanLibraries();
|
|
1216
|
+
if (!scan) return [];
|
|
1217
|
+
return [...scan.byKey.values()].flat();
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Every origin that holds `ref` (`kind:@publisher/name`) with the version it holds, in
|
|
1221
|
+
* the order `add <ref>` prefers them, so the first row is the one `add` deploys: the
|
|
1222
|
+
* project's source clones and the bundled factory source (highest version first), then
|
|
1223
|
+
* the registered libraries. `add`, `info` and `search` all read this, so the three name
|
|
1224
|
+
* one origin for one ref. Stores are not asked (no network).
|
|
1225
|
+
*
|
|
1226
|
+
* @returns one row per origin and version; `[]` for a ref without a publisher or one
|
|
1227
|
+
* nothing holds. Never warns.
|
|
1228
|
+
*/
|
|
1229
|
+
async refOrigins(ref) {
|
|
1230
|
+
const parsed = parseRefTolerant(ref);
|
|
1231
|
+
if (!parsed?.publisher) return [];
|
|
1232
|
+
const key = `${parsed.kind}:@${parsed.publisher}/${parsed.name}`;
|
|
1233
|
+
return (await this.collectOrigins(key)).get(key) ?? [];
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Every ref (`kind:@publisher/name`) that several origins hold at different versions,
|
|
1237
|
+
* with its origins in {@link refOrigins} order. A ref the origins hold at one version
|
|
1238
|
+
* has nothing to choose between and is left out.
|
|
1239
|
+
*/
|
|
1240
|
+
async competingOrigins() {
|
|
1241
|
+
const out = /* @__PURE__ */ new Map();
|
|
1242
|
+
for (const [key, origins] of await this.collectOrigins()) {
|
|
1243
|
+
if (new Set(origins.map((o) => o.version)).size > 1) out.set(key, origins);
|
|
1244
|
+
}
|
|
1245
|
+
return out;
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* The origins of {@link refOrigins} for every ref, or for the one `only` names
|
|
1249
|
+
* (`kind:@publisher/name`), keyed by `kind:@publisher/name`.
|
|
1250
|
+
*/
|
|
1251
|
+
async collectOrigins(only) {
|
|
1252
|
+
const { repositories, sources } = this.loadConfig();
|
|
1253
|
+
this.ensureSourceClonesQuiet(repositories, sources);
|
|
1254
|
+
const clones = this.buildClones(repositories);
|
|
1255
|
+
const cloneDirs = new Set(clones.map((c) => resolve(c.localPath)));
|
|
1256
|
+
const index = await buildProvenanceIndex(clones, {
|
|
1257
|
+
projectDir: this.projectDir,
|
|
1258
|
+
knownKinds: this.knownKinds
|
|
1259
|
+
});
|
|
1260
|
+
const factoryUrl = normalizeRepoUrl(FACTORY_SOURCE_URL);
|
|
1261
|
+
const hasFactory = Object.values(repositories).some((d) => d.factory);
|
|
1262
|
+
const out = /* @__PURE__ */ new Map();
|
|
1263
|
+
const originsOf = (key) => {
|
|
1264
|
+
let list = out.get(key);
|
|
1265
|
+
if (!list) {
|
|
1266
|
+
list = [];
|
|
1267
|
+
out.set(key, list);
|
|
1268
|
+
}
|
|
1269
|
+
return list;
|
|
1270
|
+
};
|
|
1271
|
+
for (const candidates of index.values()) {
|
|
1272
|
+
const first = candidates[0];
|
|
1273
|
+
if (!first) continue;
|
|
1274
|
+
const key = `${first.kind}:@${first.publisher}/${first.name}`;
|
|
1275
|
+
if (only !== void 0 && key !== only) continue;
|
|
1276
|
+
const held = [...candidates].sort((a, b) => {
|
|
1277
|
+
const va = coerceVersion(a.version);
|
|
1278
|
+
const vb = coerceVersion(b.version);
|
|
1279
|
+
if (gt(va, vb)) return -1;
|
|
1280
|
+
if (gt(vb, va)) return 1;
|
|
1281
|
+
return a.sourceUrl.localeCompare(b.sourceUrl);
|
|
1282
|
+
});
|
|
1283
|
+
for (const c of held) {
|
|
1284
|
+
const factory = hasFactory && normalizeRepoUrl(c.sourceUrl) === factoryUrl;
|
|
1285
|
+
originsOf(key).push({
|
|
1286
|
+
kind: factory ? "factory" : "source",
|
|
1287
|
+
label: factory ? "factory" : sourceShortName(c.sourceUrl),
|
|
1288
|
+
version: c.version,
|
|
1289
|
+
description: c.description ?? ""
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
const scan = await this.scanLibraries();
|
|
1294
|
+
const prefer = libraryPreference(scan?.isDefaultLib ?? /* @__PURE__ */ new Map());
|
|
1295
|
+
for (const [key, holders] of scan?.byKey ?? []) {
|
|
1296
|
+
if (only !== void 0 && key !== only) continue;
|
|
1297
|
+
for (const c of [...holders].sort(prefer)) {
|
|
1298
|
+
const dir = scan?.libraryDir.get(c.library);
|
|
1299
|
+
if (dir && cloneDirs.has(resolve(dir))) continue;
|
|
1300
|
+
originsOf(key).push({
|
|
1301
|
+
kind: "library",
|
|
1302
|
+
label: c.library,
|
|
1303
|
+
version: c.entry.version ?? "",
|
|
1304
|
+
description: c.entry.description ?? ""
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return out;
|
|
1309
|
+
}
|
|
1015
1310
|
/**
|
|
1016
1311
|
* Where each missed publisher-qualified ref went when its publisher changed, for every
|
|
1017
1312
|
* ref at once (one library scan, one lock read):
|
|
1018
|
-
* - `moved` when a registered library holds the same kind + name under another publisher
|
|
1313
|
+
* - `moved` when a registered library holds the same kind + name under another publisher.
|
|
1314
|
+
* When the lock ties the missed ref to a library, only that library is looked at (an
|
|
1315
|
+
* asset of the same name in another library is a different asset). Without a lock
|
|
1316
|
+
* entry, a library asset the project already declares or locks under its own ref is
|
|
1317
|
+
* not offered, and two candidate publishers are ambiguous: no hint;
|
|
1019
1318
|
* - `lockedAs` when the lock holds the same kind + name under another publisher, records
|
|
1020
1319
|
* the library it came from, and that library now holds the asset under yet another
|
|
1021
1320
|
* publisher than the locked one. An unrelated asset that merely shares kind + name does
|
|
@@ -1035,11 +1334,28 @@ var AssetManager = class {
|
|
|
1035
1334
|
}
|
|
1036
1335
|
if (wanted.size === 0) return out;
|
|
1037
1336
|
const lockEntries = Object.entries(this.tryReadLock()?.assets ?? {});
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1337
|
+
const scan = await this.scanLibraries();
|
|
1338
|
+
if (!scan) return out;
|
|
1339
|
+
const held = /* @__PURE__ */ new Set();
|
|
1340
|
+
for (const [key] of lockEntries) {
|
|
1341
|
+
const { publisher, kind, name } = parseCanonicalLockKey(key);
|
|
1342
|
+
if (publisher && kind && name) held.add(`${publisher}/${kind}:${name}`);
|
|
1343
|
+
}
|
|
1344
|
+
for (const d of this.loadConfig().dependencies) {
|
|
1345
|
+
const id = refIdentity(d);
|
|
1346
|
+
if (id !== null) held.add(id);
|
|
1347
|
+
}
|
|
1040
1348
|
for (const [ref, w] of wanted) {
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1349
|
+
const origin = lockEntries.map(([key, e]) => ({ id: parseCanonicalLockKey(key), library: e.library })).find(
|
|
1350
|
+
({ id, library }) => library && !library.installed && id.kind === w.kind && id.name === w.name && id.publisher === w.publisher
|
|
1351
|
+
)?.library?.name;
|
|
1352
|
+
const pool = [...scan.byKey.values()].flat().filter(
|
|
1353
|
+
(c) => c.entry.kind === w.kind && c.entry.name === w.name && c.entry.publisher !== w.publisher && (origin ? c.library === origin : !held.has(`${c.entry.publisher}/${c.entry.kind}:${c.entry.name}`))
|
|
1354
|
+
);
|
|
1355
|
+
const publishers = new Set(pool.map((c) => c.entry.publisher));
|
|
1356
|
+
const best = publishers.size === 1 ? pool.sort(libraryPreference(scan.isDefaultLib))[0] : void 0;
|
|
1357
|
+
const moved = best?.entry;
|
|
1358
|
+
const movedLibrary = best?.library;
|
|
1043
1359
|
const finding = {};
|
|
1044
1360
|
for (const [key, entry] of lockEntries) {
|
|
1045
1361
|
const id = parseCanonicalLockKey(key);
|
|
@@ -1104,17 +1420,20 @@ var AssetManager = class {
|
|
|
1104
1420
|
if (!origin) continue;
|
|
1105
1421
|
const plain = want.publisher ? `${want.kind}:@${want.publisher}/${want.name}` : `${want.kind}:${want.name}`;
|
|
1106
1422
|
const moved = findings.get(ref)?.moved;
|
|
1423
|
+
const lookup = this.lockedLibrary(origin, snap);
|
|
1107
1424
|
if (moved?.library && want.publisher) {
|
|
1108
1425
|
out.set(
|
|
1109
1426
|
ref,
|
|
1110
1427
|
`did you mean ${moved.ref}? Change the ref in skaile.yaml to it, or keep the old ref with \`skaile library set ${moved.library} publisher=${want.publisher}\``
|
|
1111
1428
|
);
|
|
1112
|
-
} else if (
|
|
1113
|
-
out.set(ref,
|
|
1429
|
+
} else if (lookup.state === "dir-missing") {
|
|
1430
|
+
out.set(ref, lockedLibraryProblem(lookup, origin, plain));
|
|
1431
|
+
} else if (lookup.state === "unregistered") {
|
|
1432
|
+
out.set(ref, unregisteredLibraryFix(origin, plain));
|
|
1114
1433
|
} else {
|
|
1115
1434
|
out.set(
|
|
1116
1435
|
ref,
|
|
1117
|
-
`library "${
|
|
1436
|
+
`library "${lookup.name}" no longer holds it \u2014 restore it there, or drop the dependency with \`skaile remove ${plain}\``
|
|
1118
1437
|
);
|
|
1119
1438
|
}
|
|
1120
1439
|
}
|
|
@@ -1214,27 +1533,120 @@ var AssetManager = class {
|
|
|
1214
1533
|
* preferring it would lock a stale version after an in-place bump. The hash is
|
|
1215
1534
|
* computed from the files on disk, so it matches what `install --locked`
|
|
1216
1535
|
* re-hashes.
|
|
1536
|
+
*
|
|
1537
|
+
* A git-backed library also records its remote URL. When the previous lock names the
|
|
1538
|
+
* library under another name that {@link lockedLibrary} matched to this one by URL (a
|
|
1539
|
+
* teammate registered the same remote under a different name), the entry keeps the locked
|
|
1540
|
+
* name and URL: the lock does not change because it was installed on another machine.
|
|
1217
1541
|
*/
|
|
1218
1542
|
async libraryLockEntries(lib) {
|
|
1219
1543
|
if (lib.entries.length === 0) return {};
|
|
1220
1544
|
const snap = await this.loadLibraryIndex();
|
|
1545
|
+
const previous = Object.entries(this.tryReadLock()?.assets ?? {}).filter(
|
|
1546
|
+
([, e]) => e.library !== void 0 && !e.library.installed
|
|
1547
|
+
);
|
|
1221
1548
|
const diskHashes = /* @__PURE__ */ new Map();
|
|
1222
1549
|
return buildLibraryLockEntries(
|
|
1223
1550
|
lib.entries.map((e) => {
|
|
1224
1551
|
const name = lib.libraryOf.get(e) ?? "";
|
|
1225
1552
|
const publisher = e.publisher ?? "";
|
|
1226
1553
|
const def = newestLibraryDef(snap, name, { kind: e.kind, publisher, name: e.name });
|
|
1227
|
-
const libPath = this.libraryPath(name, snap);
|
|
1554
|
+
const libPath = this.libraryPath({ name }, snap);
|
|
1228
1555
|
const sha256 = (libPath ? libraryDiskHashes(libPath, diskHashes, this.registry).get(`${e.kind}:${e.name}`) : void 0) ?? def?.sha256;
|
|
1556
|
+
const registered = snap?.libraries.find((l) => l.name === name);
|
|
1557
|
+
let lockName = name;
|
|
1558
|
+
let url = registered ? libraryRemoteUrl(registered) : void 0;
|
|
1559
|
+
const locked = previous.find(([key]) => {
|
|
1560
|
+
const id = parseCanonicalLockKey(key);
|
|
1561
|
+
return id.kind === e.kind && id.name === e.name && id.publisher === publisher;
|
|
1562
|
+
})?.[1].library;
|
|
1563
|
+
if (locked) {
|
|
1564
|
+
const lookup = this.lockedLibrary(locked, snap);
|
|
1565
|
+
if (lookup.state === "ok" && lookup.name === name) {
|
|
1566
|
+
lockName = locked.name;
|
|
1567
|
+
if (locked.url && url && normalizeRepoUrl(locked.url) === normalizeRepoUrl(url)) {
|
|
1568
|
+
url = locked.url;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1229
1572
|
return {
|
|
1230
1573
|
kind: e.kind,
|
|
1231
1574
|
publisher,
|
|
1232
1575
|
name: e.name,
|
|
1233
|
-
library: {
|
|
1576
|
+
library: {
|
|
1577
|
+
name: lockName,
|
|
1578
|
+
version: e.version || def?.version || "0.0.0",
|
|
1579
|
+
sha256,
|
|
1580
|
+
...url ? { url } : {}
|
|
1581
|
+
}
|
|
1234
1582
|
};
|
|
1235
1583
|
})
|
|
1236
1584
|
);
|
|
1237
1585
|
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Lock keys that `skaile.yaml` still wants: the entries a declared ref names, plus every
|
|
1588
|
+
* entry such an asset requires, transitively. All keys when a `preset:` is declared.
|
|
1589
|
+
*/
|
|
1590
|
+
async declaredLockKeys(lock, declared) {
|
|
1591
|
+
const keys = Object.keys(lock.assets);
|
|
1592
|
+
const wanted = declared.map((d) => parseRefTolerant(d.split(" ")[0] ?? d));
|
|
1593
|
+
if (wanted.some((w) => w?.kind === "preset")) return new Set(keys);
|
|
1594
|
+
const matching = (want) => keys.filter((key) => {
|
|
1595
|
+
const id = parseCanonicalLockKey(key);
|
|
1596
|
+
return id.kind === want.kind && id.name === want.name && (!want.publisher || !id.publisher || id.publisher === want.publisher);
|
|
1597
|
+
});
|
|
1598
|
+
const inScope = /* @__PURE__ */ new Set();
|
|
1599
|
+
const queue = [];
|
|
1600
|
+
const add = (key) => {
|
|
1601
|
+
if (inScope.has(key)) return;
|
|
1602
|
+
inScope.add(key);
|
|
1603
|
+
queue.push(key);
|
|
1604
|
+
};
|
|
1605
|
+
for (const w of wanted) if (w) matching(w).forEach(add);
|
|
1606
|
+
for (const key of queue) {
|
|
1607
|
+
for (const dep of await this.deployedRequirementsOf(parseCanonicalLockKey(key))) {
|
|
1608
|
+
const want = memberIdentity(dep);
|
|
1609
|
+
if (want) matching(want).forEach(add);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
return inScope;
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* The `Missing:` lines of every library-origin lock entry whose library cannot supply
|
|
1616
|
+
* assets on this machine (not registered, or registered with its directory gone). Plain
|
|
1617
|
+
* `install` stops on a non-empty list, the same lines `install --locked` prints: it must
|
|
1618
|
+
* not re-resolve a locked asset from another origin. The ONE function that decides "the
|
|
1619
|
+
* locked library is not here", so a lock-recorded URL fallback extends it in one place.
|
|
1620
|
+
*
|
|
1621
|
+
* Only entries `skaile.yaml` still declares, or that a declared asset requires
|
|
1622
|
+
* (transitively, read from the deployed copy), count. A stale entry nothing declares is
|
|
1623
|
+
* pruned by `install` as usual, so a removed dependency whose library is gone can be
|
|
1624
|
+
* cleaned up. With a declared `preset:` every entry counts: the lock records no edge
|
|
1625
|
+
* from a preset to its items, and the preset's own library may be the one that is gone.
|
|
1626
|
+
*
|
|
1627
|
+
* @param declared - The `dependencies:` refs as written in `skaile.yaml`.
|
|
1628
|
+
*/
|
|
1629
|
+
async unavailableLockedLibraries(lock, declared) {
|
|
1630
|
+
const inScope = await this.declaredLockKeys(lock, declared);
|
|
1631
|
+
const locked = Object.entries(lock.assets).filter(
|
|
1632
|
+
([key, e]) => e.library !== void 0 && !e.library.installed && inScope.has(key)
|
|
1633
|
+
);
|
|
1634
|
+
if (locked.length === 0) return [];
|
|
1635
|
+
const snap = await this.loadLibraryIndex();
|
|
1636
|
+
const lines = [];
|
|
1637
|
+
for (const [ref, e] of locked) {
|
|
1638
|
+
const { publisher, kind, name } = parseCanonicalLockKey(ref);
|
|
1639
|
+
if (!publisher || !kind || !name) continue;
|
|
1640
|
+
const lookup = this.lockedLibrary(e.library, snap);
|
|
1641
|
+
if (lookup.state === "ok") continue;
|
|
1642
|
+
const component = this.installTarget(kind);
|
|
1643
|
+
const deployed = component !== void 0 && isDeployed(component, name, this.driverTarget, this.global, this.projectDir);
|
|
1644
|
+
const problem = lockedLibraryProblem(lookup, e.library, `${kind}:@${publisher}/${name}`);
|
|
1645
|
+
const kept = deployed ? "the deployed copy and its lock entry are kept" : "its lock entry is kept; no deployed copy exists";
|
|
1646
|
+
lines.push(`${ref} (${problem}; ${kept})`);
|
|
1647
|
+
}
|
|
1648
|
+
return lines;
|
|
1649
|
+
}
|
|
1238
1650
|
/**
|
|
1239
1651
|
* Re-resolve every library-origin lock entry from its named library, for
|
|
1240
1652
|
* `install --locked`. An entry is restored only when its library is still
|
|
@@ -1258,25 +1670,26 @@ var AssetManager = class {
|
|
|
1258
1670
|
missing.push(`${ref} (unparseable lock key)`);
|
|
1259
1671
|
continue;
|
|
1260
1672
|
}
|
|
1261
|
-
const
|
|
1262
|
-
if (
|
|
1673
|
+
const lookup = this.lockedLibrary(origin, snap);
|
|
1674
|
+
if (lookup.state !== "ok") {
|
|
1263
1675
|
missing.push(
|
|
1264
|
-
`${ref} (
|
|
1676
|
+
`${ref} (${lockedLibraryProblem(lookup, origin, `${kind}:@${publisher}/${name}`)})`
|
|
1265
1677
|
);
|
|
1266
1678
|
continue;
|
|
1267
1679
|
}
|
|
1680
|
+
const libPath = lookup.path;
|
|
1268
1681
|
const found = (await scanDirectory(libPath, publisher, { knownKinds: this.knownKinds })).find(
|
|
1269
1682
|
(x) => x.kind === kind && x.name === name
|
|
1270
1683
|
);
|
|
1271
1684
|
if (!found) {
|
|
1272
|
-
missing.push(`${ref} (not found in library "${
|
|
1685
|
+
missing.push(`${ref} (not found in library "${lookup.name}")`);
|
|
1273
1686
|
continue;
|
|
1274
1687
|
}
|
|
1275
|
-
const def = newestLibraryDef(snap,
|
|
1688
|
+
const def = newestLibraryDef(snap, lookup.name, { kind, publisher, name });
|
|
1276
1689
|
const current = found.version || def?.version || "0.0.0";
|
|
1277
1690
|
if (current !== origin.version) {
|
|
1278
1691
|
missing.push(
|
|
1279
|
-
`${ref} (library "${
|
|
1692
|
+
`${ref} (library "${lookup.name}" is at ${current}, locked ${origin.version})`
|
|
1280
1693
|
);
|
|
1281
1694
|
continue;
|
|
1282
1695
|
}
|
|
@@ -1284,29 +1697,108 @@ var AssetManager = class {
|
|
|
1284
1697
|
const onDisk = libraryDiskHashes(libPath, diskHashes, this.registry).get(`${kind}:${name}`);
|
|
1285
1698
|
if (onDisk !== void 0 && onDisk !== origin.sha256) {
|
|
1286
1699
|
missing.push(
|
|
1287
|
-
`${ref} (library "${
|
|
1700
|
+
`${ref} (library "${lookup.name}" content changed since lock \u2014 or the lock predates the one shared asset hash; re-lock with \`skaile install\`)`
|
|
1288
1701
|
);
|
|
1289
1702
|
continue;
|
|
1290
1703
|
}
|
|
1291
1704
|
}
|
|
1705
|
+
found.version = current;
|
|
1292
1706
|
entries.push(withLibraryFiles(found, libPath, fileSets, this.registry));
|
|
1293
1707
|
}
|
|
1294
1708
|
return { entries, missing };
|
|
1295
1709
|
}
|
|
1296
1710
|
/**
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1711
|
+
* Whether a deployed copy exists for the lock entries `install` kept for the unresolved
|
|
1712
|
+
* `ref`. A kind with no install target has no deployed copy.
|
|
1713
|
+
*/
|
|
1714
|
+
keptCopyDeployed(kept, ref) {
|
|
1715
|
+
const want = parseRefTolerant(ref.split(" ")[0] ?? ref);
|
|
1716
|
+
if (!want) return false;
|
|
1717
|
+
return Object.keys(kept.assets).some((key) => {
|
|
1718
|
+
const id = parseCanonicalLockKey(key);
|
|
1719
|
+
if (id.kind !== want.kind || id.name !== want.name) return false;
|
|
1720
|
+
if (want.publisher && id.publisher !== want.publisher) return false;
|
|
1721
|
+
const component = this.installTarget(id.kind);
|
|
1722
|
+
return component !== void 0 && isDeployed(component, id.name, this.driverTarget, this.global, this.projectDir);
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* How the library `origin` names stands on this machine. THE one lookup of a locked library
|
|
1727
|
+
* ("is it registered here?"): `check`, `outdated`, `install` and `install --locked` all ask it.
|
|
1728
|
+
*
|
|
1729
|
+
* - registered in the index under its name: `ok` at its registered path, or `dir-missing` when
|
|
1730
|
+
* that directory is gone;
|
|
1731
|
+
* - not registered: a directory `<libraries dir>/<name>` nobody unregistered still counts as a
|
|
1732
|
+
* hand-made library (`ok`). One `library remove` unregistered without `--purge` does not
|
|
1733
|
+
* (it stays on disk but no longer resolves), and neither does the reserved `installed` folder.
|
|
1734
|
+
* - otherwise, when the lock recorded the library's remote URL: another registered library
|
|
1735
|
+
* whose git remote is the same repository (`.git` suffix, trailing `/` and the
|
|
1736
|
+
* `git@host:o/r` / `https://host/o/r` forms do not matter) and whose directory exists is
|
|
1737
|
+
* `ok`, with `name` set to the name it is registered under here. The library name is
|
|
1738
|
+
* machine-local; the remote is what a clone shares.
|
|
1299
1739
|
*/
|
|
1300
|
-
|
|
1301
|
-
|
|
1740
|
+
lockedLibrary(origin, snap) {
|
|
1741
|
+
const { name } = origin;
|
|
1742
|
+
if (name === GLOBAL_INSTALL_DIR_NAME) return { state: "unregistered" };
|
|
1743
|
+
let byName = { state: "unregistered" };
|
|
1302
1744
|
const registered = snap?.libraries.find((l) => l.name === name)?.path;
|
|
1303
|
-
if (registered
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1745
|
+
if (registered) {
|
|
1746
|
+
byName = existsSync(registered) ? { state: "ok", path: registered, name } : { state: "dir-missing", path: registered };
|
|
1747
|
+
} else {
|
|
1748
|
+
try {
|
|
1749
|
+
const guess = join(resolveLibraryDir(), name);
|
|
1750
|
+
if (existsSync(guess) && !unregisteredLibraryNames().has(name)) {
|
|
1751
|
+
byName = { state: "ok", path: guess, name };
|
|
1752
|
+
}
|
|
1753
|
+
} catch {
|
|
1754
|
+
}
|
|
1309
1755
|
}
|
|
1756
|
+
if (byName.state === "unregistered") {
|
|
1757
|
+
const renamed = renamedLibraryName(name);
|
|
1758
|
+
const now = renamed ? snap?.libraries.find((l) => l.name === renamed) : void 0;
|
|
1759
|
+
if (now && existsSync(now.path)) return { state: "ok", path: now.path, name: now.name };
|
|
1760
|
+
}
|
|
1761
|
+
if (byName.state === "ok" || !origin.url) return byName;
|
|
1762
|
+
const wanted = normalizeRepoUrl(origin.url);
|
|
1763
|
+
const byUrl = snap?.libraries.find((l) => {
|
|
1764
|
+
const url = libraryRemoteUrl(l);
|
|
1765
|
+
return l.name !== name && url !== void 0 && normalizeRepoUrl(url) === wanted && existsSync(l.path);
|
|
1766
|
+
});
|
|
1767
|
+
return byUrl ? { state: "ok", path: byUrl.path, name: byUrl.name } : byName;
|
|
1768
|
+
}
|
|
1769
|
+
/** On-disk root of the library `origin` names; `null` unless {@link lockedLibrary} says `ok`. */
|
|
1770
|
+
libraryPath(origin, snap) {
|
|
1771
|
+
const found = this.lockedLibrary(origin, snap);
|
|
1772
|
+
return found.state === "ok" ? found.path : null;
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Where each library-origin lock entry of this project resolves on this machine: identity
|
|
1776
|
+
* `kind:publisher/name` (no scope sigil, no version) → the name the locked library is
|
|
1777
|
+
* registered under here. The name differs from the locked one when the lock's remote URL
|
|
1778
|
+
* matched another registered library ({@link lockedLibrary}). A deployed entry whose library
|
|
1779
|
+
* resolves nowhere (not registered, directory gone) maps to `null`.
|
|
1780
|
+
*
|
|
1781
|
+
* Direct `asset install` entries (`library.installed`) are included and resolved the same
|
|
1782
|
+
* way, but they map to the locked library name when that lookup fails: such a copy sits in
|
|
1783
|
+
* the project and does not depend on the library still being registered. A missing or
|
|
1784
|
+
* unreadable lock yields an empty map. `skaile manage` uses it to tick the right library row.
|
|
1785
|
+
*/
|
|
1786
|
+
async lockedLibraryNames() {
|
|
1787
|
+
const out = /* @__PURE__ */ new Map();
|
|
1788
|
+
const locked = Object.entries(this.tryReadLock()?.assets ?? {}).filter(
|
|
1789
|
+
([, e]) => e.library !== void 0
|
|
1790
|
+
);
|
|
1791
|
+
if (locked.length === 0) return out;
|
|
1792
|
+
const snap = await this.loadLibraryIndex();
|
|
1793
|
+
for (const [key, e] of locked) {
|
|
1794
|
+
const { publisher, kind, name } = parseCanonicalLockKey(key);
|
|
1795
|
+
if (!publisher || !kind || !name) continue;
|
|
1796
|
+
const origin = e.library;
|
|
1797
|
+
const lookup = this.lockedLibrary(origin, snap);
|
|
1798
|
+
const resolved = lookup.state === "ok" ? lookup.name : origin.installed ? origin.name : null;
|
|
1799
|
+
out.set(`${kind}:${publisher}/${name}`, resolved);
|
|
1800
|
+
}
|
|
1801
|
+
return out;
|
|
1310
1802
|
}
|
|
1311
1803
|
// ── Store fetch-then-deploy ────────────────────────────────────────────
|
|
1312
1804
|
/** A candidate's stable identity key (`<publisher>/<kind>:<name>@<version>`). */
|
|
@@ -1443,7 +1935,10 @@ var AssetManager = class {
|
|
|
1443
1935
|
return id !== null && (opts.provenanceIndex.get(id)?.length ?? 0) === 0;
|
|
1444
1936
|
})
|
|
1445
1937
|
);
|
|
1446
|
-
const hit = await this.resolveLibraryEntries([...libraryTried]
|
|
1938
|
+
const hit = await this.resolveLibraryEntries([...libraryTried], {
|
|
1939
|
+
quiet: !opts.warnCollisions,
|
|
1940
|
+
lockedLibraries: opts.lockedLibraries
|
|
1941
|
+
});
|
|
1447
1942
|
const next = absorb(hit);
|
|
1448
1943
|
const rest = fresh.filter((ref) => !hit.resolved.has(ref));
|
|
1449
1944
|
if (rest.length > 0) {
|
|
@@ -1458,7 +1953,8 @@ var AssetManager = class {
|
|
|
1458
1953
|
for (const o of r.overridesApplied) overridesApplied.add(o);
|
|
1459
1954
|
presets.push(...r.presets);
|
|
1460
1955
|
const hit2 = await this.resolveLibraryEntries(
|
|
1461
|
-
r.missing.filter((m) => !libraryTried.has(m))
|
|
1956
|
+
r.missing.filter((m) => !libraryTried.has(m)),
|
|
1957
|
+
{ quiet: !opts.warnCollisions, lockedLibraries: opts.lockedLibraries }
|
|
1462
1958
|
);
|
|
1463
1959
|
next.push(...absorb(hit2));
|
|
1464
1960
|
for (const m of r.missing) if (!hit2.resolved.has(m)) missing.add(m);
|
|
@@ -1631,6 +2127,9 @@ var AssetManager = class {
|
|
|
1631
2127
|
* @param opts.link - When `true`, symlink local-path assets instead of copying them
|
|
1632
2128
|
* (the author dev-loop). Machine-local / not for committing; a no-op for
|
|
1633
2129
|
* store/factory sources. Defaults to copy.
|
|
2130
|
+
* @param opts.moveLibraries - When `true` (`skaile update`), a locked library asset is
|
|
2131
|
+
* re-resolved from every library and may move to another one; {@link InstallResult.moved}
|
|
2132
|
+
* lists the moves. Without it a locked asset stays on the library its lock entry names.
|
|
1634
2133
|
* @returns {@link InstallResult} summarising what was deployed, what was missing, and any collisions.
|
|
1635
2134
|
*/
|
|
1636
2135
|
async install(opts) {
|
|
@@ -1638,6 +2137,20 @@ var AssetManager = class {
|
|
|
1638
2137
|
if (opts?.locked) {
|
|
1639
2138
|
return this.installLocked(repositories, patches, dependencies);
|
|
1640
2139
|
}
|
|
2140
|
+
const lockForLibraries = this.tryReadLock();
|
|
2141
|
+
if (lockForLibraries) {
|
|
2142
|
+
const unavailable = await this.unavailableLockedLibraries(lockForLibraries, dependencies);
|
|
2143
|
+
if (unavailable.length > 0) {
|
|
2144
|
+
return {
|
|
2145
|
+
deployed: [],
|
|
2146
|
+
removed: [],
|
|
2147
|
+
missing: unavailable,
|
|
2148
|
+
lockWritten: false,
|
|
2149
|
+
npmInstalled: [],
|
|
2150
|
+
npmFailed: []
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
1641
2154
|
const cloneError = this.ensureSourceClones(repositories, sources);
|
|
1642
2155
|
if (cloneError) return cloneError;
|
|
1643
2156
|
const provenanceIndex = await buildProvenanceIndex(this.buildClones(repositories), {
|
|
@@ -1646,7 +2159,15 @@ var AssetManager = class {
|
|
|
1646
2159
|
});
|
|
1647
2160
|
const { resolved, missing, overridesApplied, lib, presets } = await this.resolveLibraryFirst(
|
|
1648
2161
|
dependencies,
|
|
1649
|
-
{
|
|
2162
|
+
{
|
|
2163
|
+
provenanceIndex,
|
|
2164
|
+
overrides,
|
|
2165
|
+
stores,
|
|
2166
|
+
declaredRefs: dependencies,
|
|
2167
|
+
// `install` restores what the lock recorded: a changed default library or a second
|
|
2168
|
+
// library holding the same ref does not move a locked asset. `update` re-resolves.
|
|
2169
|
+
...opts?.moveLibraries ? {} : { lockedLibraries: await this.lockedLibraryNames() }
|
|
2170
|
+
}
|
|
1650
2171
|
);
|
|
1651
2172
|
this.assertNoFolderCollision([...resolved, ...lib.entries.map((e) => this.entryIdentity(e))]);
|
|
1652
2173
|
const stillMissing = missing.filter((m) => !lib.resolved.has(m));
|
|
@@ -1657,20 +2178,24 @@ var AssetManager = class {
|
|
|
1657
2178
|
for (const ref of /* @__PURE__ */ new Set([...kept.refs, ...fixes.keys()])) {
|
|
1658
2179
|
const fix = fixes.get(ref);
|
|
1659
2180
|
const notes = [
|
|
1660
|
-
kept.refs.has(ref) ? "the deployed copy and its lock entry are kept" : void 0,
|
|
2181
|
+
kept.refs.has(ref) ? this.keptCopyDeployed(kept, ref) ? "the deployed copy and its lock entry are kept" : "its lock entry is kept; no deployed copy exists" : void 0,
|
|
1661
2182
|
fix
|
|
1662
2183
|
].filter((n) => n !== void 0);
|
|
1663
2184
|
missingHints[ref] = `${fix ? "" : missingHints[ref] ?? ""} (${notes.join("; ")})`;
|
|
1664
2185
|
}
|
|
1665
2186
|
const directLock = this.directInstallLockEntries();
|
|
1666
2187
|
const libraryLock = { ...directLock, ...await this.libraryLockEntries(lib) };
|
|
1667
|
-
const freshLock =
|
|
2188
|
+
const freshLock = {
|
|
2189
|
+
...buildLockFile(resolved, overridesApplied, libraryLock),
|
|
2190
|
+
...presetsLockSlice(presets)
|
|
2191
|
+
};
|
|
1668
2192
|
await this.keepRequiredByKept(prevLock, kept, Object.keys(freshLock.assets));
|
|
1669
|
-
const removed = this.reconcileRemovedAssets(
|
|
2193
|
+
const { removed, replaced } = this.reconcileRemovedAssets(
|
|
1670
2194
|
resolved,
|
|
1671
2195
|
/* @__PURE__ */ new Set([...Object.keys(libraryLock), ...Object.keys(kept.assets)])
|
|
1672
2196
|
);
|
|
1673
2197
|
const entries = [...await this.buildCatalogEntries(resolved, repositories), ...lib.entries];
|
|
2198
|
+
const copyDrift = await this.lockedCopyDrift();
|
|
1674
2199
|
const deployed = [
|
|
1675
2200
|
...new Set(
|
|
1676
2201
|
this.targetHarnesses().flatMap(
|
|
@@ -1678,23 +2203,31 @@ var AssetManager = class {
|
|
|
1678
2203
|
)
|
|
1679
2204
|
)
|
|
1680
2205
|
];
|
|
2206
|
+
const restored = this.restoredCopies(copyDrift, deployed);
|
|
1681
2207
|
await this.renderDriverMcpConfig();
|
|
1682
|
-
const agentRender = this.renderAgentInstances();
|
|
1683
|
-
const
|
|
2208
|
+
const agentRender = await this.renderAgentInstances();
|
|
2209
|
+
const lockWanted = resolved.length > 0 || lib.entries.length > 0 || existsSync(this.lockPath);
|
|
2210
|
+
let lockWritten = false;
|
|
1684
2211
|
let relocked = [];
|
|
1685
|
-
|
|
2212
|
+
let moved = [];
|
|
2213
|
+
if (lockWanted) {
|
|
1686
2214
|
const lockData = withKeptLockEntries(freshLock, kept);
|
|
1687
2215
|
relocked = relockedRefs(prevLock, lockData);
|
|
1688
|
-
|
|
2216
|
+
moved = libraryMoves(prevLock, libraryLock);
|
|
2217
|
+
lockWritten = writeLock(this.lockPath, lockData);
|
|
1689
2218
|
}
|
|
1690
2219
|
this.recordInstallHistory(deployed, removed);
|
|
1691
2220
|
const { npmInstalled, npmFailed } = await this.installRuntimeAssetDeps();
|
|
2221
|
+
await this.adviseMissing(stillMissing, missingHints);
|
|
1692
2222
|
return {
|
|
1693
2223
|
deployed,
|
|
1694
2224
|
removed,
|
|
1695
2225
|
missing: stillMissing,
|
|
1696
2226
|
...Object.keys(missingHints).length > 0 ? { missingHints } : {},
|
|
1697
2227
|
...relocked.length > 0 ? { relocked } : {},
|
|
2228
|
+
...moved.length > 0 ? { moved } : {},
|
|
2229
|
+
...replaced.length > 0 ? { replaced } : {},
|
|
2230
|
+
...restored.length > 0 ? { restored } : {},
|
|
1698
2231
|
lockWritten,
|
|
1699
2232
|
npmInstalled,
|
|
1700
2233
|
npmFailed,
|
|
@@ -1714,7 +2247,7 @@ var AssetManager = class {
|
|
|
1714
2247
|
const fresh = freshKeys.map(parseCanonicalLockKey);
|
|
1715
2248
|
const queue = Object.keys(kept.assets);
|
|
1716
2249
|
for (const key of queue) {
|
|
1717
|
-
for (const dep of await this.
|
|
2250
|
+
for (const dep of await this.deployedRequirementsOf(parseCanonicalLockKey(key))) {
|
|
1718
2251
|
const want = memberIdentity(dep);
|
|
1719
2252
|
if (!want) continue;
|
|
1720
2253
|
const matches = (id) => id.kind === want.kind && id.name === want.name && (!want.publisher || !id.publisher || id.publisher === want.publisher);
|
|
@@ -1730,9 +2263,14 @@ var AssetManager = class {
|
|
|
1730
2263
|
}
|
|
1731
2264
|
}
|
|
1732
2265
|
}
|
|
1733
|
-
/**
|
|
1734
|
-
|
|
1735
|
-
|
|
2266
|
+
/**
|
|
2267
|
+
* Publisher-qualified refs the deployed copy of `id` requires, read from its own
|
|
2268
|
+
* manifest file (`metadata.requires`, a manifest `dependencies:` entry): the one place
|
|
2269
|
+
* the requirements stay knowable when no source or library holds the asset any more.
|
|
2270
|
+
* A copy that is not deployed or cannot be read has no known requirements.
|
|
2271
|
+
*/
|
|
2272
|
+
async deployedRequirementsOf(id) {
|
|
2273
|
+
const { kind, name, publisher } = id;
|
|
1736
2274
|
const component = kind ? this.installTarget(kind) : void 0;
|
|
1737
2275
|
if (!kind || !name || !component) return [];
|
|
1738
2276
|
const dir = deployedDir(component, name, this.driverTarget, this.global, this.projectDir);
|
|
@@ -1805,21 +2343,32 @@ var AssetManager = class {
|
|
|
1805
2343
|
...keep
|
|
1806
2344
|
]);
|
|
1807
2345
|
const removed = [];
|
|
2346
|
+
const replaced = [];
|
|
1808
2347
|
if (oldLock) {
|
|
1809
2348
|
for (const ref of Object.keys(oldLock.assets)) {
|
|
1810
2349
|
if (!resolvedRefs.has(ref)) {
|
|
1811
|
-
const { kind, name } = parseCanonicalLockKey(ref);
|
|
2350
|
+
const { kind, name, publisher, version } = parseCanonicalLockKey(ref);
|
|
1812
2351
|
if (kind === "preset") continue;
|
|
1813
2352
|
if (kind && name) {
|
|
1814
2353
|
for (const t of this.targetHarnesses()) removeAsset(kind, name, this.deployOptsFor(t));
|
|
1815
|
-
|
|
2354
|
+
const successor = [...resolvedRefs].find((r) => {
|
|
2355
|
+
const o = parseCanonicalLockKey(r);
|
|
2356
|
+
return o.kind === kind && o.name === name && o.publisher === publisher;
|
|
2357
|
+
});
|
|
2358
|
+
if (successor) {
|
|
2359
|
+
replaced.push({
|
|
2360
|
+
ref: `${kind}:${publisher ? `@${publisher}/` : ""}${name}`,
|
|
2361
|
+
from: version ?? "",
|
|
2362
|
+
to: parseCanonicalLockKey(successor).version ?? ""
|
|
2363
|
+
});
|
|
2364
|
+
} else removed.push(ref);
|
|
1816
2365
|
} else {
|
|
1817
2366
|
console.warn(`skaile: cannot reconcile unparseable lock key "${ref}" \u2014 left in place.`);
|
|
1818
2367
|
}
|
|
1819
2368
|
}
|
|
1820
2369
|
}
|
|
1821
2370
|
}
|
|
1822
|
-
return removed;
|
|
2371
|
+
return { removed, replaced };
|
|
1823
2372
|
}
|
|
1824
2373
|
/** Append `add`/`remove` history entries for a completed install reconcile. */
|
|
1825
2374
|
recordInstallHistory(deployed, removed) {
|
|
@@ -1935,9 +2484,39 @@ var AssetManager = class {
|
|
|
1935
2484
|
}
|
|
1936
2485
|
return { npmInstalled, npmFailed };
|
|
1937
2486
|
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Give every ref `install` could not resolve, and that has no hint yet, the fix the shared
|
|
2489
|
+
* {@link AssetManager.projectState} names for it (a declared ref no library or source holds,
|
|
2490
|
+
* a requirement nothing holds). Runs after the deploy, so a requirer is known by then.
|
|
2491
|
+
* Mutates `hints`: ` <reason>` follows the ref in the `Missing:` line.
|
|
2492
|
+
*/
|
|
2493
|
+
async adviseMissing(refs, hints) {
|
|
2494
|
+
const open = refs.filter((r) => !hints[r]);
|
|
2495
|
+
if (open.length === 0) return;
|
|
2496
|
+
const { gaps } = await this.projectState();
|
|
2497
|
+
for (const ref of open) {
|
|
2498
|
+
const want = parseRefTolerant(ref.split(" ")[0] ?? ref);
|
|
2499
|
+
if (!want) continue;
|
|
2500
|
+
const gap = gaps.find(
|
|
2501
|
+
(g) => (g.type === "declared" || g.type === "requires") && g.manualFix && sameAsset(g.ref, want)
|
|
2502
|
+
);
|
|
2503
|
+
if (gap) hints[ref] = ` ${gap.reason}`;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
1938
2506
|
async installLocked(repositories, patches, dependencies) {
|
|
1939
2507
|
const lock = this.tryReadLock();
|
|
1940
2508
|
if (!lock) {
|
|
2509
|
+
const gaps2 = (await this.projectState()).gaps.filter((g) => g.type !== "agent");
|
|
2510
|
+
if (gaps2.some((g) => g.manualFix)) {
|
|
2511
|
+
return {
|
|
2512
|
+
deployed: [],
|
|
2513
|
+
removed: [],
|
|
2514
|
+
missing: gaps2.map((g) => g.message),
|
|
2515
|
+
lockWritten: false,
|
|
2516
|
+
npmInstalled: [],
|
|
2517
|
+
npmFailed: []
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
1941
2520
|
throw new Error("No lock file found. Run `skaile install` first.");
|
|
1942
2521
|
}
|
|
1943
2522
|
this.assertNoFolderCollision(Object.keys(lock.assets).map(parseCanonicalLockKey));
|
|
@@ -1999,6 +2578,7 @@ var AssetManager = class {
|
|
|
1999
2578
|
if (!repositories[slug]) repositories[slug] = { url: entry.source.url, path: cloneDir };
|
|
2000
2579
|
}
|
|
2001
2580
|
const library = await this.restoreLibraryLockEntries(lock);
|
|
2581
|
+
const copyDrift = await this.lockedCopyDrift();
|
|
2002
2582
|
const deployed = [
|
|
2003
2583
|
...new Set(
|
|
2004
2584
|
this.targetHarnesses().flatMap(
|
|
@@ -2011,15 +2591,17 @@ var AssetManager = class {
|
|
|
2011
2591
|
)
|
|
2012
2592
|
)
|
|
2013
2593
|
];
|
|
2594
|
+
const restored = this.restoredCopies(copyDrift, deployed);
|
|
2014
2595
|
await this.renderDriverMcpConfig();
|
|
2015
|
-
const agentRender = this.renderAgentInstances();
|
|
2596
|
+
const agentRender = await this.renderAgentInstances();
|
|
2016
2597
|
const alreadyMissing = new Set(
|
|
2017
2598
|
library.missing.map((m) => {
|
|
2018
2599
|
const p = parseRefTolerant(m.split(" ")[0] ?? m);
|
|
2019
2600
|
return p ? `${p.kind}:${p.name}` : m;
|
|
2020
2601
|
})
|
|
2021
2602
|
);
|
|
2022
|
-
const gaps = (await this.
|
|
2603
|
+
const gaps = (await this.projectState()).gaps.filter((g) => {
|
|
2604
|
+
if (g.type === "agent") return false;
|
|
2023
2605
|
const p = parseRefTolerant(g.ref);
|
|
2024
2606
|
return !(p && alreadyMissing.has(`${p.kind}:${p.name}`));
|
|
2025
2607
|
}).map((g) => g.message);
|
|
@@ -2028,9 +2610,68 @@ var AssetManager = class {
|
|
|
2028
2610
|
removed: [],
|
|
2029
2611
|
missing: [...library.missing, ...gaps],
|
|
2030
2612
|
lockWritten: false,
|
|
2613
|
+
...restored.length > 0 ? { restored } : {},
|
|
2031
2614
|
...agentRender
|
|
2032
2615
|
};
|
|
2033
2616
|
}
|
|
2617
|
+
/**
|
|
2618
|
+
* Parse the ref `add` was given. A ref the grammar rejects fails with the parser's message
|
|
2619
|
+
* minus its manifest-migration advice (it points at a skill that is not for `add`), and:
|
|
2620
|
+
* - a publisher-less ref (`report-html`, `skill:report-html`) names the assets of that
|
|
2621
|
+
* kind and name that `add` can resolve (a source, the bundled factory source or a
|
|
2622
|
+
* library holds them), so `Did you mean` never names an asset that does not resolve;
|
|
2623
|
+
* - a `@version` suffix (`skill:@acme/x@1.0.0`) says the pin is written `#<version>`.
|
|
2624
|
+
*/
|
|
2625
|
+
async parseAddRef(ref, repositories, sources) {
|
|
2626
|
+
try {
|
|
2627
|
+
return parseAssetRef(ref);
|
|
2628
|
+
} catch (err) {
|
|
2629
|
+
if (!(err instanceof Error)) throw err;
|
|
2630
|
+
const advice = " Run the `migrate-skaile-manifest` skill on legacy manifests.";
|
|
2631
|
+
const plain = err.message.endsWith(advice) ? err.message.slice(0, -advice.length) : err.message;
|
|
2632
|
+
if (plain.startsWith("publisher required")) {
|
|
2633
|
+
const matches = await this.resolvableMatches(ref, repositories, sources);
|
|
2634
|
+
const hint = matches.length === 0 ? "" : matches.length === 1 ? ` Did you mean ${matches[0]}?` : ` Did you mean one of: ${matches.join(", ")}?`;
|
|
2635
|
+
throw new Error(`${plain}${hint}`);
|
|
2636
|
+
}
|
|
2637
|
+
const pinned = /^(.*\/[^@#/]+)@([^@#/]+)$/.exec(ref.trim());
|
|
2638
|
+
if (pinned && plain.startsWith("invalid asset name")) {
|
|
2639
|
+
throw new Error(`${plain} To pin a version, write ${pinned[1]}#${pinned[2]}.`);
|
|
2640
|
+
}
|
|
2641
|
+
throw new Error(plain);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* The publisher-qualified refs of the assets named like the publisher-less `ref` that
|
|
2646
|
+
* `add` resolves: held by a source clone, the bundled factory source or a registered
|
|
2647
|
+
* library. A ref without a kind means a skill, as the grammar says.
|
|
2648
|
+
*/
|
|
2649
|
+
async resolvableMatches(ref, repositories, sources) {
|
|
2650
|
+
const trimmed = ref.trim();
|
|
2651
|
+
const colon = trimmed.indexOf(":");
|
|
2652
|
+
const rawKind = colon === -1 ? "skill" : trimmed.slice(0, colon).trim();
|
|
2653
|
+
const kind = rawKind === "mcp" ? "mcp-server" : rawKind;
|
|
2654
|
+
const hash = trimmed.indexOf("#", colon + 1);
|
|
2655
|
+
const name = trimmed.slice(colon + 1, hash === -1 ? void 0 : hash).trim();
|
|
2656
|
+
if (!name || /[@/\s]/.test(name)) return [];
|
|
2657
|
+
const found = /* @__PURE__ */ new Set();
|
|
2658
|
+
const note = (c) => {
|
|
2659
|
+
if (c.kind === kind && c.name === name && c.publisher) {
|
|
2660
|
+
found.add(assetRefToStr({ kind, name, publisher: c.publisher.replace(/^@/, "") }));
|
|
2661
|
+
}
|
|
2662
|
+
};
|
|
2663
|
+
try {
|
|
2664
|
+
this.ensureSourceClonesQuiet(repositories, sources);
|
|
2665
|
+
const index = await buildProvenanceIndex(this.buildClones(repositories), {
|
|
2666
|
+
projectDir: this.projectDir,
|
|
2667
|
+
knownKinds: this.knownKinds
|
|
2668
|
+
});
|
|
2669
|
+
for (const candidates of index.values()) for (const c of candidates) note(c);
|
|
2670
|
+
for (const { entry } of await this.libraryAssets()) note(entry);
|
|
2671
|
+
} catch {
|
|
2672
|
+
}
|
|
2673
|
+
return [...found].sort();
|
|
2674
|
+
}
|
|
2034
2675
|
/**
|
|
2035
2676
|
* Add a single asset (and its transitive dependencies) to the workspace.
|
|
2036
2677
|
*
|
|
@@ -2044,6 +2685,8 @@ var AssetManager = class {
|
|
|
2044
2685
|
* store/factory sources. Defaults to copy.
|
|
2045
2686
|
* @param opts.onLibraryOrigin - Called once per deployed library-origin asset with its
|
|
2046
2687
|
* canonical ref and the name of the library it was resolved from.
|
|
2688
|
+
* @param opts.onLibraryMoved - Called once per asset whose lock entry named another library
|
|
2689
|
+
* than the one this add resolved it from.
|
|
2047
2690
|
* @param opts.onPresets - Called once with the presets expanded into this add (the
|
|
2048
2691
|
* added preset and any it nests); a library-held preset names its library.
|
|
2049
2692
|
* @returns Array of asset refs that were deployed in this call.
|
|
@@ -2052,7 +2695,7 @@ var AssetManager = class {
|
|
|
2052
2695
|
*/
|
|
2053
2696
|
async add(ref, opts) {
|
|
2054
2697
|
const { repositories, sources, stores, dependencies, overrides, patches } = this.loadConfig();
|
|
2055
|
-
const parsed =
|
|
2698
|
+
const parsed = await this.parseAddRef(ref, repositories, sources);
|
|
2056
2699
|
this.ensureSourceClonesQuiet(repositories, sources);
|
|
2057
2700
|
const provenanceIndex = await buildProvenanceIndex(this.buildClones(repositories), {
|
|
2058
2701
|
projectDir: this.projectDir,
|
|
@@ -2060,9 +2703,24 @@ var AssetManager = class {
|
|
|
2060
2703
|
});
|
|
2061
2704
|
const { resolved, missing, overridesApplied, lib, presets } = await this.resolveLibraryFirst(
|
|
2062
2705
|
[ref],
|
|
2063
|
-
{ provenanceIndex, overrides, stores, declaredRefs: dependencies }
|
|
2706
|
+
{ provenanceIndex, overrides, stores, declaredRefs: dependencies, warnCollisions: true }
|
|
2064
2707
|
);
|
|
2065
2708
|
if (resolved.length === 0 && lib.entries.length === 0 && presets.length === 0) {
|
|
2709
|
+
const snap = await this.loadLibraryIndex();
|
|
2710
|
+
const bare = (p) => (p ?? "").replace(/^@/, "");
|
|
2711
|
+
for (const l of snap?.libraries ?? []) {
|
|
2712
|
+
const libPath = this.libraryPath({ name: l.name }, snap);
|
|
2713
|
+
if (!libPath || !manifestFileHasAssetsArray(libPath)) continue;
|
|
2714
|
+
const unlisted = await scanUnlistedAssets(libPath, l.name, this.registry);
|
|
2715
|
+
const hit = unlisted.find(
|
|
2716
|
+
(u) => !u.registeredAt && u.entry.kind === parsed.kind && u.entry.name === parsed.name && (!parsed.publisher || bare(u.entry.publisher) === bare(parsed.publisher))
|
|
2717
|
+
);
|
|
2718
|
+
if (hit) {
|
|
2719
|
+
throw new Error(
|
|
2720
|
+
`${canonicalEntryRef(hit.entry)} is in library "${l.name}" but not listed in its skaile.manifest.yaml, so only part of its files would be deployed. Run \`skaile library index ${l.name}\`, then add it again.`
|
|
2721
|
+
);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2066
2724
|
throw new Error(
|
|
2067
2725
|
`Asset not found: ${ref}${(await this.publisherChangeHints([ref]))[ref] ?? ""}${missing.length ? ` (missing: ${missing.join(", ")})` : ""}`
|
|
2068
2726
|
);
|
|
@@ -2072,6 +2730,18 @@ var AssetManager = class {
|
|
|
2072
2730
|
`Preset ${ref} has items that do not resolve: ${missing.join(", ")} \u2014 nothing was added`
|
|
2073
2731
|
);
|
|
2074
2732
|
}
|
|
2733
|
+
if (lib.entries.length > 0) {
|
|
2734
|
+
const snap = await this.loadLibraryIndex();
|
|
2735
|
+
for (const e of lib.entries) {
|
|
2736
|
+
const origin = lib.libraryOf.get(e);
|
|
2737
|
+
const libPath = origin ? this.libraryPath({ name: origin }, snap) : null;
|
|
2738
|
+
if (origin && libPath && isUnlistedLibraryAsset(e, libPath)) {
|
|
2739
|
+
throw new Error(
|
|
2740
|
+
`${canonicalEntryRef(e)} is in library "${origin}" but not listed in its skaile.manifest.yaml, so only part of its files would be deployed. Run \`skaile library index ${origin}\`, then add it again.`
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2075
2745
|
this.assertNoFolderCollision(
|
|
2076
2746
|
[...resolved, ...lib.entries.map((e) => this.entryIdentity(e))],
|
|
2077
2747
|
[
|
|
@@ -2080,15 +2750,20 @@ var AssetManager = class {
|
|
|
2080
2750
|
]
|
|
2081
2751
|
);
|
|
2082
2752
|
if (missing.length > 0) {
|
|
2083
|
-
|
|
2084
|
-
|
|
2753
|
+
throw new Error(
|
|
2754
|
+
`${ref.split("#")[0]} requires ${missing.join(", ")} (not found \u2014 ${REGISTER_HOLDER}, or remove the requirement from the asset's manifest) \u2014 nothing was added`
|
|
2085
2755
|
);
|
|
2086
2756
|
}
|
|
2087
2757
|
const entries = [...await this.buildCatalogEntries(resolved, repositories), ...lib.entries];
|
|
2088
2758
|
const deployed = [
|
|
2089
2759
|
...new Set(
|
|
2090
2760
|
this.targetHarnesses().flatMap(
|
|
2091
|
-
(t) => deployAll(
|
|
2761
|
+
(t) => deployAll(
|
|
2762
|
+
entries,
|
|
2763
|
+
repositories,
|
|
2764
|
+
patches,
|
|
2765
|
+
this.deployOptsFor(t, opts?.link, opts?.onSkippedSymlink, opts?.onEnvFile)
|
|
2766
|
+
)
|
|
2092
2767
|
)
|
|
2093
2768
|
)
|
|
2094
2769
|
];
|
|
@@ -2102,10 +2777,18 @@ var AssetManager = class {
|
|
|
2102
2777
|
if (presets.length > 0) opts?.onPresets?.(presets);
|
|
2103
2778
|
this.addDependencyToConfig(ref);
|
|
2104
2779
|
await this.renderDriverMcpConfig();
|
|
2105
|
-
this.renderAgentInstances();
|
|
2780
|
+
await this.renderAgentInstances();
|
|
2106
2781
|
if (resolved.length > 0 || lib.entries.length > 0) {
|
|
2107
|
-
const
|
|
2108
|
-
|
|
2782
|
+
const libraryLock = await this.libraryLockEntries(lib);
|
|
2783
|
+
const fresh = {
|
|
2784
|
+
...buildLockFile(resolved, overridesApplied, libraryLock),
|
|
2785
|
+
...presetsLockSlice(presets)
|
|
2786
|
+
};
|
|
2787
|
+
const prevLock = this.tryReadLock();
|
|
2788
|
+
if (opts?.onLibraryMoved) {
|
|
2789
|
+
for (const m of libraryMoves(prevLock, libraryLock)) opts.onLibraryMoved(m);
|
|
2790
|
+
}
|
|
2791
|
+
writeLock(this.lockPath, mergeLockFile(prevLock, fresh));
|
|
2109
2792
|
}
|
|
2110
2793
|
for (const d of deployed) {
|
|
2111
2794
|
appendHistory(this.projectDir, {
|
|
@@ -2163,16 +2846,16 @@ var AssetManager = class {
|
|
|
2163
2846
|
const target = memberIdentity(ref);
|
|
2164
2847
|
if (!target || target.kind === "preset") return [];
|
|
2165
2848
|
try {
|
|
2166
|
-
const
|
|
2849
|
+
const sameAsset2 = (id, want) => !!id && id.kind === want.kind && id.name === want.name && (!id.publisher || !want.publisher || id.publisher === want.publisher);
|
|
2167
2850
|
const declared = this.loadConfig().dependencies.map(memberIdentity);
|
|
2168
2851
|
const out = [];
|
|
2169
2852
|
const seen = /* @__PURE__ */ new Set();
|
|
2170
2853
|
for (const p of await this.search(void 0, "preset")) {
|
|
2171
2854
|
const members = [...p.dependencies, ...p.requires.map(depToStr)];
|
|
2172
|
-
if (!members.some((m) =>
|
|
2855
|
+
if (!members.some((m) => sameAsset2(memberIdentity(m), target))) continue;
|
|
2173
2856
|
const publisher = p.publisher?.replace(/^@/, "");
|
|
2174
2857
|
const presetId = { kind: "preset", name: p.name, publisher };
|
|
2175
|
-
if (declared.some((d) =>
|
|
2858
|
+
if (declared.some((d) => sameAsset2(d, presetId))) continue;
|
|
2176
2859
|
const presetRef = publisher ? assetRefToStr({ kind: "preset", name: p.name, publisher }) : `preset:${p.name}`;
|
|
2177
2860
|
if (seen.has(presetRef)) continue;
|
|
2178
2861
|
seen.add(presetRef);
|
|
@@ -2383,19 +3066,39 @@ var AssetManager = class {
|
|
|
2383
3066
|
* preset's items count as its requirements. They are undeployed and unlocked;
|
|
2384
3067
|
* a dependency still declared or required elsewhere stays.
|
|
2385
3068
|
*
|
|
3069
|
+
* An asset another locked asset (or a declared preset) requires is kept and reported in
|
|
3070
|
+
* `blocked`, nothing changes, unless `opts.force` (see {@link requiredByOthers}).
|
|
3071
|
+
*
|
|
2386
3072
|
* @returns `changed` as {@link remove}; `pruned` the canonical refs
|
|
2387
|
-
* (`kind:@publisher/name`) of the dependencies dropped as well
|
|
3073
|
+
* (`kind:@publisher/name`) of the dependencies dropped as well; `blocked` when the
|
|
3074
|
+
* removal was refused.
|
|
2388
3075
|
*/
|
|
2389
|
-
async removeWithDependencies(ref) {
|
|
3076
|
+
async removeWithDependencies(ref, opts) {
|
|
2390
3077
|
const target = this.resolveRemoveTarget(ref);
|
|
2391
3078
|
if (!target) return { changed: false, pruned: [] };
|
|
3079
|
+
if (!opts?.force) {
|
|
3080
|
+
const blocked = await this.requiredByOthers(target);
|
|
3081
|
+
if (blocked) return { changed: false, pruned: [], blocked };
|
|
3082
|
+
}
|
|
2392
3083
|
const asked = target.publisher ? assetRefToStr({ kind: target.kind, name: target.name, publisher: target.publisher }) : `${target.kind}:${target.name}`;
|
|
2393
3084
|
let entry = null;
|
|
2394
3085
|
try {
|
|
2395
3086
|
entry = await this.info(asked);
|
|
2396
3087
|
} catch {
|
|
2397
3088
|
}
|
|
2398
|
-
|
|
3089
|
+
let requiredRefs;
|
|
3090
|
+
if (entry) {
|
|
3091
|
+
requiredRefs = libraryEntryDepRefs(entry);
|
|
3092
|
+
} else {
|
|
3093
|
+
const lockedAs = Object.keys(this.tryReadLock()?.assets ?? {}).map(parseCanonicalLockKey).find(
|
|
3094
|
+
(id) => id.kind === target.kind && id.name === target.name && (!target.publisher || id.publisher === target.publisher)
|
|
3095
|
+
);
|
|
3096
|
+
requiredRefs = await this.deployedRequirementsOf({
|
|
3097
|
+
kind: target.kind,
|
|
3098
|
+
name: target.name,
|
|
3099
|
+
publisher: target.publisher ?? lockedAs?.publisher
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
2399
3102
|
const changed = this.removeResolved(target);
|
|
2400
3103
|
if (!changed || requiredRefs.length === 0) return { changed, pruned: [] };
|
|
2401
3104
|
return { changed, pruned: await this.pruneOrphans(requiredRefs) };
|
|
@@ -2449,15 +3152,65 @@ var AssetManager = class {
|
|
|
2449
3152
|
return kind ? { kind, name } : null;
|
|
2450
3153
|
}
|
|
2451
3154
|
}
|
|
3155
|
+
/**
|
|
3156
|
+
* The reverse guard of {@link removeWithDependencies}: which other locked assets (and
|
|
3157
|
+
* declared presets) require `target`. Their requirements are read the way {@link lockedGraph}
|
|
3158
|
+
* reads them for pruning. Never touches the project.
|
|
3159
|
+
*
|
|
3160
|
+
* @returns `ref` and `requiredBy` (both `kind:@publisher/name`, version-agnostic), or `null`
|
|
3161
|
+
* when `target` is not locked or nothing else requires it.
|
|
3162
|
+
*/
|
|
3163
|
+
async requiredByOthers(target) {
|
|
3164
|
+
const lock = this.tryReadLock();
|
|
3165
|
+
if (!lock) return null;
|
|
3166
|
+
const { requires, findKey } = await this.lockedGraph(lock);
|
|
3167
|
+
const targetKey = findKey(
|
|
3168
|
+
target.publisher ? assetRefToStr({ kind: target.kind, name: target.name, publisher: target.publisher }) : `${target.kind}:${target.name}`
|
|
3169
|
+
);
|
|
3170
|
+
if (!targetKey) return null;
|
|
3171
|
+
const display = (node) => {
|
|
3172
|
+
if (!lock.assets[node]) return node;
|
|
3173
|
+
const { kind, name, publisher } = parseCanonicalLockKey(node);
|
|
3174
|
+
if (!kind || !name) return node;
|
|
3175
|
+
return publisher ? assetRefToStr({ kind, name, publisher }) : `${kind}:${name}`;
|
|
3176
|
+
};
|
|
3177
|
+
const requiredBy = [...requires].filter(([node, deps]) => node !== targetKey && deps.includes(targetKey)).map(([node]) => display(node));
|
|
3178
|
+
return requiredBy.length > 0 ? { ref: display(targetKey), requiredBy } : null;
|
|
3179
|
+
}
|
|
3180
|
+
/**
|
|
3181
|
+
* Publishers under which the lock or `skaile.yaml` track `kind:name`. A deployed dir carries
|
|
3182
|
+
* no publisher, so this is what says whose copy it is. `undefined` stands for a
|
|
3183
|
+
* publisher-less declaration.
|
|
3184
|
+
*/
|
|
3185
|
+
trackedPublishers(kind, name) {
|
|
3186
|
+
const found = /* @__PURE__ */ new Set();
|
|
3187
|
+
const note = (id) => {
|
|
3188
|
+
if (id?.kind === kind && id.name === name) found.add(id.publisher?.replace(/^@/, ""));
|
|
3189
|
+
};
|
|
3190
|
+
for (const key of Object.keys(this.tryReadLock()?.assets ?? {})) {
|
|
3191
|
+
note(parseCanonicalLockKey(key));
|
|
3192
|
+
}
|
|
3193
|
+
try {
|
|
3194
|
+
for (const dep of this.loadConfig().dependencies) note(memberIdentity(dep));
|
|
3195
|
+
} catch {
|
|
3196
|
+
}
|
|
3197
|
+
return found;
|
|
3198
|
+
}
|
|
2452
3199
|
/** Undeploy `parsed` from every target harness and drop its declaration and lock entry. */
|
|
2453
3200
|
removeResolved(parsed) {
|
|
3201
|
+
const wantPub = parsed.publisher?.replace(/^@/, "");
|
|
3202
|
+
const tracked = this.trackedPublishers(parsed.kind, parsed.name);
|
|
3203
|
+
const someoneElses = wantPub !== void 0 && tracked.size > 0 && !tracked.has(wantPub) && !tracked.has(void 0);
|
|
2454
3204
|
let undeployed = false;
|
|
2455
|
-
|
|
2456
|
-
|
|
3205
|
+
if (!someoneElses) {
|
|
3206
|
+
for (const t of this.targetHarnesses()) {
|
|
3207
|
+
if (removeAsset(parsed.kind, parsed.name, this.deployOptsFor(t))) undeployed = true;
|
|
3208
|
+
}
|
|
2457
3209
|
}
|
|
2458
3210
|
const undeclared = this.removeDependencyFromConfig(parsed);
|
|
2459
|
-
const
|
|
2460
|
-
const
|
|
3211
|
+
const unbound = this.removeAgentBindings(parsed);
|
|
3212
|
+
const unlocked = undeployed || undeclared || unbound ? this.pruneLockEntry(parsed) : false;
|
|
3213
|
+
const changed = undeployed || undeclared || unbound || unlocked;
|
|
2461
3214
|
if (changed) {
|
|
2462
3215
|
appendHistory(this.projectDir, {
|
|
2463
3216
|
ref: `${parsed.kind}:${parsed.name}`,
|
|
@@ -2518,6 +3271,17 @@ var AssetManager = class {
|
|
|
2518
3271
|
changed = true;
|
|
2519
3272
|
}
|
|
2520
3273
|
}
|
|
3274
|
+
if (parsed.kind === "preset" && lock.presets) {
|
|
3275
|
+
const kept = lock.presets.filter((key) => {
|
|
3276
|
+
const id = parseCanonicalLockKey(key);
|
|
3277
|
+
return !(id.kind === parsed.kind && id.name === parsed.name && (!parsed.publisher || id.publisher === parsed.publisher));
|
|
3278
|
+
});
|
|
3279
|
+
if (kept.length !== lock.presets.length) {
|
|
3280
|
+
if (kept.length > 0) lock.presets = kept;
|
|
3281
|
+
else delete lock.presets;
|
|
3282
|
+
changed = true;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
2521
3285
|
if (changed) writeLock(this.lockPath, lock);
|
|
2522
3286
|
return changed;
|
|
2523
3287
|
}
|
|
@@ -2578,6 +3342,7 @@ var AssetManager = class {
|
|
|
2578
3342
|
const { repositories } = this.loadConfig();
|
|
2579
3343
|
const parsed = parseRefTolerant(ref);
|
|
2580
3344
|
if (!parsed) return null;
|
|
3345
|
+
let best;
|
|
2581
3346
|
for (const [name, decl] of Object.entries(repositories)) {
|
|
2582
3347
|
const repoDir = this.resolveRepoDir(decl, name);
|
|
2583
3348
|
if (!repoDir) continue;
|
|
@@ -2585,9 +3350,12 @@ var AssetManager = class {
|
|
|
2585
3350
|
const match = entries.find(
|
|
2586
3351
|
(e) => e.kind === parsed.kind && e.name === parsed.name && (!parsed.publisher || e.publisher === parsed.publisher)
|
|
2587
3352
|
);
|
|
2588
|
-
if (match)
|
|
3353
|
+
if (match && (!best || gt(coerceVersion(match.version ?? ""), coerceVersion(best.version ?? "")))) {
|
|
3354
|
+
best = match;
|
|
3355
|
+
}
|
|
2589
3356
|
}
|
|
2590
|
-
|
|
3357
|
+
if (best) return best;
|
|
3358
|
+
const lib = await this.resolveLibraryEntries([ref], { quiet: true });
|
|
2591
3359
|
return lib.entries[0] ?? null;
|
|
2592
3360
|
}
|
|
2593
3361
|
/**
|
|
@@ -2929,7 +3697,8 @@ var AssetManager = class {
|
|
|
2929
3697
|
const out = [];
|
|
2930
3698
|
try {
|
|
2931
3699
|
const entry = await this.info(ref);
|
|
2932
|
-
|
|
3700
|
+
const deps = entry ? libraryEntryDepRefs(entry) : lock.assets[node] ? await this.deployedRequirementsOf(parseCanonicalLockKey(node)) : [];
|
|
3701
|
+
for (const dep of deps) {
|
|
2933
3702
|
const key = findKey(dep);
|
|
2934
3703
|
if (key && key !== node && !out.includes(key)) out.push(key);
|
|
2935
3704
|
}
|
|
@@ -3022,7 +3791,7 @@ var AssetManager = class {
|
|
|
3022
3791
|
return locked.kind === asked.kind && locked.name === asked.name;
|
|
3023
3792
|
};
|
|
3024
3793
|
const key = lock.assets[ref] ? ref : Object.keys(lock.assets).find(matchesIdentity);
|
|
3025
|
-
if (!key) return [];
|
|
3794
|
+
if (!key) return asked?.kind === "preset" ? this.whyPreset(lock, asked) : [];
|
|
3026
3795
|
const { requires, roots } = await this.lockedGraph(lock);
|
|
3027
3796
|
if (roots.includes(key)) return [key, "direct (skaile.yaml)"];
|
|
3028
3797
|
const requirers = /* @__PURE__ */ new Map();
|
|
@@ -3047,6 +3816,52 @@ var AssetManager = class {
|
|
|
3047
3816
|
}
|
|
3048
3817
|
return [key, "not required by a declared dependency"];
|
|
3049
3818
|
}
|
|
3819
|
+
/**
|
|
3820
|
+
* The {@link AssetManager.why} chain of a preset. A preset is never a lock asset: its record
|
|
3821
|
+
* is the lock's `presets` slice. Declared in `skaile.yaml` it is `direct`; one only a declared
|
|
3822
|
+
* preset nests is traced through the presets that list it.
|
|
3823
|
+
*
|
|
3824
|
+
* @returns `[]` when the lock does not record the preset.
|
|
3825
|
+
*/
|
|
3826
|
+
async whyPreset(lock, asked) {
|
|
3827
|
+
const wanted = memberIdentity(assetRefToStr(asked));
|
|
3828
|
+
const same = (ref) => {
|
|
3829
|
+
const id = memberIdentity(ref);
|
|
3830
|
+
return id !== null && wanted !== null && id.kind === "preset" && id.name === wanted.name && (!id.publisher || !wanted.publisher || id.publisher === wanted.publisher);
|
|
3831
|
+
};
|
|
3832
|
+
const recorded = (lock.presets ?? []).find(same);
|
|
3833
|
+
if (!recorded) return [];
|
|
3834
|
+
let declared = [];
|
|
3835
|
+
try {
|
|
3836
|
+
declared = this.loadConfig().dependencies;
|
|
3837
|
+
} catch {
|
|
3838
|
+
}
|
|
3839
|
+
const roots = declared.filter((d) => memberIdentity(d)?.kind === "preset");
|
|
3840
|
+
if (roots.some(same)) return [recorded, "direct (skaile.yaml)"];
|
|
3841
|
+
const parent = /* @__PURE__ */ new Map();
|
|
3842
|
+
const seen = new Set(roots);
|
|
3843
|
+
const queue = [...roots];
|
|
3844
|
+
for (let node = queue.shift(); node !== void 0; node = queue.shift()) {
|
|
3845
|
+
let items = [];
|
|
3846
|
+
try {
|
|
3847
|
+
const entry = await this.info(node);
|
|
3848
|
+
items = entry ? libraryEntryDepRefs(entry) : [];
|
|
3849
|
+
} catch {
|
|
3850
|
+
}
|
|
3851
|
+
for (const item of items) {
|
|
3852
|
+
if (memberIdentity(item)?.kind !== "preset" || seen.has(item)) continue;
|
|
3853
|
+
seen.add(item);
|
|
3854
|
+
parent.set(item, node);
|
|
3855
|
+
if (same(item)) {
|
|
3856
|
+
const chain = [];
|
|
3857
|
+
for (let c = item; c !== void 0; c = parent.get(c)) chain.push(c);
|
|
3858
|
+
return [...chain, "direct (skaile.yaml)"];
|
|
3859
|
+
}
|
|
3860
|
+
queue.push(item);
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
return [recorded, "not required by a declared dependency"];
|
|
3864
|
+
}
|
|
3050
3865
|
/**
|
|
3051
3866
|
* List installed assets that have a newer upstream available.
|
|
3052
3867
|
*
|
|
@@ -3057,11 +3872,12 @@ var AssetManager = class {
|
|
|
3057
3872
|
* - **Library assets** — a library-origin lock entry whose library files on disk
|
|
3058
3873
|
* (not the index, which lags until `skaile library index`) hold a newer version
|
|
3059
3874
|
* than the locked one (`latestVersion`, `library` set), or the same version with
|
|
3060
|
-
* different bytes (`contentChanged`). `behind` is 0.
|
|
3061
|
-
*
|
|
3062
|
-
*
|
|
3063
|
-
*
|
|
3064
|
-
* `
|
|
3875
|
+
* different bytes (`contentChanged`). `behind` is 0.
|
|
3876
|
+
* - **Project state** — every {@link AssetManager.projectState} gap (an unregistered or
|
|
3877
|
+
* renamed library, a missing deployed copy, a declared dependency or a deployed asset's
|
|
3878
|
+
* `requires` with no lock entry, a lock that contradicts a `skaile.yaml` pin) as a
|
|
3879
|
+
* `problem` row, so `outdated` lists what `check`, `doctor` and `install --locked` fail
|
|
3880
|
+
* on. An asset that also has a version row is listed once, with that row.
|
|
3065
3881
|
*
|
|
3066
3882
|
* @returns Array of {@link OutdatedEntry} objects.
|
|
3067
3883
|
*/
|
|
@@ -3070,7 +3886,6 @@ var AssetManager = class {
|
|
|
3070
3886
|
const lock = this.tryReadLock();
|
|
3071
3887
|
if (!lock) return this.addLockGapRows([]);
|
|
3072
3888
|
const result = [];
|
|
3073
|
-
const reported = /* @__PURE__ */ new Set();
|
|
3074
3889
|
const commitCounts = /* @__PURE__ */ new Map();
|
|
3075
3890
|
for (const src of lock.sources) {
|
|
3076
3891
|
const slug = sourceCacheKey(src.url);
|
|
@@ -3094,7 +3909,6 @@ var AssetManager = class {
|
|
|
3094
3909
|
currentVersion: version ?? "",
|
|
3095
3910
|
behind
|
|
3096
3911
|
});
|
|
3097
|
-
reported.add(ref);
|
|
3098
3912
|
}
|
|
3099
3913
|
}
|
|
3100
3914
|
const libraryAssets = Object.entries(lock.assets).filter(
|
|
@@ -3108,28 +3922,9 @@ var AssetManager = class {
|
|
|
3108
3922
|
const origin = entry.library;
|
|
3109
3923
|
const { publisher, kind, name } = parseCanonicalLockKey(ref);
|
|
3110
3924
|
if (!publisher || !kind || !name) continue;
|
|
3111
|
-
const
|
|
3112
|
-
if (
|
|
3113
|
-
|
|
3114
|
-
const heldElsewhere = (await this.resolveLibraryEntries([canonical], { quiet: true })).resolved.size > 0;
|
|
3115
|
-
const fix = heldElsewhere ? void 0 : (await this.unresolvedFixes([canonical])).get(canonical) ?? unregisteredLibraryFix(origin.name, canonical);
|
|
3116
|
-
result.push({
|
|
3117
|
-
kind,
|
|
3118
|
-
name,
|
|
3119
|
-
publisher,
|
|
3120
|
-
currentVersion: origin.version,
|
|
3121
|
-
behind: 0,
|
|
3122
|
-
library: origin.name,
|
|
3123
|
-
...fix === void 0 ? {
|
|
3124
|
-
problem: `library "${origin.name}" is not registered (renamed?) \u2014 run \`skaile install\` to re-lock it against the library that holds it now`
|
|
3125
|
-
} : {
|
|
3126
|
-
problem: `library "${origin.name}" is not registered (renamed or removed?) \u2014 ${fix}`,
|
|
3127
|
-
manualFix: true
|
|
3128
|
-
}
|
|
3129
|
-
});
|
|
3130
|
-
reported.add(ref);
|
|
3131
|
-
continue;
|
|
3132
|
-
}
|
|
3925
|
+
const lookup = this.lockedLibrary(origin, snap);
|
|
3926
|
+
if (lookup.state !== "ok") continue;
|
|
3927
|
+
const libPath = lookup.path;
|
|
3133
3928
|
const scanKey = `${libPath}\0${publisher}`;
|
|
3134
3929
|
let scanned = scans.get(scanKey);
|
|
3135
3930
|
if (!scanned) {
|
|
@@ -3138,7 +3933,7 @@ var AssetManager = class {
|
|
|
3138
3933
|
}
|
|
3139
3934
|
const found = scanned.find((x) => x.kind === kind && x.name === name);
|
|
3140
3935
|
if (!found) continue;
|
|
3141
|
-
const diskVersion = found.version || newestLibraryDef(snap,
|
|
3936
|
+
const diskVersion = found.version || newestLibraryDef(snap, lookup.name, { kind, publisher, name })?.version;
|
|
3142
3937
|
if (diskVersion && gt(coerceVersion(diskVersion), coerceVersion(origin.version))) {
|
|
3143
3938
|
result.push({
|
|
3144
3939
|
kind,
|
|
@@ -3147,9 +3942,8 @@ var AssetManager = class {
|
|
|
3147
3942
|
currentVersion: origin.version,
|
|
3148
3943
|
behind: 0,
|
|
3149
3944
|
latestVersion: diskVersion,
|
|
3150
|
-
library:
|
|
3945
|
+
library: lookup.name
|
|
3151
3946
|
});
|
|
3152
|
-
reported.add(ref);
|
|
3153
3947
|
continue;
|
|
3154
3948
|
}
|
|
3155
3949
|
if (origin.sha256) {
|
|
@@ -3163,41 +3957,24 @@ var AssetManager = class {
|
|
|
3163
3957
|
publisher,
|
|
3164
3958
|
currentVersion: origin.version,
|
|
3165
3959
|
behind: 0,
|
|
3166
|
-
library:
|
|
3960
|
+
library: lookup.name,
|
|
3167
3961
|
contentChanged: true
|
|
3168
3962
|
});
|
|
3169
|
-
reported.add(ref);
|
|
3170
3963
|
}
|
|
3171
3964
|
}
|
|
3172
3965
|
}
|
|
3173
3966
|
}
|
|
3174
|
-
for (const [ref, entry] of Object.entries(lock.assets)) {
|
|
3175
|
-
if (reported.has(ref) || entry.library?.installed) continue;
|
|
3176
|
-
const { publisher, kind, name, version } = parseCanonicalLockKey(ref);
|
|
3177
|
-
const component = kind ? this.installTarget(kind) : void 0;
|
|
3178
|
-
if (!kind || !name || !component) continue;
|
|
3179
|
-
if (isDeployed(component, name, this.driverTarget, this.global, this.projectDir)) {
|
|
3180
|
-
continue;
|
|
3181
|
-
}
|
|
3182
|
-
result.push({
|
|
3183
|
-
kind,
|
|
3184
|
-
name,
|
|
3185
|
-
publisher: publisher ?? "",
|
|
3186
|
-
currentVersion: entry.library?.version ?? version ?? "",
|
|
3187
|
-
behind: 0,
|
|
3188
|
-
...entry.library ? { library: entry.library.name } : {},
|
|
3189
|
-
problem: "deployed copy missing"
|
|
3190
|
-
});
|
|
3191
|
-
}
|
|
3192
3967
|
return this.addLockGapRows(result);
|
|
3193
3968
|
}
|
|
3194
3969
|
/**
|
|
3195
|
-
*
|
|
3196
|
-
* listed yet, so `outdated`
|
|
3970
|
+
* Add one `problem` row per {@link AssetManager.projectState} gap whose asset is not
|
|
3971
|
+
* listed yet, so `outdated` lists the same assets `check`, `doctor` and `install --locked`
|
|
3972
|
+
* fail on. An asset that is behind its source and also broken is listed once, with its
|
|
3973
|
+
* version row.
|
|
3197
3974
|
*/
|
|
3198
3975
|
async addLockGapRows(rows) {
|
|
3199
3976
|
const listed = new Set(rows.map((r) => `${r.kind}:${r.name}`));
|
|
3200
|
-
for (const gap of await this.
|
|
3977
|
+
for (const gap of (await this.projectState()).gaps) {
|
|
3201
3978
|
const parsed = parseRefTolerant(gap.ref);
|
|
3202
3979
|
const id = parsed ? `${parsed.kind}:${parsed.name}` : gap.ref;
|
|
3203
3980
|
if (listed.has(id)) continue;
|
|
@@ -3214,6 +3991,33 @@ var AssetManager = class {
|
|
|
3214
3991
|
}
|
|
3215
3992
|
return rows;
|
|
3216
3993
|
}
|
|
3994
|
+
/**
|
|
3995
|
+
* The deploy entry of a library-origin asset from the library its lock entry records,
|
|
3996
|
+
* at the locked version when that library still holds it (else its newest). Other
|
|
3997
|
+
* libraries holding the same ref are not consulted: the lock already chose.
|
|
3998
|
+
*
|
|
3999
|
+
* @param want - Kind, name and optional publisher of the asset.
|
|
4000
|
+
* @returns `null` when the lock has no library-origin entry for it, or the recorded
|
|
4001
|
+
* library is gone or no longer holds it (callers fall back to a general lookup).
|
|
4002
|
+
*/
|
|
4003
|
+
async lockedLibraryEntry(want) {
|
|
4004
|
+
const snap = await this.loadLibraryIndex();
|
|
4005
|
+
for (const [key, e] of Object.entries(this.tryReadLock()?.assets ?? {})) {
|
|
4006
|
+
const id = parseCanonicalLockKey(key);
|
|
4007
|
+
if (!e.library || e.library.installed || !id.publisher) continue;
|
|
4008
|
+
if (id.kind !== want.kind || id.name !== want.name) continue;
|
|
4009
|
+
if (want.publisher && id.publisher !== want.publisher) continue;
|
|
4010
|
+
const canonical = `${id.kind}:@${id.publisher}/${id.name}`;
|
|
4011
|
+
const refs = e.library.version ? [`${canonical}#${e.library.version}`, canonical] : [canonical];
|
|
4012
|
+
const lookup = this.lockedLibrary(e.library, snap);
|
|
4013
|
+
const library = lookup.state === "ok" ? lookup.name : e.library.name;
|
|
4014
|
+
for (const r of refs) {
|
|
4015
|
+
const hit = (await this.resolveLibraryEntries([r], { library })).entries[0];
|
|
4016
|
+
if (hit) return hit;
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
return null;
|
|
4020
|
+
}
|
|
3217
4021
|
/**
|
|
3218
4022
|
* Produce a unified diff from the deployed asset to its catalog source: `-` lines
|
|
3219
4023
|
* are in the deployed copy only (local edits `update` would overwrite), `+` lines
|
|
@@ -3231,10 +4035,16 @@ var AssetManager = class {
|
|
|
3231
4035
|
async diff(ref) {
|
|
3232
4036
|
const parsed = parseRefTolerant(ref);
|
|
3233
4037
|
if (!parsed) return { status: "not-found" };
|
|
3234
|
-
const entry = await this.info(ref);
|
|
4038
|
+
const entry = await this.lockedLibraryEntry(parsed) ?? await this.info(ref);
|
|
3235
4039
|
if (!entry || !existsSync(entry.source)) return { status: "not-found" };
|
|
3236
4040
|
const component = this.installTarget(parsed.kind);
|
|
4041
|
+
if (parsed.kind === "preset" && this.presetRecorded(parsed)) return { status: "no-files" };
|
|
3237
4042
|
if (!component) return { status: "not-deployed" };
|
|
4043
|
+
if (component === "agent") {
|
|
4044
|
+
const diff = diffDeployedAgent(entry, this.deployOptsFor(this.driverTarget));
|
|
4045
|
+
if (diff === null) return { status: "not-deployed" };
|
|
4046
|
+
return diff === "" ? { status: "identical" } : { status: "differs", diff };
|
|
4047
|
+
}
|
|
3238
4048
|
const deployedPath = join(
|
|
3239
4049
|
deployedBase(component, this.driverTarget, this.global, this.projectDir),
|
|
3240
4050
|
parsed.name
|
|
@@ -3413,7 +4223,10 @@ var AssetManager = class {
|
|
|
3413
4223
|
continue;
|
|
3414
4224
|
}
|
|
3415
4225
|
const gaps = [];
|
|
3416
|
-
|
|
4226
|
+
const requirements = dedupeRequirements(full.requires, publisher).map(
|
|
4227
|
+
(r) => r.publisher || !publisher ? r : { ...r, publisher }
|
|
4228
|
+
);
|
|
4229
|
+
for (const req of requirements) {
|
|
3417
4230
|
const dep = memberIdentity(depToStr(req));
|
|
3418
4231
|
if (!dep) continue;
|
|
3419
4232
|
const depComponent = this.installTarget(dep.kind);
|
|
@@ -3432,7 +4245,10 @@ var AssetManager = class {
|
|
|
3432
4245
|
if (!inRepos) gaps.push({ issue, ref: depToStr(req) });
|
|
3433
4246
|
}
|
|
3434
4247
|
}
|
|
3435
|
-
const inLibrary = await this.resolveLibraryEntries(
|
|
4248
|
+
const inLibrary = await this.resolveLibraryEntries(
|
|
4249
|
+
gaps.map((g) => g.ref),
|
|
4250
|
+
{ quiet: true }
|
|
4251
|
+
);
|
|
3436
4252
|
for (const g of gaps) g.issue.inRepos = inLibrary.resolved.has(g.ref);
|
|
3437
4253
|
}
|
|
3438
4254
|
for (const [presetRef, gaps] of await this.declaredPresetGaps(dependencies, lock)) {
|
|
@@ -3453,30 +4269,52 @@ var AssetManager = class {
|
|
|
3453
4269
|
return issues;
|
|
3454
4270
|
}
|
|
3455
4271
|
/**
|
|
3456
|
-
*
|
|
3457
|
-
* `skaile check`, `skaile
|
|
4272
|
+
* The project's state as one verdict: every way the project differs from what it
|
|
4273
|
+
* declares and locks. `skaile check`, `skaile doctor`, `skaile outdated`, `skaile diff`
|
|
4274
|
+
* and `skaile install --locked` all take their verdict and exit code from this one list,
|
|
4275
|
+
* so one situation reads the same in all five.
|
|
3458
4276
|
*
|
|
3459
4277
|
* - `declared` — a `skaile.yaml` dependency with no lock entry (library or source gone,
|
|
3460
4278
|
* unresolvable ref, never installed);
|
|
3461
4279
|
* - `invalid` — a `dependencies:` entry that is not a parseable ref;
|
|
3462
|
-
* - `drift` —
|
|
4280
|
+
* - `drift` — a locked asset whose deployed files are missing;
|
|
3463
4281
|
* - `pin` — a declared `#pin` that no lock entry for that asset satisfies;
|
|
3464
|
-
* - `requires` — a deployed asset's `requires`
|
|
3465
|
-
*
|
|
4282
|
+
* - `requires` — a deployed asset's `requires` (or a declared preset's item) that is not
|
|
4283
|
+
* deployed (so not locked either);
|
|
4284
|
+
* - `unresolved` — a lock-managed deployed asset that no source or library holds any more;
|
|
4285
|
+
* - `library` — a locked library asset whose library is not registered (renamed or
|
|
4286
|
+
* removed) or no longer holds it, while another library holds it now;
|
|
4287
|
+
* - `incomplete` — a deployed library asset that lacks files of its library entry;
|
|
4288
|
+
* - `content` — a locked library asset whose files in the library changed since the lock
|
|
4289
|
+
* (or whose lock hash predates the one shared asset hash); `skaile install` re-locks it;
|
|
4290
|
+
* - `agent` — an `agents.<id>.ref` (or `agent.definition`) that binds to no agent.
|
|
4291
|
+
*
|
|
4292
|
+
* An asset appears once for what the lock says about it (`unresolved`, else `library`,
|
|
4293
|
+
* else `drift`). A declared ref no registered library or source holds names
|
|
4294
|
+
* `skaile library init` / `skaile remove`, never `skaile install`: `install` has nothing
|
|
4295
|
+
* to resolve it from.
|
|
3466
4296
|
*
|
|
3467
|
-
* Project mode only: a global manager (`global: true`)
|
|
3468
|
-
* `skaile.yaml` has no declared set, so only the
|
|
3469
|
-
* deployed assets can appear.
|
|
4297
|
+
* Project mode only: a global manager (`global: true`) has no gaps. A project without
|
|
4298
|
+
* `skaile.yaml` has no declared set, so only the lock and deployed-asset gaps can appear.
|
|
3470
4299
|
*
|
|
3471
|
-
* @returns
|
|
4300
|
+
* @returns The {@link ProjectState}; `gaps` is empty when the project is as declared and locked.
|
|
3472
4301
|
*/
|
|
3473
|
-
async
|
|
3474
|
-
if (this.global) return [];
|
|
3475
|
-
const
|
|
3476
|
-
const
|
|
3477
|
-
|
|
4302
|
+
async projectState() {
|
|
4303
|
+
if (this.global) return { gaps: [], requirements: [] };
|
|
4304
|
+
const lock = this.tryReadLock();
|
|
4305
|
+
const lockedIds = Object.keys(lock?.assets ?? {}).map(parseCanonicalLockKey);
|
|
4306
|
+
const requirements = await this.doctor();
|
|
4307
|
+
let gaps = [];
|
|
4308
|
+
const lockIdentity = (kind, name) => {
|
|
4309
|
+
const publisher = lockedIds.find((id) => id.kind === kind && id.name === name)?.publisher;
|
|
4310
|
+
return publisher ? `${kind}:@${publisher}/${name}` : `${kind}:${name}`;
|
|
4311
|
+
};
|
|
4312
|
+
for (const i of requirements) {
|
|
3478
4313
|
if (i.unresolved) {
|
|
3479
|
-
const
|
|
4314
|
+
const publisher = lockedIds.find(
|
|
4315
|
+
(id) => id.kind === i.assetKind && id.name === i.assetName
|
|
4316
|
+
)?.publisher;
|
|
4317
|
+
const ref = publisher ? `${i.assetKind}:@${publisher}/${i.assetName}` : `${i.assetKind}:${i.assetName}`;
|
|
3480
4318
|
const reason = `is locked but no source or library provides it any more \u2014 its requirements cannot be checked${i.fix ? ` (${i.fix})` : ""}`;
|
|
3481
4319
|
gaps.push({
|
|
3482
4320
|
type: "unresolved",
|
|
@@ -3487,27 +4325,351 @@ var AssetManager = class {
|
|
|
3487
4325
|
});
|
|
3488
4326
|
continue;
|
|
3489
4327
|
}
|
|
3490
|
-
const asset =
|
|
4328
|
+
const asset = lockIdentity(i.assetKind, i.assetName);
|
|
3491
4329
|
const dep = `${i.depKind}:${i.depName}`;
|
|
3492
|
-
|
|
4330
|
+
if (i.inRepos) {
|
|
4331
|
+
gaps.push({
|
|
4332
|
+
type: "requires",
|
|
4333
|
+
ref: dep,
|
|
4334
|
+
asset,
|
|
4335
|
+
reason: `is required by ${asset} but neither deployed nor locked (fixable)`,
|
|
4336
|
+
message: `${asset} requires ${dep} (fixable)`
|
|
4337
|
+
});
|
|
4338
|
+
} else {
|
|
4339
|
+
const fix = requirementNoHolderFix(asset);
|
|
4340
|
+
gaps.push({
|
|
4341
|
+
type: "requires",
|
|
4342
|
+
ref: dep,
|
|
4343
|
+
asset,
|
|
4344
|
+
reason: `is required by ${asset} but neither deployed nor locked (not found \u2014 ${fix})`,
|
|
4345
|
+
message: `${asset} requires ${dep} (not found \u2014 ${fix})`,
|
|
4346
|
+
manualFix: true
|
|
4347
|
+
});
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
const named = new Set(
|
|
4351
|
+
gaps.filter((g) => g.type === "unresolved").map((g) => gapIdentity(g.ref))
|
|
4352
|
+
);
|
|
4353
|
+
for (const gap of await this.libraryOriginGaps(named)) {
|
|
4354
|
+
gaps.push(gap);
|
|
4355
|
+
named.add(gapIdentity(gap.ref));
|
|
4356
|
+
}
|
|
4357
|
+
const libraryHeld = (dep) => {
|
|
4358
|
+
const want = parseRefTolerant(dep);
|
|
4359
|
+
return want !== null && gaps.some((g) => g.type === "library" && sameAsset(g.ref, want));
|
|
4360
|
+
};
|
|
4361
|
+
gaps = gaps.filter((g) => !(g.type === "requires" && libraryHeld(g.ref)));
|
|
4362
|
+
for (const gap of this.undeployedLockGaps(lock, named)) {
|
|
4363
|
+
gaps.push(gap);
|
|
4364
|
+
named.add(gapIdentity(gap.ref));
|
|
4365
|
+
}
|
|
4366
|
+
for (const d of await this.lockedCopyDrift()) {
|
|
4367
|
+
if (named.has(gapIdentity(d.ref))) continue;
|
|
4368
|
+
const parts = [
|
|
4369
|
+
...d.modified.map((f) => `${f} was edited`),
|
|
4370
|
+
...d.extra.map((f) => `${f} is not part of the asset`)
|
|
4371
|
+
];
|
|
4372
|
+
const reason = `is locked, but its deployed copy differs from the lock (${parts.join("; ")}) \u2014 run \`skaile install --locked\` to restore it`;
|
|
4373
|
+
gaps.push({ type: "modified", ref: d.ref, reason, message: `${d.ref} ${reason}` });
|
|
4374
|
+
named.add(gapIdentity(d.ref));
|
|
4375
|
+
}
|
|
4376
|
+
gaps.push(...await this.declaredGaps(requirements, named));
|
|
4377
|
+
for (const d of await this.incompleteDeploys()) {
|
|
4378
|
+
const reason = `is deployed without ${d.missing.join(", ")} \u2014 run \`skaile install\` to restore it`;
|
|
4379
|
+
gaps.push({ type: "incomplete", ref: d.ref, reason, message: `${d.ref} ${reason}` });
|
|
4380
|
+
}
|
|
4381
|
+
for (const u of unboundAgentInstances(this.projectDir, this.driverTarget)) {
|
|
4382
|
+
const reason = u.ref.startsWith("agent:") ? await this.unboundAgentReason(u.id, u.ref) : `is bound by agent instance "${u.id}" but resolves to no agent.yaml \u2014 fix the path, or drop the binding from skaile.yaml`;
|
|
3493
4383
|
gaps.push({
|
|
3494
|
-
type: "
|
|
3495
|
-
ref:
|
|
3496
|
-
reason
|
|
3497
|
-
message: `${
|
|
4384
|
+
type: "agent",
|
|
4385
|
+
ref: u.ref,
|
|
4386
|
+
reason,
|
|
4387
|
+
message: `${u.ref} ${reason}`,
|
|
4388
|
+
manualFix: true
|
|
3498
4389
|
});
|
|
3499
4390
|
}
|
|
3500
|
-
|
|
4391
|
+
return {
|
|
4392
|
+
gaps,
|
|
4393
|
+
requirements: requirements.filter(
|
|
4394
|
+
(i) => i.unresolved || !libraryHeld(`${i.depKind}:${i.depName}`)
|
|
4395
|
+
)
|
|
4396
|
+
};
|
|
4397
|
+
}
|
|
4398
|
+
/**
|
|
4399
|
+
* Why the `agent:` ref of agent instance `id` binds to nothing, as the phrase that follows
|
|
4400
|
+
* the ref in a {@link LockGap}; see {@link AssetManager.unboundAgentAdvice}.
|
|
4401
|
+
*/
|
|
4402
|
+
async unboundAgentReason(id, ref) {
|
|
4403
|
+
const { lead, advice } = await this.unboundAgentAdvice(ref);
|
|
4404
|
+
return `is bound by agent instance "${id}" but ${lead} \u2014 ${advice}`;
|
|
4405
|
+
}
|
|
4406
|
+
/**
|
|
4407
|
+
* What is wrong with an `agent:` ref bound by an agent instance and what to do (`lead`, then
|
|
4408
|
+
* `advice`). `skaile add` is named only when a registered library or source holds the agent
|
|
4409
|
+
* and `add` takes the ref (it needs a publisher); otherwise the user registers the library
|
|
4410
|
+
* that holds it, writes the publisher, or drops the binding.
|
|
4411
|
+
*/
|
|
4412
|
+
async unboundAgentAdvice(ref) {
|
|
4413
|
+
const drop = "or drop the binding from skaile.yaml";
|
|
4414
|
+
const publisher = parseRefTolerant(ref)?.publisher;
|
|
4415
|
+
if (publisher && await this.info(ref) !== null) {
|
|
4416
|
+
return {
|
|
4417
|
+
lead: "no agent was added for it",
|
|
4418
|
+
advice: `run \`skaile add ${ref}\` first, ${drop}`
|
|
4419
|
+
};
|
|
4420
|
+
}
|
|
4421
|
+
if (!publisher) {
|
|
4422
|
+
return {
|
|
4423
|
+
lead: "names no publisher",
|
|
4424
|
+
advice: `write it as agent:@<publisher>/<name> (\`skaile add\` needs the publisher), ${drop}`
|
|
4425
|
+
};
|
|
4426
|
+
}
|
|
4427
|
+
const fix = (await this.unresolvedFixes([ref])).get(ref);
|
|
4428
|
+
if (fix) {
|
|
4429
|
+
const register = fix.replace(/, or drop the dependency with `skaile remove [^`]*`$/, "");
|
|
4430
|
+
return {
|
|
4431
|
+
lead: "no agent was added for it, and `skaile add` cannot resolve it",
|
|
4432
|
+
advice: `${register}, ${drop}`
|
|
4433
|
+
};
|
|
4434
|
+
}
|
|
4435
|
+
return {
|
|
4436
|
+
lead: "no registered library or source holds it",
|
|
4437
|
+
advice: `register the library that holds it with \`skaile library init <name> --path <dir>\`, ${drop}`
|
|
4438
|
+
};
|
|
4439
|
+
}
|
|
4440
|
+
/** @deprecated Use `(await projectState()).gaps`; removed in the next major. */
|
|
4441
|
+
async lockGaps() {
|
|
4442
|
+
return (await this.projectState()).gaps;
|
|
4443
|
+
}
|
|
4444
|
+
/**
|
|
4445
|
+
* The {@link ProjectState} gaps that name `ref`: as the gap's own ref, or as the asset
|
|
4446
|
+
* whose requirement is unmet. A ref without a publisher matches any publisher.
|
|
4447
|
+
*
|
|
4448
|
+
* @param ref - Asset ref in `kind:name` or `kind:@publisher/name` form.
|
|
4449
|
+
*/
|
|
4450
|
+
async gapsOf(ref) {
|
|
4451
|
+
const want = parseRefTolerant(ref);
|
|
4452
|
+
if (!want) return [];
|
|
4453
|
+
const names = (gapRef) => {
|
|
4454
|
+
const p = parseRefTolerant(gapRef);
|
|
4455
|
+
return p !== null && p.kind === want.kind && p.name === want.name && (!want.publisher || !p.publisher || p.publisher === want.publisher);
|
|
4456
|
+
};
|
|
4457
|
+
return (await this.projectState()).gaps.filter(
|
|
4458
|
+
(g) => names(g.ref) || g.asset && names(g.asset)
|
|
4459
|
+
);
|
|
4460
|
+
}
|
|
4461
|
+
/**
|
|
4462
|
+
* Locked library assets whose library cannot supply them: the library is not registered
|
|
4463
|
+
* (renamed or removed) or no longer holds the asset. Another library holding it makes the
|
|
4464
|
+
* gap fixable by `skaile install` (it re-locks); otherwise the user registers the library
|
|
4465
|
+
* again or drops the dependency.
|
|
4466
|
+
*
|
|
4467
|
+
* @param named - `kind:name` identities already reported; they are skipped.
|
|
4468
|
+
*/
|
|
4469
|
+
async libraryOriginGaps(named) {
|
|
4470
|
+
const locked = Object.entries(this.tryReadLock()?.assets ?? {}).filter(
|
|
4471
|
+
([, e]) => e.library !== void 0 && !e.library.installed
|
|
4472
|
+
);
|
|
4473
|
+
if (locked.length === 0) return [];
|
|
4474
|
+
const snap = await this.loadLibraryIndex();
|
|
4475
|
+
const scans = /* @__PURE__ */ new Map();
|
|
4476
|
+
const diskHashes = /* @__PURE__ */ new Map();
|
|
4477
|
+
const gaps = [];
|
|
4478
|
+
for (const [key, entry] of locked) {
|
|
4479
|
+
const origin = entry.library;
|
|
4480
|
+
const { publisher, kind, name } = parseCanonicalLockKey(key);
|
|
4481
|
+
if (!publisher || !kind || !name || named.has(`${kind}:${name}`)) continue;
|
|
4482
|
+
const ref = `${kind}:@${publisher}/${name}`;
|
|
4483
|
+
const lookup = this.lockedLibrary(origin, snap);
|
|
4484
|
+
const libPath = lookup.state === "ok" ? lookup.path : null;
|
|
4485
|
+
if (lookup.state === "ok" && libPath) {
|
|
4486
|
+
const scanKey = `${libPath}\0${publisher}`;
|
|
4487
|
+
let scanned = scans.get(scanKey);
|
|
4488
|
+
if (!scanned) {
|
|
4489
|
+
scanned = await scanDirectory(libPath, publisher, { knownKinds: this.knownKinds });
|
|
4490
|
+
scans.set(scanKey, scanned);
|
|
4491
|
+
}
|
|
4492
|
+
const found = scanned.find((x) => x.kind === kind && x.name === name);
|
|
4493
|
+
if (found) {
|
|
4494
|
+
const onDisk = origin.sha256 ? libraryDiskHashes(libPath, diskHashes, this.registry).get(`${kind}:${name}`) : void 0;
|
|
4495
|
+
if (onDisk !== void 0 && onDisk !== origin.sha256) {
|
|
4496
|
+
const diskVersion = found.version || newestLibraryDef(snap, lookup.name, { kind, publisher, name })?.version;
|
|
4497
|
+
if (!diskVersion || !gt(coerceVersion(diskVersion), coerceVersion(origin.version))) {
|
|
4498
|
+
const reason2 = `is locked, but library "${lookup.name}" content changed since lock \u2014 or the lock predates the one shared asset hash; run \`skaile install\` to re-lock it`;
|
|
4499
|
+
gaps.push({ type: "content", ref, reason: reason2, message: `${ref} ${reason2}` });
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
continue;
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
if (lookup.state === "dir-missing") {
|
|
4506
|
+
const reason2 = `is locked, but ${lockedLibraryProblem(lookup, origin, ref)}`;
|
|
4507
|
+
gaps.push({ type: "library", ref, reason: reason2, message: `${ref} ${reason2}`, manualFix: true });
|
|
4508
|
+
continue;
|
|
4509
|
+
}
|
|
4510
|
+
if (lookup.state === "unregistered") {
|
|
4511
|
+
const reason2 = `is locked, but library "${origin.name}" is not registered (renamed or removed?) \u2014 ${unregisteredLibraryFix(origin, ref)}`;
|
|
4512
|
+
gaps.push({ type: "library", ref, reason: reason2, message: `${ref} ${reason2}`, manualFix: true });
|
|
4513
|
+
continue;
|
|
4514
|
+
}
|
|
4515
|
+
const heldElsewhere = (await this.resolveLibraryEntries([ref], { quiet: true })).resolved.size > 0;
|
|
4516
|
+
if (heldElsewhere) {
|
|
4517
|
+
const reason2 = `is locked, but library "${lookup.name}" no longer holds it \u2014 run \`skaile install\` to re-lock it against the library that holds it now`;
|
|
4518
|
+
gaps.push({ type: "library", ref, reason: reason2, message: `${ref} ${reason2}` });
|
|
4519
|
+
continue;
|
|
4520
|
+
}
|
|
4521
|
+
const reason = `is locked, but library "${lookup.name}" no longer holds it \u2014 restore it there, or drop the dependency with \`skaile remove ${ref}\``;
|
|
4522
|
+
gaps.push({ type: "library", ref, reason, message: `${ref} ${reason}`, manualFix: true });
|
|
4523
|
+
}
|
|
4524
|
+
return gaps;
|
|
4525
|
+
}
|
|
4526
|
+
/**
|
|
4527
|
+
* The {@link RestoredAsset} rows for the drifted copies (see {@link lockedCopyDrift}) a
|
|
4528
|
+
* deploy just wrote again: those whose ref is among the `deployed` canonical refs.
|
|
4529
|
+
*/
|
|
4530
|
+
restoredCopies(drifts, deployed) {
|
|
4531
|
+
const rewritten = new Set(deployed.map((d) => d.split("#")[0]));
|
|
4532
|
+
const out = [];
|
|
4533
|
+
for (const d of drifts) {
|
|
4534
|
+
if (!rewritten.has(d.ref)) continue;
|
|
4535
|
+
const { kind, name } = parseCanonicalLockKey(d.key);
|
|
4536
|
+
const component = kind ? this.installTarget(kind) : void 0;
|
|
4537
|
+
if (!kind || !name || !component) continue;
|
|
4538
|
+
const dir = relative(
|
|
4539
|
+
this.projectDir,
|
|
4540
|
+
deployedDir(component, name, this.driverTarget, this.global, this.projectDir)
|
|
4541
|
+
).split(sep).join("/");
|
|
4542
|
+
out.push({
|
|
4543
|
+
ref: d.key,
|
|
4544
|
+
restored: d.modified.map((f) => `${dir}/${f}`),
|
|
4545
|
+
removed: d.extra.map((f) => `${dir}/${f}`)
|
|
4546
|
+
});
|
|
4547
|
+
}
|
|
4548
|
+
return out;
|
|
4549
|
+
}
|
|
4550
|
+
/**
|
|
4551
|
+
* Locked assets whose deployed copy differs from the bytes the lock covers: a file edited by
|
|
4552
|
+
* hand, or a file the asset does not ship. One row per asset, `ref` being `kind:@publisher/name`
|
|
4553
|
+
* and `key` its lock key. The one comparison behind the `modified` gap and the `Restored` lines
|
|
4554
|
+
* of `install`.
|
|
4555
|
+
*
|
|
4556
|
+
* - A library-origin asset is compared with its library's files, but only while those equal the
|
|
4557
|
+
* locked hash (changed library content is the `content` gap's business), both modified and
|
|
4558
|
+
* extra files.
|
|
4559
|
+
* - A source asset is compared with the per-file hashes the lock records (modified files only:
|
|
4560
|
+
* the lock lists what the source covers, not what the deploy copies).
|
|
4561
|
+
*
|
|
4562
|
+
* An agent (rendered, not copied), a `--link` deploy, an asset with a configured patch, and a
|
|
4563
|
+
* copy that is missing are not compared: other gaps cover them. Project mode only.
|
|
4564
|
+
*/
|
|
4565
|
+
async lockedCopyDrift() {
|
|
4566
|
+
if (this.global) return [];
|
|
4567
|
+
const lock = this.tryReadLock();
|
|
4568
|
+
if (!lock) return [];
|
|
4569
|
+
const { patches } = this.loadConfig();
|
|
4570
|
+
const out = [];
|
|
4571
|
+
const deployOpts = {
|
|
4572
|
+
registry: this.registry,
|
|
4573
|
+
driverTarget: this.driverTarget,
|
|
4574
|
+
global: this.global,
|
|
4575
|
+
cwd: this.projectDir
|
|
4576
|
+
};
|
|
4577
|
+
const refOf = (key) => {
|
|
4578
|
+
const { kind, publisher, name } = parseCanonicalLockKey(key);
|
|
4579
|
+
return publisher ? `${kind}:@${publisher}/${name}` : `${kind}:${name}`;
|
|
4580
|
+
};
|
|
4581
|
+
const keyOf = /* @__PURE__ */ new Map();
|
|
4582
|
+
for (const key of Object.keys(lock.assets)) keyOf.set(refOf(key), key);
|
|
4583
|
+
const { entries } = await this.restoreLibraryLockEntries(lock);
|
|
4584
|
+
for (const e of entries) {
|
|
4585
|
+
if (patches[`${e.kind}:${e.name}`]) continue;
|
|
4586
|
+
const drift = deployedAssetDrift(e, deployOpts);
|
|
4587
|
+
if (!drift || drift.modified.length === 0 && drift.extra.length === 0) continue;
|
|
4588
|
+
const ref = `${e.kind}:@${(e.publisher ?? "").replace(/^@/, "")}/${e.name}`;
|
|
4589
|
+
out.push({ ref, key: keyOf.get(ref) ?? ref, ...drift, library: true });
|
|
4590
|
+
}
|
|
4591
|
+
for (const [key, entry] of Object.entries(lock.assets)) {
|
|
4592
|
+
if (!entry.source || entry.files.length === 0) continue;
|
|
4593
|
+
const { kind, name } = parseCanonicalLockKey(key);
|
|
4594
|
+
const component = kind ? this.installTarget(kind) : void 0;
|
|
4595
|
+
if (!kind || !name || !component || component === "agent") continue;
|
|
4596
|
+
if (patches[`${kind}:${name}`]) continue;
|
|
4597
|
+
const dest = deployedDir(component, name, this.driverTarget, this.global, this.projectDir);
|
|
4598
|
+
try {
|
|
4599
|
+
const deployed = lstatSync(dest);
|
|
4600
|
+
if (deployed.isSymbolicLink() || !deployed.isDirectory()) continue;
|
|
4601
|
+
} catch {
|
|
4602
|
+
continue;
|
|
4603
|
+
}
|
|
4604
|
+
const dirs = entry.files.map(
|
|
4605
|
+
(f) => posix.dirname(f.path).split("/").filter((s) => s !== ".")
|
|
4606
|
+
);
|
|
4607
|
+
let common = dirs[0] ?? [];
|
|
4608
|
+
for (const d of dirs.slice(1)) {
|
|
4609
|
+
let i = 0;
|
|
4610
|
+
while (i < common.length && i < d.length && common[i] === d[i]) i++;
|
|
4611
|
+
common = common.slice(0, i);
|
|
4612
|
+
}
|
|
4613
|
+
const prefix = common.length > 0 ? `${common.join("/")}/` : "";
|
|
4614
|
+
const modified = [];
|
|
4615
|
+
for (const f of entry.files) {
|
|
4616
|
+
const rel = f.path.startsWith(prefix) ? f.path.slice(prefix.length) : f.path;
|
|
4617
|
+
const deployedFile = join(dest, rel);
|
|
4618
|
+
if (!existsSync(deployedFile)) continue;
|
|
4619
|
+
try {
|
|
4620
|
+
if (computeHash(deployedFile) !== f.sha256) modified.push(rel);
|
|
4621
|
+
} catch {
|
|
4622
|
+
modified.push(rel);
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
if (modified.length > 0)
|
|
4626
|
+
out.push({ ref: refOf(key), key, modified, extra: [], library: false });
|
|
4627
|
+
}
|
|
4628
|
+
return out;
|
|
4629
|
+
}
|
|
4630
|
+
/**
|
|
4631
|
+
* Locked assets (declared or required by one) whose deployed copy is gone: `skaile install`
|
|
4632
|
+
* restores them. Same deployability rule as {@link AssetManager.listDeclared}'s `drift`.
|
|
4633
|
+
*
|
|
4634
|
+
* @param named - `kind:name` identities already reported; they are skipped.
|
|
4635
|
+
*/
|
|
4636
|
+
undeployedLockGaps(lock, named) {
|
|
4637
|
+
const gaps = [];
|
|
4638
|
+
for (const [key, entry] of Object.entries(lock?.assets ?? {})) {
|
|
4639
|
+
if (entry.library?.installed) continue;
|
|
4640
|
+
const { publisher, kind, name } = parseCanonicalLockKey(key);
|
|
4641
|
+
const component = kind ? this.installTarget(kind) : void 0;
|
|
4642
|
+
if (!kind || !name || !component || named.has(`${kind}:${name}`)) continue;
|
|
4643
|
+
if (isDeployed(component, name, this.driverTarget, this.global, this.projectDir)) continue;
|
|
4644
|
+
const ref = publisher ? `${kind}:@${publisher}/${name}` : `${kind}:${name}`;
|
|
4645
|
+
const reason = "is locked but its deployed copy is missing \u2014 run `skaile install`";
|
|
4646
|
+
gaps.push({ type: "drift", ref, reason, message: `${ref} ${reason}` });
|
|
4647
|
+
}
|
|
4648
|
+
return gaps;
|
|
4649
|
+
}
|
|
4650
|
+
/**
|
|
4651
|
+
* The `skaile.yaml` side of {@link AssetManager.projectState}: declared refs that do not
|
|
4652
|
+
* parse, never resolved into the lock, are not materialized, or contradict a `#pin`.
|
|
4653
|
+
* A project with no (or an unreadable) `skaile.yaml` has no declared set to reconcile.
|
|
4654
|
+
*
|
|
4655
|
+
* @param requirements - What {@link AssetManager.doctor} found; a declared preset whose
|
|
4656
|
+
* items it already named needs no second line.
|
|
4657
|
+
* @param named - `kind:name` identities already reported; a declared asset among them
|
|
4658
|
+
* needs no `drift` line.
|
|
4659
|
+
*/
|
|
4660
|
+
async declaredGaps(requirements, named) {
|
|
4661
|
+
let declared;
|
|
3501
4662
|
try {
|
|
3502
4663
|
declared = await this.listDeclared();
|
|
3503
4664
|
} catch {
|
|
3504
|
-
return
|
|
4665
|
+
return [];
|
|
3505
4666
|
}
|
|
4667
|
+
const gaps = [];
|
|
3506
4668
|
const unlocked = declared.filter((d) => d.state === "declared").map((d) => d.ref);
|
|
3507
4669
|
const hints = await this.publisherChangeHints(unlocked);
|
|
3508
4670
|
const lockKeys = Object.keys(this.tryReadLock()?.assets ?? {});
|
|
3509
4671
|
for (const d of declared) {
|
|
3510
|
-
if (d.kind === "preset" &&
|
|
4672
|
+
if (d.kind === "preset" && requirements.some((i) => i.assetKind === "preset" && i.assetName === d.name)) {
|
|
3511
4673
|
continue;
|
|
3512
4674
|
}
|
|
3513
4675
|
let gap;
|
|
@@ -3517,19 +4679,41 @@ var AssetManager = class {
|
|
|
3517
4679
|
reason: "is not a valid asset ref (expected kind:@<publisher>/<name>#<version>)"
|
|
3518
4680
|
};
|
|
3519
4681
|
} else if (d.state === "drift") {
|
|
4682
|
+
if (named.has(`${d.kind}:${d.name}`)) continue;
|
|
3520
4683
|
gap = {
|
|
3521
4684
|
type: "drift",
|
|
3522
4685
|
reason: "is declared and locked but not materialized \u2014 run `skaile install`"
|
|
3523
4686
|
};
|
|
3524
4687
|
} else if (d.state === "declared") {
|
|
3525
4688
|
const hint = hints[d.ref];
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
4689
|
+
if (hint) {
|
|
4690
|
+
gap = {
|
|
4691
|
+
type: "declared",
|
|
4692
|
+
reason: `is declared but does not resolve${hint}`,
|
|
4693
|
+
manualFix: true
|
|
4694
|
+
};
|
|
4695
|
+
} else if (await this.declaredRefResolvable(d.ref)) {
|
|
4696
|
+
gap = { type: "declared", reason: "is declared but not locked \u2014 run `skaile install`" };
|
|
4697
|
+
} else {
|
|
4698
|
+
gap = {
|
|
4699
|
+
type: "declared",
|
|
4700
|
+
reason: declaredNoHolderReason(d.ref),
|
|
4701
|
+
manualFix: true
|
|
4702
|
+
};
|
|
4703
|
+
}
|
|
3530
4704
|
} else {
|
|
3531
4705
|
const want = parseAssetRef(d.ref);
|
|
3532
|
-
if (
|
|
4706
|
+
if (want.kind === "preset") {
|
|
4707
|
+
if (this.presetRecorded(want) || await this.info(d.ref) !== null) continue;
|
|
4708
|
+
gap = await this.declaredRefResolvable(d.ref) ? { type: "declared", reason: "is declared but not locked \u2014 run `skaile install`" } : {
|
|
4709
|
+
type: "declared",
|
|
4710
|
+
reason: declaredNoHolderReason(d.ref),
|
|
4711
|
+
manualFix: true
|
|
4712
|
+
};
|
|
4713
|
+
gaps.push({ ...gap, ref: d.ref, message: `${d.ref} ${gap.reason}` });
|
|
4714
|
+
continue;
|
|
4715
|
+
}
|
|
4716
|
+
if (!want.pin) continue;
|
|
3533
4717
|
const held = lockKeys.map((key) => ({ key, id: parseCanonicalLockKey(key) })).filter(
|
|
3534
4718
|
({ id }) => id.kind === want.kind && id.name === want.name && (!want.publisher || id.publisher === want.publisher)
|
|
3535
4719
|
);
|
|
@@ -3544,6 +4728,22 @@ var AssetManager = class {
|
|
|
3544
4728
|
}
|
|
3545
4729
|
return gaps;
|
|
3546
4730
|
}
|
|
4731
|
+
/** Whether the lock's `presets` slice records `want` (kind, name and, when both carry one, publisher). */
|
|
4732
|
+
presetRecorded(want) {
|
|
4733
|
+
return (this.tryReadLock()?.presets ?? []).some((ref) => {
|
|
4734
|
+
const p = parseRefTolerant(ref);
|
|
4735
|
+
return p !== null && p.kind === want.kind && p.name === want.name && (!want.publisher || !p.publisher || p.publisher === want.publisher);
|
|
4736
|
+
});
|
|
4737
|
+
}
|
|
4738
|
+
/**
|
|
4739
|
+
* Whether `skaile install` can resolve a declared ref: a source or library holds it, or the
|
|
4740
|
+
* project declares a store that may. The global catalog is not asked: a check never goes
|
|
4741
|
+
* to the network.
|
|
4742
|
+
*/
|
|
4743
|
+
async declaredRefResolvable(ref) {
|
|
4744
|
+
if (await this.info(ref) !== null) return true;
|
|
4745
|
+
return this.loadConfig().stores.length > 0;
|
|
4746
|
+
}
|
|
3547
4747
|
/**
|
|
3548
4748
|
* Items of each declared `preset:` dependency that the lock does not cover.
|
|
3549
4749
|
*
|
|
@@ -3810,6 +5010,54 @@ var AssetManager = class {
|
|
|
3810
5010
|
writeFileSync(configPath, stringify(raw, { lineWidth: 120 }));
|
|
3811
5011
|
return true;
|
|
3812
5012
|
}
|
|
5013
|
+
/**
|
|
5014
|
+
* Drop the agent instances of `skaile.yaml` (`agents.<id>`, or the `agent.definition`
|
|
5015
|
+
* default) whose `ref` binds to `target`, and the native agent files an install rendered for
|
|
5016
|
+
* them. Only an `agent` target has bindings; project mode only. A `ref` without a publisher
|
|
5017
|
+
* matches any publisher, and a pin is ignored.
|
|
5018
|
+
*
|
|
5019
|
+
* @returns `true` when a binding was removed.
|
|
5020
|
+
*/
|
|
5021
|
+
removeAgentBindings(target) {
|
|
5022
|
+
if (this.global || target.kind !== "agent") return false;
|
|
5023
|
+
const configPath = join(this.stateRoot, "skaile.yaml");
|
|
5024
|
+
if (!existsSync(configPath)) return false;
|
|
5025
|
+
let raw;
|
|
5026
|
+
try {
|
|
5027
|
+
raw = parse(readFileSync(configPath, "utf8"));
|
|
5028
|
+
} catch {
|
|
5029
|
+
return false;
|
|
5030
|
+
}
|
|
5031
|
+
if (!raw || typeof raw !== "object") return false;
|
|
5032
|
+
const wantPub = target.publisher?.replace(/^@/, "");
|
|
5033
|
+
const binds = (ref) => {
|
|
5034
|
+
const p = typeof ref === "string" ? parseRefTolerant(ref) : null;
|
|
5035
|
+
if (!p || p.kind !== "agent" || p.name !== target.name) return false;
|
|
5036
|
+
const havePub = p.publisher?.replace(/^@/, "");
|
|
5037
|
+
return wantPub === void 0 || havePub === void 0 || havePub === wantPub;
|
|
5038
|
+
};
|
|
5039
|
+
const ids = [];
|
|
5040
|
+
const instances = raw.agents;
|
|
5041
|
+
if (instances && typeof instances === "object") {
|
|
5042
|
+
for (const [id, instance] of Object.entries(instances)) {
|
|
5043
|
+
if (!binds(instance?.ref)) continue;
|
|
5044
|
+
ids.push(id);
|
|
5045
|
+
delete instances[id];
|
|
5046
|
+
}
|
|
5047
|
+
if (Object.keys(instances).length === 0) delete raw.agents;
|
|
5048
|
+
}
|
|
5049
|
+
if (raw.agent && typeof raw.agent === "object" && binds(raw.agent.definition)) {
|
|
5050
|
+
ids.push(DEFAULT_AGENT_INSTANCE_ID);
|
|
5051
|
+
delete raw.agent.definition;
|
|
5052
|
+
if (Object.keys(raw.agent).length === 0) delete raw.agent;
|
|
5053
|
+
}
|
|
5054
|
+
if (ids.length === 0) return false;
|
|
5055
|
+
writeFileSync(configPath, stringify(raw, { lineWidth: 120 }));
|
|
5056
|
+
for (const driverTarget of this.targetHarnesses()) {
|
|
5057
|
+
removeAgentInstanceRenderings({ projectDir: this.projectDir, driverTarget, ids });
|
|
5058
|
+
}
|
|
5059
|
+
return true;
|
|
5060
|
+
}
|
|
3813
5061
|
resolveRepoDir(decl, name) {
|
|
3814
5062
|
const links = readLinks(this.projectDir);
|
|
3815
5063
|
if (links[name]) {
|
|
@@ -3828,5 +5076,5 @@ var AssetManager = class {
|
|
|
3828
5076
|
};
|
|
3829
5077
|
|
|
3830
5078
|
export { AssetManager, FACTORY_SOURCE_URL, MissingManifestError, installAgentInstances, normalizeRepoUrl, parseCanonicalLockKey, parseRefTolerant, renderMcpServerConfig, softDepName };
|
|
3831
|
-
//# sourceMappingURL=chunk-
|
|
3832
|
-
//# sourceMappingURL=chunk-
|
|
5079
|
+
//# sourceMappingURL=chunk-DZMNFCWB.js.map
|
|
5080
|
+
//# sourceMappingURL=chunk-DZMNFCWB.js.map
|