@skaile/workspaces 0.29.1 → 0.30.1
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 +40 -0
- package/dist/{asset-feeds-2V7LZIG7.js → asset-feeds-IWOBYROH.js} +14 -13
- package/dist/asset-feeds-IWOBYROH.js.map +1 -0
- package/dist/asset-manager/index.js +12 -12
- package/dist/asset-manager/installer.js +11 -11
- package/dist/asset-manager/renderers.js +2 -2
- package/dist/asset-manager/src/index.d.ts +27 -0
- package/dist/asset-manager/src/index.d.ts.map +1 -1
- package/dist/asset-manager/src/install-agent.d.ts.map +1 -1
- package/dist/asset-manager/src/installer.d.ts +1 -2
- package/dist/asset-manager/src/installer.d.ts.map +1 -1
- package/dist/asset-manager/src/renderers.d.ts.map +1 -1
- package/dist/bridge/drivers/claude-sdk.js +324 -226
- package/dist/bridge/drivers/claude-sdk.js.map +1 -1
- package/dist/bridge/drivers/codex.js +3 -3
- package/dist/bridge/drivers/codex.js.map +1 -1
- package/dist/bridge/drivers/continue.js +4 -4
- package/dist/bridge/drivers/echo.js +2 -2
- package/dist/bridge/drivers/echo.js.map +1 -1
- package/dist/bridge/drivers/gemini.js +5 -5
- package/dist/bridge/drivers/goose.js +4 -4
- package/dist/bridge/drivers/omp.js +3 -3
- package/dist/bridge/drivers/opencode.js +4 -4
- package/dist/bridge/drivers/qwen.js +5 -5
- package/dist/bridge/index.js +4 -4
- package/dist/bridge/src/drivers/claude-sdk.d.ts +64 -0
- package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
- package/dist/bridge/src/drivers/echo.d.ts +1 -1
- package/dist/bridge/src/drivers/echo.d.ts.map +1 -1
- package/dist/bridge/src/error-classifier.d.ts.map +1 -1
- package/dist/bridge/src/normalizer.d.ts +5 -0
- package/dist/bridge/src/normalizer.d.ts.map +1 -1
- package/dist/{chunk-LDUNTZB6.js → chunk-26L2B6P6.js} +2 -2
- package/dist/{chunk-LDUNTZB6.js.map → chunk-26L2B6P6.js.map} +1 -1
- package/dist/{chunk-FWHOU3CX.js → chunk-2VKVWN5E.js} +6 -6
- package/dist/{chunk-FWHOU3CX.js.map → chunk-2VKVWN5E.js.map} +1 -1
- package/dist/{chunk-2FS4FPZ7.js → chunk-4AGIEU7V.js} +44 -62
- package/dist/chunk-4AGIEU7V.js.map +1 -0
- package/dist/{chunk-ZONCCO5N.js → chunk-4R6DROGN.js} +30 -32
- package/dist/chunk-4R6DROGN.js.map +1 -0
- package/dist/{chunk-3QQSSRYN.js → chunk-5DQOZFWJ.js} +416 -345
- package/dist/chunk-5DQOZFWJ.js.map +1 -0
- package/dist/{chunk-LD4QIVBY.js → chunk-5MJJYIL4.js} +3 -3
- package/dist/{chunk-LD4QIVBY.js.map → chunk-5MJJYIL4.js.map} +1 -1
- package/dist/{chunk-AKSR434V.js → chunk-5XAQDOMU.js} +79 -56
- package/dist/chunk-5XAQDOMU.js.map +1 -0
- package/dist/{chunk-MBOR7D65.js → chunk-67MFR5LS.js} +3 -3
- package/dist/{chunk-MBOR7D65.js.map → chunk-67MFR5LS.js.map} +1 -1
- package/dist/{chunk-7GWYRVAX.js → chunk-6LW7Q53Z.js} +162 -165
- package/dist/chunk-6LW7Q53Z.js.map +1 -0
- package/dist/{chunk-SF4GWVR5.js → chunk-6TCOYFVJ.js} +86 -55
- package/dist/chunk-6TCOYFVJ.js.map +1 -0
- package/dist/{chunk-QMONOHXT.js → chunk-74UKG2LA.js} +3 -3
- package/dist/{chunk-QMONOHXT.js.map → chunk-74UKG2LA.js.map} +1 -1
- package/dist/{chunk-JIQLLZ65.js → chunk-7MANNJ3U.js} +534 -235
- package/dist/chunk-7MANNJ3U.js.map +1 -0
- package/dist/{chunk-7R6W5EQR.js → chunk-ACXLKNIM.js} +2 -2
- package/dist/{chunk-7R6W5EQR.js.map → chunk-ACXLKNIM.js.map} +1 -1
- package/dist/{chunk-72IA6BTP.js → chunk-AKYEI3D6.js} +9 -9
- package/dist/{chunk-72IA6BTP.js.map → chunk-AKYEI3D6.js.map} +1 -1
- package/dist/{chunk-CMG3GJ6B.js → chunk-DAI754GK.js} +4 -4
- package/dist/{chunk-CMG3GJ6B.js.map → chunk-DAI754GK.js.map} +1 -1
- package/dist/{chunk-OTFG5Q7H.js → chunk-FRRTUVUM.js} +4594 -4480
- package/dist/chunk-FRRTUVUM.js.map +1 -0
- package/dist/{chunk-TV72ZJYN.js → chunk-GXC3TYMQ.js} +60 -47
- package/dist/chunk-GXC3TYMQ.js.map +1 -0
- package/dist/{chunk-IERB3UXJ.js → chunk-H5YXL4KI.js} +83 -69
- package/dist/chunk-H5YXL4KI.js.map +1 -0
- package/dist/{chunk-QDJKE557.js → chunk-HRKB2ATT.js} +64 -64
- package/dist/chunk-HRKB2ATT.js.map +1 -0
- package/dist/{chunk-ZEKWHJBB.js → chunk-IZIFMNTI.js} +3 -3
- package/dist/{chunk-ZEKWHJBB.js.map → chunk-IZIFMNTI.js.map} +1 -1
- package/dist/{chunk-24UIWON4.js → chunk-JB35MVBN.js} +167 -156
- package/dist/chunk-JB35MVBN.js.map +1 -0
- package/dist/{chunk-UIZKIUC6.js → chunk-KHZI44SS.js} +3 -3
- package/dist/chunk-KHZI44SS.js.map +1 -0
- package/dist/{chunk-5YO3H5Q4.js → chunk-KY5NY7LB.js} +3 -3
- package/dist/chunk-KY5NY7LB.js.map +1 -0
- package/dist/{chunk-MIH45XPY.js → chunk-LA2YSXEE.js} +1974 -1919
- package/dist/chunk-LA2YSXEE.js.map +1 -0
- package/dist/{chunk-IEJ5IOC3.js → chunk-LFDZYCEO.js} +4 -2
- package/dist/chunk-LFDZYCEO.js.map +1 -0
- package/dist/{chunk-2NOE4AGD.js → chunk-LIK75OJ5.js} +3 -3
- package/dist/{chunk-2NOE4AGD.js.map → chunk-LIK75OJ5.js.map} +1 -1
- package/dist/{chunk-PAYAFNXG.js → chunk-NGJ6E344.js} +104 -74
- package/dist/chunk-NGJ6E344.js.map +1 -0
- package/dist/{chunk-5V5KZQIC.js → chunk-PVMAIVNL.js} +4 -4
- package/dist/{chunk-5V5KZQIC.js.map → chunk-PVMAIVNL.js.map} +1 -1
- package/dist/{chunk-HK5FRWG6.js → chunk-R6O6YWU7.js} +2 -2
- package/dist/{chunk-HK5FRWG6.js.map → chunk-R6O6YWU7.js.map} +1 -1
- package/dist/{chunk-PRLD7EXG.js → chunk-SBGHNXJD.js} +2 -2
- package/dist/{chunk-PRLD7EXG.js.map → chunk-SBGHNXJD.js.map} +1 -1
- package/dist/{chunk-5RBPPBEK.js → chunk-SCJD3FMO.js} +4 -4
- package/dist/{chunk-5RBPPBEK.js.map → chunk-SCJD3FMO.js.map} +1 -1
- package/dist/{chunk-BZ2JU7IP.js → chunk-TU4VZULO.js} +5 -5
- package/dist/{chunk-BZ2JU7IP.js.map → chunk-TU4VZULO.js.map} +1 -1
- package/dist/{chunk-DVMSGGCM.js → chunk-VGFYUD3T.js} +158 -108
- package/dist/chunk-VGFYUD3T.js.map +1 -0
- package/dist/{chunk-Z3XXKR5E.js → chunk-WDMH4UMD.js} +4 -6
- package/dist/chunk-WDMH4UMD.js.map +1 -0
- package/dist/{chunk-W7W66MDZ.js → chunk-X46FY45B.js} +44 -42
- package/dist/chunk-X46FY45B.js.map +1 -0
- package/dist/{chunk-JKNWJ64A.js → chunk-XGCXFX7X.js} +2 -2
- package/dist/{chunk-JKNWJ64A.js.map → chunk-XGCXFX7X.js.map} +1 -1
- package/dist/{chunk-MLJKA3S6.js → chunk-XOAF5QAE.js} +2094 -1901
- package/dist/chunk-XOAF5QAE.js.map +1 -0
- package/dist/{chunk-FQHHCICT.js → chunk-XVDXGQ55.js} +226 -226
- package/dist/chunk-XVDXGQ55.js.map +1 -0
- package/dist/{chunk-M4KLIEIJ.js → chunk-YBJLYT4S.js} +3 -3
- package/dist/chunk-YBJLYT4S.js.map +1 -0
- package/dist/{chunk-OKRUTSG7.js → chunk-ZDKJHQY2.js} +2 -2
- package/dist/{chunk-OKRUTSG7.js.map → chunk-ZDKJHQY2.js.map} +1 -1
- package/dist/{chunk-UBCZVONU.js → chunk-ZME3JDA6.js} +3 -3
- package/dist/{chunk-UBCZVONU.js.map → chunk-ZME3JDA6.js.map} +1 -1
- package/dist/cli/index.js +2811 -2662
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/src/asset-feeds.d.ts +0 -9
- package/dist/cli/src/asset-feeds.d.ts.map +1 -1
- package/dist/cli/src/commands/catalog-cmd.d.ts.map +1 -1
- package/dist/cli/src/commands/catalog.d.ts.map +1 -1
- package/dist/cli/src/commands/config.d.ts.map +1 -1
- package/dist/cli/src/commands/deploy.d.ts.map +1 -1
- package/dist/cli/src/commands/integration.d.ts.map +1 -1
- package/dist/cli/src/commands/library-cmd.d.ts.map +1 -1
- package/dist/cli/src/commands/manage.d.ts.map +1 -1
- package/dist/cli/src/commands/preset-cmd.d.ts.map +1 -1
- package/dist/cli/src/commands/project.d.ts +1 -1
- package/dist/cli/src/commands/project.d.ts.map +1 -1
- package/dist/cli/src/commands/show.d.ts.map +1 -1
- package/dist/cli/src/commands/source.d.ts.map +1 -1
- package/dist/cli/src/commands/validate.d.ts.map +1 -1
- package/dist/cli/src/ensure-sources.d.ts.map +1 -1
- package/dist/cli/src/flow-discovery.d.ts.map +1 -1
- package/dist/cli/src/open-registry.d.ts.map +1 -1
- package/dist/cli/src/plugin-store/index.d.ts +4 -6
- package/dist/cli/src/plugin-store/index.d.ts.map +1 -1
- package/dist/cli/src/plugin-store/paths.d.ts +0 -2
- package/dist/cli/src/plugin-store/paths.d.ts.map +1 -1
- package/dist/cli/src/setup.d.ts.map +1 -1
- package/dist/connectors/config.js +9 -9
- package/dist/connectors/index.js +12 -12
- package/dist/connectors/rclone.js +2 -2
- package/dist/connectors/src/connector-prompt.d.ts.map +1 -1
- package/dist/connectors/src/connector-registry.d.ts.map +1 -1
- package/dist/connectors/src/connector-types.d.ts +1 -1
- package/dist/connectors/src/connector-types.d.ts.map +1 -1
- package/dist/connectors/src/index.d.ts +5 -5
- package/dist/connectors/src/index.d.ts.map +1 -1
- package/dist/connectors/src/managed-gitconfig.d.ts.map +1 -1
- package/dist/{connectors-PTCSHCHZ.js → connectors-DY7UBBV5.js} +3 -3
- package/dist/{connectors-PTCSHCHZ.js.map → connectors-DY7UBBV5.js.map} +1 -1
- package/dist/connectors-shared/src/index.d.ts +1 -1
- package/dist/connectors-shared/src/index.d.ts.map +1 -1
- package/dist/core/index.js +8 -8
- 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/runtime-assets.js +5 -5
- package/dist/core/src/lock.d.ts +0 -9
- package/dist/core/src/lock.d.ts.map +1 -1
- package/dist/core/src/logging/create-logger.d.ts.map +1 -1
- package/dist/core/src/logging/index.d.ts +11 -11
- package/dist/core/src/logging/index.d.ts.map +1 -1
- package/dist/core/src/logging/log-store.d.ts.map +1 -1
- package/dist/core/src/logging/sinks/no-op-sink.d.ts.map +1 -1
- package/dist/core/src/logging/sinks/sqlite-sink.d.ts.map +1 -1
- package/dist/core/src/manifest.d.ts.map +1 -1
- package/dist/core/src/models.d.ts +6 -0
- package/dist/core/src/models.d.ts.map +1 -1
- package/dist/core/src/publish-manifest.d.ts.map +1 -1
- package/dist/core/src/repo-manager.d.ts +0 -12
- package/dist/core/src/repo-manager.d.ts.map +1 -1
- package/dist/core/src/runtime-assets.d.ts.map +1 -1
- package/dist/core/src/store.d.ts +1 -1
- package/dist/core/src/store.d.ts.map +1 -1
- package/dist/core/src/walker.d.ts.map +1 -1
- package/dist/core/src/workspace-config.d.ts +0 -27
- package/dist/core/src/workspace-config.d.ts.map +1 -1
- package/dist/core/store.js +1 -1
- package/dist/core/workspace-config.js +4 -4
- package/dist/deploy/index.js +8 -8
- package/dist/discovery/index.js +6 -6
- package/dist/discovery/src/asset-kind-registry.d.ts +1 -1
- package/dist/discovery/src/asset-kind-registry.d.ts.map +1 -1
- package/dist/discovery/src/builtin-providers.d.ts.map +1 -1
- package/dist/discovery/src/discover-manifest.d.ts +1 -1
- package/dist/discovery/src/discover-manifest.d.ts.map +1 -1
- package/dist/discovery/src/discover.d.ts +30 -3
- package/dist/discovery/src/discover.d.ts.map +1 -1
- package/dist/discovery/src/domains.d.ts +144 -0
- package/dist/discovery/src/domains.d.ts.map +1 -0
- package/dist/discovery/src/index.d.ts +9 -7
- package/dist/discovery/src/index.d.ts.map +1 -1
- package/dist/discovery/src/source-config.d.ts +1 -1
- package/dist/discovery/src/source-config.d.ts.map +1 -1
- package/dist/discovery/src/tree-entries.d.ts +13 -1
- package/dist/discovery/src/tree-entries.d.ts.map +1 -1
- package/dist/ensure-sources-6GW6UBXY.js +84 -0
- package/dist/ensure-sources-6GW6UBXY.js.map +1 -0
- package/dist/factory-assets/connectors/deploy/adapter.d.ts +1 -1
- package/dist/factory-assets/connectors/deploy/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/deploy.js +12 -12
- package/dist/factory-assets/connectors/devserver/adapter.d.ts +1 -1
- package/dist/factory-assets/connectors/devserver/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/devserver.js +12 -12
- package/dist/factory-assets/connectors/flow/adapter.d.ts +16 -2
- package/dist/factory-assets/connectors/flow/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/flow/adapter.js +12 -12
- package/dist/factory-assets/connectors/flow/engine/flow-kind-provider.d.ts.map +1 -1
- package/dist/factory-assets/connectors/flow/engine/index.d.ts +3 -3
- package/dist/factory-assets/connectors/flow/engine/index.d.ts.map +1 -1
- package/dist/factory-assets/connectors/flow/engine.js +9 -9
- package/dist/factory-assets/connectors/flow/index.d.ts +1 -1
- package/dist/factory-assets/connectors/flow/index.d.ts.map +1 -1
- package/dist/factory-assets/connectors/flow/run-flow.js +15 -15
- package/dist/factory-assets/connectors/flow.js +12 -12
- package/dist/factory-assets/connectors/git/driver.d.ts +32 -0
- package/dist/factory-assets/connectors/git/driver.d.ts.map +1 -1
- package/dist/factory-assets/connectors/git.js +12 -12
- package/dist/factory-assets/connectors/gmail/adapter.d.ts +1 -1
- package/dist/factory-assets/connectors/gmail/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/gmail.js +12 -12
- package/dist/factory-assets/connectors/googledrive.js +12 -12
- package/dist/factory-assets/connectors/local/driver.d.ts +1 -2
- package/dist/factory-assets/connectors/local/driver.d.ts.map +1 -1
- package/dist/factory-assets/connectors/local.js +12 -12
- package/dist/factory-assets/connectors/mattermost/adapter.d.ts +1 -1
- package/dist/factory-assets/connectors/mattermost/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/mattermost.js +12 -12
- package/dist/factory-assets/connectors/memory/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/memory.js +12 -12
- package/dist/factory-assets/connectors/minio/adapter.d.ts +1 -1
- package/dist/factory-assets/connectors/minio/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/minio.js +12 -12
- package/dist/factory-assets/connectors/postgres/adapter.d.ts +6 -1
- package/dist/factory-assets/connectors/postgres/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/postgres.js +12 -12
- package/dist/factory-assets/connectors/s3/driver.d.ts.map +1 -1
- package/dist/factory-assets/connectors/s3.js +12 -12
- package/dist/factory-assets/connectors/sharepoint.js +12 -12
- package/dist/factory-assets/connectors/sqlite/adapter.d.ts +1 -1
- package/dist/factory-assets/connectors/sqlite/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/sqlite.js +12 -12
- package/dist/factory-assets/connectors/static-server/adapter.d.ts +6 -1
- package/dist/factory-assets/connectors/static-server/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/static-server.js +12 -12
- package/dist/factory-assets/connectors/tunnel/adapter.d.ts +1 -1
- package/dist/factory-assets/connectors/tunnel/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/tunnel.js +12 -12
- package/dist/factory-assets/connectors/webdav.js +12 -12
- package/dist/factory-assets/connectors/xstate/adapter.d.ts +1 -1
- package/dist/factory-assets/connectors/xstate/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/xstate-store/adapter.d.ts +7 -0
- package/dist/factory-assets/connectors/xstate-store/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/xstate-store.js +12 -12
- package/dist/factory-assets/connectors/xstate.js +12 -12
- package/dist/flows-JXO3KGC2.js +7 -0
- package/dist/{flows-L3GHQD7F.js.map → flows-JXO3KGC2.js.map} +1 -1
- package/dist/library/index.js +13 -13
- package/dist/library/install.js +9 -9
- package/dist/library/src/config.d.ts.map +1 -1
- package/dist/library/src/index.d.ts +27 -27
- package/dist/library/src/index.d.ts.map +1 -1
- package/dist/library/src/install/index.d.ts +2 -2
- package/dist/library/src/install/index.d.ts.map +1 -1
- package/dist/library/src/install/install-from-manifest.d.ts +0 -21
- package/dist/library/src/install/install-from-manifest.d.ts.map +1 -1
- package/dist/library/src/knowledge/index.d.ts +2 -2
- package/dist/library/src/knowledge/index.d.ts.map +1 -1
- package/dist/library/src/knowledge/knowledge-kind-provider.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/store-paths.d.ts.map +1 -1
- package/dist/library/src/pin-resolver.d.ts.map +1 -1
- package/dist/library/src/preset/apply.d.ts +2 -2
- package/dist/library/src/preset/apply.d.ts.map +1 -1
- package/dist/library/src/preset/index.d.ts +10 -10
- package/dist/library/src/preset/index.d.ts.map +1 -1
- package/dist/library/src/preset/parse.d.ts.map +1 -1
- package/dist/library/src/preset/resolve-item.d.ts +1 -1
- package/dist/library/src/preset/resolve-item.d.ts.map +1 -1
- package/dist/library/src/preset/upgrade.d.ts +1 -1
- package/dist/library/src/preset/upgrade.d.ts.map +1 -1
- package/dist/library/src/remote/index.d.ts +5 -5
- package/dist/library/src/remote/index.d.ts.map +1 -1
- package/dist/library/src/remote/remote-catalog-source.d.ts +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 +1 -1
- package/dist/library/src/remote/rest-catalog-source.d.ts.map +1 -1
- package/dist/library/src/sync/driver.d.ts +1 -1
- package/dist/library/src/sync/driver.d.ts.map +1 -1
- package/dist/library/src/sync/git-driver.d.ts +1 -1
- package/dist/library/src/sync/git-driver.d.ts.map +1 -1
- package/dist/library/src/sync/local-driver.d.ts +1 -1
- package/dist/library/src/sync/local-driver.d.ts.map +1 -1
- package/dist/library/src/workspace-config.d.ts +0 -9
- package/dist/library/src/workspace-config.d.ts.map +1 -1
- package/dist/open-library-SH6NCJM2.js +23 -0
- package/dist/{open-library-3VJY74LQ.js.map → open-library-SH6NCJM2.js.map} +1 -1
- package/dist/paths-YSXGJ7TO.js +15 -0
- package/dist/{paths-CE66L3AL.js.map → paths-YSXGJ7TO.js.map} +1 -1
- package/dist/plugin-registry/src/targets.d.ts +2 -3
- package/dist/plugin-registry/src/targets.d.ts.map +1 -1
- package/dist/{plugin-store-GL5VKYUA.js → plugin-store-2KMDUF7R.js} +11 -14
- package/dist/plugin-store-2KMDUF7R.js.map +1 -0
- package/dist/plugins/index.js +1 -1
- package/dist/plugins/src/asset-kind-registry.d.ts +1 -1
- package/dist/plugins/src/asset-kind-registry.d.ts.map +1 -1
- package/dist/plugins/src/catalog-source.d.ts +5 -0
- package/dist/plugins/src/catalog-source.d.ts.map +1 -1
- package/dist/plugins/src/index.d.ts +5 -5
- package/dist/plugins/src/index.d.ts.map +1 -1
- package/dist/{provider-GAWKFQ3T.js → provider-ZPT443IX.js} +18 -16
- package/dist/provider-ZPT443IX.js.map +1 -0
- package/dist/resolver/index.js +1 -1
- package/dist/resolver/src/preamble.d.ts.map +1 -1
- package/dist/runner/index.js +17 -17
- package/dist/runner/src/builtin-capabilities.d.ts +12 -0
- package/dist/runner/src/builtin-capabilities.d.ts.map +1 -1
- package/dist/runner/src/capability-registry.d.ts.map +1 -1
- package/dist/runner/src/compaction/index.d.ts +0 -4
- package/dist/runner/src/compaction/index.d.ts.map +1 -1
- package/dist/runner/src/compaction/orchestrator.d.ts +23 -2
- package/dist/runner/src/compaction/orchestrator.d.ts.map +1 -1
- package/dist/runner/src/composition/compile.d.ts.map +1 -1
- package/dist/runner/src/composition/index.d.ts +4 -4
- package/dist/runner/src/composition/index.d.ts.map +1 -1
- package/dist/runner/src/composition/resolve.d.ts.map +1 -1
- package/dist/runner/src/composition/types.d.ts +2 -2
- package/dist/runner/src/composition/types.d.ts.map +1 -1
- package/dist/runner/src/config.d.ts +2 -2
- package/dist/runner/src/config.d.ts.map +1 -1
- package/dist/runner/src/define-capability.d.ts +1 -1
- package/dist/runner/src/define-capability.d.ts.map +1 -1
- package/dist/runner/src/external-mcp.d.ts.map +1 -1
- package/dist/runner/src/index.d.ts +9 -9
- package/dist/runner/src/index.d.ts.map +1 -1
- package/dist/runner/src/logging-bootstrap.d.ts +1 -1
- package/dist/runner/src/logging-bootstrap.d.ts.map +1 -1
- package/dist/runner/src/manifest.d.ts.map +1 -1
- package/dist/runner/src/resource-handler.d.ts.map +1 -1
- package/dist/runner/src/resources.d.ts.map +1 -1
- package/dist/runner/src/runner-capabilities.d.ts +25 -0
- package/dist/runner/src/runner-capabilities.d.ts.map +1 -1
- package/dist/runner/src/serve.d.ts.map +1 -1
- package/dist/runner/src/session-builder.d.ts.map +1 -1
- package/dist/sdk/asset-manager.js +12 -12
- package/dist/sdk/bridge.js +4 -4
- package/dist/sdk/core.js +8 -8
- package/dist/sdk/flow.js +9 -9
- package/dist/sdk/index.js +92 -76
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/resolver.js +1 -1
- package/dist/sdk/runner.js +17 -17
- package/dist/sdk/session.js +1 -1
- package/dist/sdk/src/agent-session.d.ts +8 -0
- package/dist/sdk/src/agent-session.d.ts.map +1 -1
- package/dist/sdk/src/factory.d.ts.map +1 -1
- package/dist/sdk/src/index.d.ts +2 -2
- package/dist/sdk/src/index.d.ts.map +1 -1
- package/dist/sdk/store.js +1 -1
- package/dist/sdk/telemetry.js +1 -1
- package/dist/secrets/index.js +2 -2
- package/dist/secrets/src/index.d.ts +8 -8
- package/dist/secrets/src/index.d.ts.map +1 -1
- package/dist/secrets/src/lifecycle.d.ts +1 -2
- package/dist/secrets/src/lifecycle.d.ts.map +1 -1
- package/dist/secrets/src/providers/env.d.ts +1 -1
- package/dist/secrets/src/providers/env.d.ts.map +1 -1
- package/dist/secrets/src/providers/local.d.ts +1 -1
- package/dist/secrets/src/providers/local.d.ts.map +1 -1
- package/dist/secrets/src/router.d.ts +1 -1
- package/dist/secrets/src/router.d.ts.map +1 -1
- package/dist/session/index.js +1 -1
- package/dist/session/src/mentions.d.ts.map +1 -1
- package/dist/{sessions-2IUX4P3P.js → sessions-DICDEONJ.js} +3 -3
- package/dist/{sessions-2IUX4P3P.js.map → sessions-DICDEONJ.js.map} +1 -1
- package/dist/{setup-4YMN2BDU.js → setup-6MQ6QWML.js} +198 -175
- package/dist/setup-6MQ6QWML.js.map +1 -0
- package/dist/skills-BMI6H7HI.js +7 -0
- package/dist/{skills-6HJ3WINN.js.map → skills-BMI6H7HI.js.map} +1 -1
- package/dist/store/index.js +1 -1
- package/dist/store/src/index.d.ts +1 -1
- package/dist/store/src/index.d.ts.map +1 -1
- package/dist/store/src/resource-client.d.ts +1 -1
- package/dist/store/src/resource-client.d.ts.map +1 -1
- package/dist/store/src/vue.d.ts.map +1 -1
- package/dist/store/vue.js.map +1 -1
- package/dist/store-client-LDYF4TK4.js +15 -0
- package/dist/{store-client-BCP5LCAZ.js.map → store-client-LDYF4TK4.js.map} +1 -1
- package/dist/telemetry/index.js +1 -1
- package/dist/telemetry/src/context.d.ts.map +1 -1
- package/dist/telemetry/src/index.d.ts +3 -3
- package/dist/telemetry/src/index.d.ts.map +1 -1
- package/dist/telemetry/src/otel/gen-ai.d.ts +1 -1
- package/dist/telemetry/src/otel/gen-ai.d.ts.map +1 -1
- package/dist/telemetry/src/otel/provider.d.ts.map +1 -1
- package/dist/transport/src/index.d.ts +1 -1
- package/dist/transport/src/index.d.ts.map +1 -1
- package/dist/tui/index.js +251 -218
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/src/index.d.ts +2 -2
- package/dist/tui/src/index.d.ts.map +1 -1
- package/dist/tui/src/repl-renderer.d.ts.map +1 -1
- package/dist/tui/src/repl-session.d.ts +1 -1
- package/dist/tui/src/repl-session.d.ts.map +1 -1
- package/dist/tui/src/repl.d.ts.map +1 -1
- package/dist/types/manifests.js +1 -1
- package/dist/types/src/envelope.d.ts +1 -1
- package/dist/types/src/envelope.d.ts.map +1 -1
- package/dist/types/src/manifests/agent.d.ts.map +1 -1
- package/dist/types/src/manifests/connector.d.ts.map +1 -1
- package/dist/types/src/manifests/contract.d.ts.map +1 -1
- package/dist/types/src/manifests/index.d.ts +16 -16
- package/dist/types/src/manifests/index.d.ts.map +1 -1
- package/dist/types/src/manifests/mcp-server.d.ts.map +1 -1
- package/dist/types/src/manifests/mount.d.ts.map +1 -1
- package/dist/types/src/manifests/persona.d.ts.map +1 -1
- package/dist/types/src/manifests/preset.d.ts +0 -18
- package/dist/types/src/manifests/preset.d.ts.map +1 -1
- package/dist/types/src/manifests/prompt.d.ts.map +1 -1
- package/dist/types/src/manifests/ruleset.d.ts.map +1 -1
- package/dist/types/src/manifests/skill.d.ts.map +1 -1
- package/dist/validator-VF5M764T.js +5 -0
- package/dist/{validator-47SQUW3J.js.map → validator-VF5M764T.js.map} +1 -1
- package/dist/workspace-plugin/adapters/mcp.js +6 -6
- package/dist/workspace-plugin/adapters/omp.js +76 -69
- package/dist/workspace-plugin/adapters/omp.js.map +1 -1
- package/dist/workspace-plugin/index.js +2 -2
- package/dist/workspace-plugin/src/adapters/omp.d.ts.map +1 -1
- package/dist/workspace-plugin/src/tools/connectors.d.ts.map +1 -1
- package/dist/workspace-plugin/src/tools/flows.d.ts.map +1 -1
- package/dist/workspace-plugin/src/tools/sessions.d.ts.map +1 -1
- package/dist/workspace-plugin/src/tools/skills.d.ts.map +1 -1
- package/dist/workspace-plugin/src/tools/validator.d.ts.map +1 -1
- package/package.json +1 -2
- package/dist/asset-feeds-2V7LZIG7.js.map +0 -1
- package/dist/chunk-24UIWON4.js.map +0 -1
- package/dist/chunk-2FS4FPZ7.js.map +0 -1
- package/dist/chunk-3QQSSRYN.js.map +0 -1
- package/dist/chunk-5YO3H5Q4.js.map +0 -1
- package/dist/chunk-7GWYRVAX.js.map +0 -1
- package/dist/chunk-AKSR434V.js.map +0 -1
- package/dist/chunk-DVMSGGCM.js.map +0 -1
- package/dist/chunk-FQHHCICT.js.map +0 -1
- package/dist/chunk-IEJ5IOC3.js.map +0 -1
- package/dist/chunk-IERB3UXJ.js.map +0 -1
- package/dist/chunk-JIQLLZ65.js.map +0 -1
- package/dist/chunk-M4KLIEIJ.js.map +0 -1
- package/dist/chunk-MIH45XPY.js.map +0 -1
- package/dist/chunk-MLJKA3S6.js.map +0 -1
- package/dist/chunk-OTFG5Q7H.js.map +0 -1
- package/dist/chunk-PAYAFNXG.js.map +0 -1
- package/dist/chunk-QDJKE557.js.map +0 -1
- package/dist/chunk-SF4GWVR5.js.map +0 -1
- package/dist/chunk-TV72ZJYN.js.map +0 -1
- package/dist/chunk-UIZKIUC6.js.map +0 -1
- package/dist/chunk-W7W66MDZ.js.map +0 -1
- package/dist/chunk-Z3XXKR5E.js.map +0 -1
- package/dist/chunk-ZONCCO5N.js.map +0 -1
- package/dist/ensure-sources-BNSRO5G7.js +0 -74
- package/dist/ensure-sources-BNSRO5G7.js.map +0 -1
- package/dist/flows-L3GHQD7F.js +0 -7
- package/dist/open-library-3VJY74LQ.js +0 -23
- package/dist/paths-CE66L3AL.js +0 -15
- package/dist/plugin-store-GL5VKYUA.js.map +0 -1
- package/dist/provider-GAWKFQ3T.js.map +0 -1
- package/dist/setup-4YMN2BDU.js.map +0 -1
- package/dist/skills-6HJ3WINN.js +0 -7
- package/dist/store-client-BCP5LCAZ.js +0 -15
- package/dist/validator-47SQUW3J.js +0 -5
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { readManifestSourceConfig, discoverAssetsInTree } from './chunk-
|
|
2
|
-
import { installFromManifest } from './chunk-
|
|
3
|
-
import { parseAssetRef } from './chunk-
|
|
4
|
-
import { PresetManifestSchema } from './chunk-
|
|
1
|
+
import { readManifestSourceConfig, discoverAssetsInTree } from './chunk-7MANNJ3U.js';
|
|
2
|
+
import { installFromManifest } from './chunk-WDMH4UMD.js';
|
|
3
|
+
import { parseAssetRef } from './chunk-LFDZYCEO.js';
|
|
4
|
+
import { PresetManifestSchema } from './chunk-HRKB2ATT.js';
|
|
5
5
|
import { __export } from './chunk-NSBPE2FW.js';
|
|
6
|
-
import * as
|
|
7
|
-
import * as fs11 from 'fs';
|
|
8
|
-
import * as path4 from 'path';
|
|
9
|
-
import { eq, like, and } from 'drizzle-orm';
|
|
10
|
-
import * as z2 from 'zod';
|
|
6
|
+
import * as fs10 from 'fs';
|
|
11
7
|
import * as os from 'os';
|
|
8
|
+
import * as path5 from 'path';
|
|
12
9
|
import { stringify, parse, parseDocument } from 'yaml';
|
|
10
|
+
import * as z3 from 'zod';
|
|
11
|
+
import * as crypto from 'crypto';
|
|
12
|
+
import { eq, like, and } from 'drizzle-orm';
|
|
13
13
|
import { createClient } from '@libsql/client';
|
|
14
14
|
import { drizzle } from 'drizzle-orm/libsql';
|
|
15
15
|
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
|
@@ -74,46 +74,48 @@ var InstanceHasConsumersError = class extends LibraryError {
|
|
|
74
74
|
this.name = "InstanceHasConsumersError";
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
|
+
|
|
78
|
+
// library/src/config.ts
|
|
77
79
|
var LOCAL_CATALOG_URL = "local";
|
|
78
80
|
function isLocalCatalogUrl(url) {
|
|
79
81
|
return typeof url === "string" && url.trim().toLowerCase() === LOCAL_CATALOG_URL;
|
|
80
82
|
}
|
|
81
|
-
var CatalogConfigSchema =
|
|
83
|
+
var CatalogConfigSchema = z3.object({
|
|
82
84
|
/** Catalog base URL, or `"local"` (case-insensitive) to disable the remote catalog. Default: https://skaile.store */
|
|
83
|
-
url:
|
|
84
|
-
|
|
85
|
+
url: z3.union([
|
|
86
|
+
z3.string().url(),
|
|
85
87
|
// Accept any casing/whitespace variant of "local", normalise to canonical.
|
|
86
|
-
|
|
88
|
+
z3.string().refine((s) => s.trim().toLowerCase() === LOCAL_CATALOG_URL, {
|
|
87
89
|
message: `Expected a valid URL or "${LOCAL_CATALOG_URL}"`
|
|
88
90
|
}).transform(() => LOCAL_CATALOG_URL)
|
|
89
91
|
]).optional(),
|
|
90
92
|
/** Cache TTL in seconds. 0 = air-gapped (manual `skaile update` only). */
|
|
91
|
-
cache_ttl:
|
|
93
|
+
cache_ttl: z3.number().int().min(0).optional(),
|
|
92
94
|
/**
|
|
93
95
|
* Wire framing to use when talking to a remote catalog.
|
|
94
96
|
* `"trpc"` — skaile.store tRPC backend (default for `https://skaile.store`).
|
|
95
97
|
* `"rest"` — forge-store REST backend (default for any other URL).
|
|
96
98
|
*/
|
|
97
|
-
framing:
|
|
99
|
+
framing: z3.enum(["trpc", "rest"]).optional()
|
|
98
100
|
});
|
|
99
|
-
var LibraryConfigSchema =
|
|
101
|
+
var LibraryConfigSchema = z3.object({
|
|
100
102
|
/** Library storage directory. Default: ~/.skaile/libraries */
|
|
101
|
-
dir:
|
|
103
|
+
dir: z3.string().optional(),
|
|
102
104
|
/** Default pin policy for new instances/assignments. */
|
|
103
|
-
pin_policy:
|
|
105
|
+
pin_policy: z3.enum(PIN_POLICIES).optional()
|
|
104
106
|
});
|
|
105
|
-
var SkaileConfigSchema =
|
|
107
|
+
var SkaileConfigSchema = z3.object({
|
|
106
108
|
catalog: CatalogConfigSchema.optional(),
|
|
107
109
|
library: LibraryConfigSchema.optional()
|
|
108
110
|
});
|
|
109
111
|
function skaileHomeDir() {
|
|
110
|
-
return
|
|
112
|
+
return path5.join(os.homedir(), ".skaile");
|
|
111
113
|
}
|
|
112
114
|
function defaultLibraryDir() {
|
|
113
|
-
return
|
|
115
|
+
return path5.join(skaileHomeDir(), "libraries");
|
|
114
116
|
}
|
|
115
117
|
function defaultIndexPath() {
|
|
116
|
-
return
|
|
118
|
+
return path5.join(skaileHomeDir(), "index.db");
|
|
117
119
|
}
|
|
118
120
|
function resolveLibraryDir() {
|
|
119
121
|
if (process.env.SKAILE_LIBRARIES_DIR) return process.env.SKAILE_LIBRARIES_DIR;
|
|
@@ -131,15 +133,15 @@ function getConfigDefaults() {
|
|
|
131
133
|
}
|
|
132
134
|
var CONFIG_FILENAME = "config.yaml";
|
|
133
135
|
function userConfigPath() {
|
|
134
|
-
return
|
|
136
|
+
return path5.join(os.homedir(), ".skaile", CONFIG_FILENAME);
|
|
135
137
|
}
|
|
136
138
|
function projectConfigPath(projectDir) {
|
|
137
|
-
return
|
|
139
|
+
return path5.join(projectDir, ".skaile", CONFIG_FILENAME);
|
|
138
140
|
}
|
|
139
141
|
function loadConfigFile(filePath) {
|
|
140
|
-
if (!
|
|
142
|
+
if (!fs10.existsSync(filePath)) return null;
|
|
141
143
|
try {
|
|
142
|
-
const content =
|
|
144
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
143
145
|
const raw = parse(content);
|
|
144
146
|
if (!raw || typeof raw !== "object") return null;
|
|
145
147
|
return SkaileConfigSchema.partial().parse(raw);
|
|
@@ -187,12 +189,76 @@ function resolveConfig(opts) {
|
|
|
187
189
|
return resolved;
|
|
188
190
|
}
|
|
189
191
|
function saveConfig(filePath, config) {
|
|
190
|
-
const dir =
|
|
191
|
-
if (!
|
|
192
|
-
|
|
192
|
+
const dir = path5.dirname(filePath);
|
|
193
|
+
if (!fs10.existsSync(dir)) {
|
|
194
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
195
|
+
}
|
|
196
|
+
fs10.writeFileSync(filePath, stringify(config, { indent: 2 }), "utf-8");
|
|
197
|
+
}
|
|
198
|
+
var KnowledgeManifestSchema = z3.looseObject({
|
|
199
|
+
name: z3.string().optional(),
|
|
200
|
+
description: z3.string().optional(),
|
|
201
|
+
version: z3.string().optional(),
|
|
202
|
+
files: z3.array(z3.string()).optional(),
|
|
203
|
+
keywords: z3.array(z3.string()).optional(),
|
|
204
|
+
license: z3.string().optional()
|
|
205
|
+
});
|
|
206
|
+
function validateKnowledge(data) {
|
|
207
|
+
const result = KnowledgeManifestSchema.safeParse(data);
|
|
208
|
+
if (result.success) {
|
|
209
|
+
return { ok: true, data: result.data };
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
errors: result.error.issues.map((issue) => ({
|
|
214
|
+
path: issue.path.join("."),
|
|
215
|
+
message: issue.message
|
|
216
|
+
}))
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// library/src/knowledge/knowledge-kind-provider.ts
|
|
221
|
+
function collectDirRecursive(baseDir, relativeDir) {
|
|
222
|
+
const full = path5.join(baseDir, relativeDir);
|
|
223
|
+
if (!fs10.existsSync(full) || !fs10.statSync(full).isDirectory()) return [];
|
|
224
|
+
const results = [];
|
|
225
|
+
for (const entry of fs10.readdirSync(full, { withFileTypes: true })) {
|
|
226
|
+
const rel = path5.posix.join(relativeDir, entry.name);
|
|
227
|
+
if (entry.isDirectory()) {
|
|
228
|
+
results.push(...collectDirRecursive(baseDir, rel));
|
|
229
|
+
} else if (entry.isFile()) {
|
|
230
|
+
results.push(rel);
|
|
231
|
+
}
|
|
193
232
|
}
|
|
194
|
-
|
|
233
|
+
return results;
|
|
195
234
|
}
|
|
235
|
+
var knowledgeKindProvider = {
|
|
236
|
+
kind: "knowledge",
|
|
237
|
+
displayName: "Knowledge",
|
|
238
|
+
providerVersion: "1.0.0",
|
|
239
|
+
discoveryPatterns: ["**/knowledge.yaml", "**/knowledge/index.yaml"],
|
|
240
|
+
matchesPath(relativePath) {
|
|
241
|
+
const parts = relativePath.split("/");
|
|
242
|
+
const filename = parts[parts.length - 1] ?? "";
|
|
243
|
+
if (filename === "knowledge.yaml") return true;
|
|
244
|
+
if (filename === "index.yaml" && parts.length >= 2 && parts[parts.length - 2] === "knowledge") {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
},
|
|
249
|
+
validateManifest(data) {
|
|
250
|
+
return validateKnowledge(data);
|
|
251
|
+
},
|
|
252
|
+
defaultFileFilter(manifestPath) {
|
|
253
|
+
const dir = path5.dirname(manifestPath);
|
|
254
|
+
const manifestName = path5.basename(manifestPath);
|
|
255
|
+
const files = [manifestName];
|
|
256
|
+
files.push(...collectDirRecursive(dir, "documents"));
|
|
257
|
+
files.sort();
|
|
258
|
+
return { assetRoot: dir, files };
|
|
259
|
+
}
|
|
260
|
+
// Knowledge has no cross-asset dependency edges — omit extractRequires
|
|
261
|
+
};
|
|
196
262
|
|
|
197
263
|
// library/src/local/schema.ts
|
|
198
264
|
var schema_exports = {};
|
|
@@ -265,23 +331,23 @@ var assignments = sqliteTable("assignments", {
|
|
|
265
331
|
});
|
|
266
332
|
var SKAILE_HOME_ENV = "SKAILE_HOME";
|
|
267
333
|
function getSidecarRoot() {
|
|
268
|
-
const home = process.env[SKAILE_HOME_ENV] ??
|
|
269
|
-
return
|
|
334
|
+
const home = process.env[SKAILE_HOME_ENV] ?? path5.join(os.homedir(), ".skaile");
|
|
335
|
+
return path5.join(home, "sources");
|
|
270
336
|
}
|
|
271
337
|
function resolveSidecarPaths(slug) {
|
|
272
338
|
const rootDir = getSidecarRoot();
|
|
273
|
-
const sidecarDir =
|
|
339
|
+
const sidecarDir = path5.join(rootDir, slug);
|
|
274
340
|
return {
|
|
275
341
|
rootDir,
|
|
276
342
|
sidecarDir,
|
|
277
|
-
manifestFile:
|
|
278
|
-
readmeFile:
|
|
279
|
-
lockFile:
|
|
343
|
+
manifestFile: path5.join(sidecarDir, "skaile.manifest.yaml"),
|
|
344
|
+
readmeFile: path5.join(sidecarDir, "README.md"),
|
|
345
|
+
lockFile: path5.join(sidecarDir, "skaile.manifest.lock.json")
|
|
280
346
|
};
|
|
281
347
|
}
|
|
282
348
|
function deriveSlug(sourceConfig, sourcePath, existingSlugs) {
|
|
283
349
|
const fromConfig = sourceConfig && typeof sourceConfig.name === "string" ? sourceConfig.name : void 0;
|
|
284
|
-
const base = fromConfig && fromConfig.length > 0 ? slugify(fromConfig) : slugify(
|
|
350
|
+
const base = fromConfig && fromConfig.length > 0 ? slugify(fromConfig) : slugify(path5.basename(sourcePath || "")) || "unnamed-source";
|
|
285
351
|
const taken = new Set(existingSlugs);
|
|
286
352
|
if (!taken.has(base)) return base;
|
|
287
353
|
for (let i = 2; i < 1e3; i++) {
|
|
@@ -292,9 +358,9 @@ function deriveSlug(sourceConfig, sourcePath, existingSlugs) {
|
|
|
292
358
|
}
|
|
293
359
|
function listSidecarSlugsOnDisk() {
|
|
294
360
|
const root = getSidecarRoot();
|
|
295
|
-
if (!
|
|
361
|
+
if (!fs10.existsSync(root)) return [];
|
|
296
362
|
const out = [];
|
|
297
|
-
for (const entry of
|
|
363
|
+
for (const entry of fs10.readdirSync(root, { withFileTypes: true })) {
|
|
298
364
|
if (!entry.isDirectory()) continue;
|
|
299
365
|
if (entry.name.startsWith(".")) continue;
|
|
300
366
|
out.push(entry.name);
|
|
@@ -307,40 +373,37 @@ function slugify(input) {
|
|
|
307
373
|
|
|
308
374
|
// library/src/local/store-paths.ts
|
|
309
375
|
function getStoreRoot() {
|
|
310
|
-
const home = process.env.SKAILE_HOME ??
|
|
311
|
-
return
|
|
376
|
+
const home = process.env.SKAILE_HOME ?? path5.join(os.homedir(), ".skaile");
|
|
377
|
+
return path5.join(home, "store");
|
|
312
378
|
}
|
|
313
379
|
function getStoreManifestsDir() {
|
|
314
|
-
return
|
|
380
|
+
return path5.join(getStoreRoot(), "manifests");
|
|
315
381
|
}
|
|
316
382
|
function migratedMarkerPath() {
|
|
317
|
-
return
|
|
383
|
+
return path5.join(getStoreRoot(), ".migrated");
|
|
318
384
|
}
|
|
319
|
-
function
|
|
320
|
-
const storeRoot = getStoreRoot();
|
|
321
|
-
const marker = migratedMarkerPath();
|
|
322
|
-
if (fs11.existsSync(marker)) return { migrated: 0, storeRoot };
|
|
323
|
-
const sidecarRoot = getSidecarRoot();
|
|
324
|
-
const manifestsDir = getStoreManifestsDir();
|
|
385
|
+
function collectSidecarMigrationCandidates(sidecarRoot) {
|
|
325
386
|
const candidates = [];
|
|
326
|
-
if (
|
|
327
|
-
for (const entry of
|
|
387
|
+
if (fs10.existsSync(sidecarRoot)) {
|
|
388
|
+
for (const entry of fs10.readdirSync(sidecarRoot, { withFileTypes: true })) {
|
|
328
389
|
if (!entry.isDirectory()) continue;
|
|
329
390
|
if (entry.name.startsWith(".")) continue;
|
|
330
|
-
const sidecarDir =
|
|
331
|
-
const current =
|
|
332
|
-
const legacy =
|
|
333
|
-
const src =
|
|
391
|
+
const sidecarDir = path5.join(sidecarRoot, entry.name);
|
|
392
|
+
const current = path5.join(sidecarDir, "skaile.manifest.yaml");
|
|
393
|
+
const legacy = path5.join(sidecarDir, ".skaile-source.yaml");
|
|
394
|
+
const src = fs10.existsSync(current) ? current : fs10.existsSync(legacy) ? legacy : void 0;
|
|
334
395
|
if (src) candidates.push({ src, slug: entry.name });
|
|
335
396
|
}
|
|
336
397
|
}
|
|
337
|
-
|
|
398
|
+
return candidates;
|
|
399
|
+
}
|
|
400
|
+
function moveSidecarManifests(candidates, manifestsDir) {
|
|
338
401
|
let migrated = 0;
|
|
339
402
|
for (const { src, slug } of candidates) {
|
|
340
|
-
const dest =
|
|
341
|
-
if (
|
|
403
|
+
const dest = path5.join(manifestsDir, `${slug}.yaml`);
|
|
404
|
+
if (fs10.existsSync(dest)) continue;
|
|
342
405
|
try {
|
|
343
|
-
|
|
406
|
+
fs10.renameSync(src, dest);
|
|
344
407
|
migrated++;
|
|
345
408
|
} catch (err) {
|
|
346
409
|
process.stderr.write(
|
|
@@ -349,18 +412,31 @@ function migrateSidecarsToStoreIfNeeded() {
|
|
|
349
412
|
);
|
|
350
413
|
}
|
|
351
414
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
|
|
415
|
+
return migrated;
|
|
416
|
+
}
|
|
417
|
+
function initStoreGitRepo(storeRoot) {
|
|
418
|
+
if (fs10.existsSync(path5.join(storeRoot, ".git"))) return;
|
|
419
|
+
try {
|
|
420
|
+
spawnSync("git", ["init", "--quiet"], { cwd: storeRoot, stdio: "ignore" });
|
|
421
|
+
fs10.writeFileSync(
|
|
422
|
+
path5.join(storeRoot, ".gitignore"),
|
|
423
|
+
"# Remote-store cache \u2014 local to this machine.\nremote-cache/\n",
|
|
424
|
+
"utf8"
|
|
425
|
+
);
|
|
426
|
+
} catch {
|
|
362
427
|
}
|
|
363
|
-
|
|
428
|
+
}
|
|
429
|
+
function migrateSidecarsToStoreIfNeeded() {
|
|
430
|
+
const storeRoot = getStoreRoot();
|
|
431
|
+
const marker = migratedMarkerPath();
|
|
432
|
+
if (fs10.existsSync(marker)) return { migrated: 0, storeRoot };
|
|
433
|
+
const sidecarRoot = getSidecarRoot();
|
|
434
|
+
const manifestsDir = getStoreManifestsDir();
|
|
435
|
+
const candidates = collectSidecarMigrationCandidates(sidecarRoot);
|
|
436
|
+
fs10.mkdirSync(manifestsDir, { recursive: true });
|
|
437
|
+
const migrated = moveSidecarManifests(candidates, manifestsDir);
|
|
438
|
+
initStoreGitRepo(storeRoot);
|
|
439
|
+
fs10.writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString(), "utf8");
|
|
364
440
|
return { migrated, storeRoot };
|
|
365
441
|
}
|
|
366
442
|
|
|
@@ -441,7 +517,7 @@ async function librariesPresent(client) {
|
|
|
441
517
|
return rs.rows.length > 0;
|
|
442
518
|
}
|
|
443
519
|
function slugify2(p) {
|
|
444
|
-
const base =
|
|
520
|
+
const base = path5.basename(p) || "lib";
|
|
445
521
|
return base.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "") || "lib";
|
|
446
522
|
}
|
|
447
523
|
async function runLibrariesMigration(client) {
|
|
@@ -527,23 +603,23 @@ async function runAssignmentsMigration(client) {
|
|
|
527
603
|
]);
|
|
528
604
|
}
|
|
529
605
|
function assertNoLegacyLibDb(newDbPath) {
|
|
530
|
-
if (
|
|
531
|
-
const parent =
|
|
532
|
-
const skaileHome =
|
|
533
|
-
const defaultIndexPath2 =
|
|
606
|
+
if (fs10.existsSync(newDbPath)) return;
|
|
607
|
+
const parent = path5.dirname(newDbPath);
|
|
608
|
+
const skaileHome = path5.join(os.homedir(), ".skaile");
|
|
609
|
+
const defaultIndexPath2 = path5.join(skaileHome, "index.db");
|
|
534
610
|
const candidates = [];
|
|
535
|
-
if (
|
|
536
|
-
candidates.push(
|
|
611
|
+
if (path5.basename(newDbPath) === "index.db") {
|
|
612
|
+
candidates.push(path5.join(parent, "lib.db"));
|
|
537
613
|
}
|
|
538
614
|
if (newDbPath === defaultIndexPath2) {
|
|
539
|
-
candidates.push(
|
|
540
|
-
candidates.push(
|
|
615
|
+
candidates.push(path5.join(skaileHome, "libraries", "lib.db"));
|
|
616
|
+
candidates.push(path5.join(skaileHome, "library", "lib.db"));
|
|
541
617
|
}
|
|
542
|
-
if (
|
|
543
|
-
candidates.push(
|
|
618
|
+
if (path5.basename(parent) === "libraries") {
|
|
619
|
+
candidates.push(path5.join(path5.dirname(parent), "library", "lib.db"));
|
|
544
620
|
}
|
|
545
621
|
for (const legacy of candidates) {
|
|
546
|
-
if (!
|
|
622
|
+
if (!fs10.existsSync(legacy)) continue;
|
|
547
623
|
throw new Error(
|
|
548
624
|
`Found legacy index database at ${legacy}. The lib.db \u2192 index.db relocation ran for one release and has been removed. Move the file manually to ${newDbPath} (and any -wal / -shm siblings), then retry.`
|
|
549
625
|
);
|
|
@@ -683,26 +759,26 @@ var LocalIndex = class {
|
|
|
683
759
|
let dbPath;
|
|
684
760
|
if (typeof optionsOrDir === "string") {
|
|
685
761
|
dir = optionsOrDir;
|
|
686
|
-
dbPath =
|
|
762
|
+
dbPath = path5.join(dir, "index.db");
|
|
687
763
|
} else if (optionsOrDir?.indexPath) {
|
|
688
764
|
dbPath = optionsOrDir.indexPath;
|
|
689
765
|
dir = optionsOrDir.libraryDir ?? resolveLibraryDir();
|
|
690
766
|
this._kindRegistry = optionsOrDir.kindRegistry;
|
|
691
767
|
} else if (optionsOrDir?.libraryDir) {
|
|
692
768
|
dir = optionsOrDir.libraryDir;
|
|
693
|
-
dbPath =
|
|
769
|
+
dbPath = path5.join(dir, "index.db");
|
|
694
770
|
this._kindRegistry = optionsOrDir.kindRegistry;
|
|
695
771
|
} else {
|
|
696
772
|
dir = resolveLibraryDir();
|
|
697
773
|
dbPath = resolveIndexPath();
|
|
698
774
|
this._kindRegistry = optionsOrDir?.kindRegistry;
|
|
699
775
|
}
|
|
700
|
-
if (!
|
|
701
|
-
|
|
776
|
+
if (!fs10.existsSync(dir)) {
|
|
777
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
702
778
|
}
|
|
703
|
-
const dbParent =
|
|
704
|
-
if (!
|
|
705
|
-
|
|
779
|
+
const dbParent = path5.dirname(dbPath);
|
|
780
|
+
if (!fs10.existsSync(dbParent)) {
|
|
781
|
+
fs10.mkdirSync(dbParent, { recursive: true });
|
|
706
782
|
}
|
|
707
783
|
this.libraryDir = dir;
|
|
708
784
|
this.dbPath = dbPath;
|
|
@@ -900,7 +976,7 @@ var LocalIndex = class {
|
|
|
900
976
|
* @returns The created Instance plus its on-disk install path.
|
|
901
977
|
*/
|
|
902
978
|
async install(manifest, opts) {
|
|
903
|
-
const libraryRoot = opts?.libraryRoot ??
|
|
979
|
+
const libraryRoot = opts?.libraryRoot ?? path5.join(this.libraryDir, "installed");
|
|
904
980
|
const { installPath } = await installFromManifest({
|
|
905
981
|
manifest,
|
|
906
982
|
libraryRoot,
|
|
@@ -1329,8 +1405,8 @@ var LibraryManager = class {
|
|
|
1329
1405
|
const lib = await this.requireLibrary(nameOrId);
|
|
1330
1406
|
const db = await this.lib.dbHandle();
|
|
1331
1407
|
await db.delete(libraries).where(eq(libraries.id, lib.id)).run();
|
|
1332
|
-
if (opts.purge &&
|
|
1333
|
-
|
|
1408
|
+
if (opts.purge && fs10.existsSync(lib.path)) {
|
|
1409
|
+
fs10.rmSync(lib.path, { recursive: true, force: true });
|
|
1334
1410
|
}
|
|
1335
1411
|
}
|
|
1336
1412
|
/**
|
|
@@ -1346,8 +1422,8 @@ var LibraryManager = class {
|
|
|
1346
1422
|
(l) => l.isDefault && (l.ownership === "owner" || l.ownership === "contributor")
|
|
1347
1423
|
);
|
|
1348
1424
|
if (writableDefault) return { library: writableDefault, created: false };
|
|
1349
|
-
const personalPath =
|
|
1350
|
-
|
|
1425
|
+
const personalPath = path5.join(librariesDir, "personal");
|
|
1426
|
+
fs10.mkdirSync(personalPath, { recursive: true });
|
|
1351
1427
|
const lib = await this.addLibrary({
|
|
1352
1428
|
name: "personal",
|
|
1353
1429
|
path: personalPath,
|
|
@@ -1365,6 +1441,121 @@ var LibraryManager = class {
|
|
|
1365
1441
|
return { library: lib, created: true };
|
|
1366
1442
|
}
|
|
1367
1443
|
};
|
|
1444
|
+
|
|
1445
|
+
// library/src/local/local-catalog-source.ts
|
|
1446
|
+
var LocalCatalogSource = class {
|
|
1447
|
+
constructor(library, sourceId, rootPath, registry, options) {
|
|
1448
|
+
this.library = library;
|
|
1449
|
+
this.sourceId = sourceId;
|
|
1450
|
+
this.rootPath = rootPath;
|
|
1451
|
+
this.registry = registry;
|
|
1452
|
+
this.id = `local:${sourceId}`;
|
|
1453
|
+
this.sidecarPath = options?.sidecarPath;
|
|
1454
|
+
}
|
|
1455
|
+
library;
|
|
1456
|
+
sourceId;
|
|
1457
|
+
rootPath;
|
|
1458
|
+
registry;
|
|
1459
|
+
id;
|
|
1460
|
+
sidecarPath;
|
|
1461
|
+
async resolve(ref) {
|
|
1462
|
+
const def = await this.library.getAssetDef(ref);
|
|
1463
|
+
if (!def || def.libraryId !== this.sourceId) return null;
|
|
1464
|
+
return defToCatalogAsset(def);
|
|
1465
|
+
}
|
|
1466
|
+
async listAssets(filter) {
|
|
1467
|
+
const libraryFilter = { libraryId: this.sourceId };
|
|
1468
|
+
if (filter?.kind) libraryFilter.kind = filter.kind;
|
|
1469
|
+
if (filter?.publisher) libraryFilter.publisher = filter.publisher;
|
|
1470
|
+
if (filter?.prefix) libraryFilter.prefix = filter.prefix;
|
|
1471
|
+
const defs = await this.library.listAssetDefs(libraryFilter);
|
|
1472
|
+
return defs.map(defToCatalogAsset);
|
|
1473
|
+
}
|
|
1474
|
+
async listVersions(ref) {
|
|
1475
|
+
const parsed = parseAssetRef(ref);
|
|
1476
|
+
const defs = await this.library.listAssetDefs({
|
|
1477
|
+
libraryId: this.sourceId,
|
|
1478
|
+
kind: parsed.kind
|
|
1479
|
+
});
|
|
1480
|
+
const wantPublisher = parsed.publisher?.replace(/^@/, "");
|
|
1481
|
+
return defs.filter(
|
|
1482
|
+
(d) => d.name === parsed.name && (wantPublisher === void 0 || d.publisher.replace(/^@/, "") === wantPublisher)
|
|
1483
|
+
).map((d) => d.version);
|
|
1484
|
+
}
|
|
1485
|
+
async fetchTarball(_ref, _sha256) {
|
|
1486
|
+
throw new Error(
|
|
1487
|
+
"LocalCatalogSource does not support tarball delivery. Assets are available on disk."
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
async listSources() {
|
|
1491
|
+
return [{ id: this.sourceId, type: "local", url: this.rootPath }];
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Sync: discover assets in the source directory and cache them in the library.
|
|
1495
|
+
*
|
|
1496
|
+
* Runs the discovery pipeline (glob -> parse -> filter -> hash -> requires)
|
|
1497
|
+
* and upserts each discovered asset into the LocalIndex cache.
|
|
1498
|
+
*
|
|
1499
|
+
* @param syncOpts - When `includeDev` is set, assets under the source
|
|
1500
|
+
* config's `dev_paths` (e.g. `ai-assets-dev/`) are discovered and cached
|
|
1501
|
+
* too. Defaults to excluding them (a normal run).
|
|
1502
|
+
* @returns Discovery statistics and any errors encountered.
|
|
1503
|
+
*/
|
|
1504
|
+
async sync(syncOpts) {
|
|
1505
|
+
const opts = {
|
|
1506
|
+
registry: this.registry,
|
|
1507
|
+
sidecarPath: this.sidecarPath,
|
|
1508
|
+
includeDev: syncOpts?.includeDev ?? false
|
|
1509
|
+
};
|
|
1510
|
+
if (!this.sidecarPath) {
|
|
1511
|
+
opts.sourceConfig = readManifestSourceConfig(this.rootPath);
|
|
1512
|
+
}
|
|
1513
|
+
const result = discoverAssetsInTree(this.rootPath, opts);
|
|
1514
|
+
let assetsUpdated = 0;
|
|
1515
|
+
const errors = [];
|
|
1516
|
+
for (const asset of result.assets) {
|
|
1517
|
+
if (asset.kind === "preset") continue;
|
|
1518
|
+
try {
|
|
1519
|
+
await this.library.upsertAssetDef({
|
|
1520
|
+
id: asset.ref,
|
|
1521
|
+
kind: asset.kind,
|
|
1522
|
+
publisher: asset.publisher,
|
|
1523
|
+
name: asset.name,
|
|
1524
|
+
version: asset.version,
|
|
1525
|
+
sha256: asset.sha256,
|
|
1526
|
+
description: typeof asset.manifest.description === "string" ? asset.manifest.description : void 0,
|
|
1527
|
+
license: typeof asset.manifest.license === "string" ? asset.manifest.license : void 0,
|
|
1528
|
+
category: typeof asset.manifest.category === "string" ? asset.manifest.category : void 0,
|
|
1529
|
+
manifest: asset.manifest,
|
|
1530
|
+
libraryId: this.sourceId,
|
|
1531
|
+
cachedAt: /* @__PURE__ */ new Date(),
|
|
1532
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1533
|
+
});
|
|
1534
|
+
assetsUpdated++;
|
|
1535
|
+
} catch (err) {
|
|
1536
|
+
errors.push(`${asset.ref}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
for (const err of result.errors) {
|
|
1540
|
+
errors.push(`${err.path}: ${err.error}`);
|
|
1541
|
+
}
|
|
1542
|
+
return { assetsFound: result.assets.length, assetsUpdated, errors };
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
function defToCatalogAsset(def) {
|
|
1546
|
+
return {
|
|
1547
|
+
id: def.id,
|
|
1548
|
+
kind: def.kind,
|
|
1549
|
+
publisher: def.publisher,
|
|
1550
|
+
name: def.name,
|
|
1551
|
+
version: def.version,
|
|
1552
|
+
sha256: def.sha256,
|
|
1553
|
+
description: def.description,
|
|
1554
|
+
license: def.license,
|
|
1555
|
+
category: def.category,
|
|
1556
|
+
manifest: def.manifest
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1368
1559
|
var FALLBACK_USER_NAME = "Skaile CLI";
|
|
1369
1560
|
var FALLBACK_USER_EMAIL = "skaile-cli@local";
|
|
1370
1561
|
var ROOT_README = `# Skaile Sidecar Manifests
|
|
@@ -1409,21 +1600,21 @@ var SidecarGit = class {
|
|
|
1409
1600
|
* @docLink packages/library/concepts#sidecar-git
|
|
1410
1601
|
*/
|
|
1411
1602
|
async ensureInitialized() {
|
|
1412
|
-
if (!
|
|
1413
|
-
|
|
1603
|
+
if (!fs10.existsSync(this.rootDir)) {
|
|
1604
|
+
fs10.mkdirSync(this.rootDir, { recursive: true });
|
|
1414
1605
|
}
|
|
1415
|
-
const gitDir =
|
|
1416
|
-
const freshRepo = !
|
|
1606
|
+
const gitDir = path5.join(this.rootDir, ".git");
|
|
1607
|
+
const freshRepo = !fs10.existsSync(gitDir);
|
|
1417
1608
|
if (freshRepo) {
|
|
1418
1609
|
const init = this.spawn(["init", "-b", "main"]);
|
|
1419
1610
|
if (init.exitCode !== 0) {
|
|
1420
1611
|
throw new Error(`git init failed: ${init.stderr || init.stdout}`);
|
|
1421
1612
|
}
|
|
1422
1613
|
}
|
|
1423
|
-
const attrsPath =
|
|
1424
|
-
if (!
|
|
1425
|
-
const readmePath =
|
|
1426
|
-
if (!
|
|
1614
|
+
const attrsPath = path5.join(this.rootDir, ".gitattributes");
|
|
1615
|
+
if (!fs10.existsSync(attrsPath)) fs10.writeFileSync(attrsPath, GITATTRIBUTES, "utf-8");
|
|
1616
|
+
const readmePath = path5.join(this.rootDir, "README.md");
|
|
1617
|
+
if (!fs10.existsSync(readmePath)) fs10.writeFileSync(readmePath, ROOT_README, "utf-8");
|
|
1427
1618
|
if (!this.hasGlobalGitConfig("user.name")) {
|
|
1428
1619
|
this.spawn(["config", "user.name", FALLBACK_USER_NAME]);
|
|
1429
1620
|
}
|
|
@@ -1448,8 +1639,8 @@ var SidecarGit = class {
|
|
|
1448
1639
|
*/
|
|
1449
1640
|
async commit(slug, message, opts) {
|
|
1450
1641
|
if (opts?.skip) return;
|
|
1451
|
-
const slugDir =
|
|
1452
|
-
if (
|
|
1642
|
+
const slugDir = path5.join(this.rootDir, slug);
|
|
1643
|
+
if (fs10.existsSync(slugDir)) {
|
|
1453
1644
|
this.spawn(["add", "--", slug]);
|
|
1454
1645
|
} else {
|
|
1455
1646
|
this.spawn(["add", "-A"]);
|
|
@@ -1507,181 +1698,30 @@ ${result.stderr}`;
|
|
|
1507
1698
|
return result.exitCode === 0;
|
|
1508
1699
|
}
|
|
1509
1700
|
};
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
instances: z2.array(LibraryInstanceRefSchema).default([])
|
|
1518
|
-
});
|
|
1519
|
-
var PresetAppliedItemSchema = z2.object({
|
|
1520
|
-
instance_id: z2.string().uuid(),
|
|
1521
|
-
def_ref: z2.string()
|
|
1522
|
-
});
|
|
1523
|
-
var PresetAppliedSchema = z2.object({
|
|
1524
|
-
ref: z2.string(),
|
|
1525
|
-
applied_at: z2.string(),
|
|
1526
|
-
items: z2.array(PresetAppliedItemSchema).default([])
|
|
1527
|
-
});
|
|
1528
|
-
var WorkspaceConfigV2Schema = z2.object({
|
|
1529
|
-
version: z2.literal(2),
|
|
1530
|
-
library: LibrarySectionSchema.optional(),
|
|
1531
|
-
presets_applied: z2.array(PresetAppliedSchema).optional()
|
|
1532
|
-
});
|
|
1533
|
-
var LockAssignmentSchema = z2.object({
|
|
1534
|
-
assignment_id: z2.string().uuid(),
|
|
1535
|
-
instance_id: z2.string().uuid(),
|
|
1536
|
-
def_ref: z2.string(),
|
|
1537
|
-
resolved_version: z2.string(),
|
|
1538
|
-
resolved_sha256: z2.string().optional(),
|
|
1539
|
-
pin_policy: PinPolicySchema
|
|
1540
|
-
});
|
|
1541
|
-
var RequiresEdgeSchema = z2.object({
|
|
1542
|
-
from: z2.string(),
|
|
1543
|
-
to: z2.string(),
|
|
1544
|
-
kind: z2.string().optional()
|
|
1545
|
-
});
|
|
1546
|
-
var PresetExpandedSchema = z2.object({
|
|
1547
|
-
preset_ref: z2.string(),
|
|
1548
|
-
instance_ids: z2.array(z2.string().uuid())
|
|
1549
|
-
});
|
|
1550
|
-
var LockFileSchema = z2.object({
|
|
1551
|
-
version: z2.literal(2),
|
|
1552
|
-
generated_at: z2.string(),
|
|
1553
|
-
assignments: z2.array(LockAssignmentSchema).default([]),
|
|
1554
|
-
requires_graph: z2.array(RequiresEdgeSchema).default([]),
|
|
1555
|
-
presets_expanded: z2.array(PresetExpandedSchema).default([])
|
|
1556
|
-
});
|
|
1557
|
-
function parseWorkspaceV2(raw) {
|
|
1558
|
-
if (raw.version !== 2) return null;
|
|
1559
|
-
return WorkspaceConfigV2Schema.parse(raw);
|
|
1560
|
-
}
|
|
1561
|
-
function loadWorkspaceV2(dir, filename = "skaile.yaml") {
|
|
1562
|
-
const filePath = path4.join(dir, filename);
|
|
1563
|
-
if (!fs11.existsSync(filePath)) return null;
|
|
1564
|
-
const content = fs11.readFileSync(filePath, "utf-8");
|
|
1701
|
+
function migrateWorkspaceConfig(dir, opts) {
|
|
1702
|
+
const filename = opts?.filename ?? "skaile.yaml";
|
|
1703
|
+
const filePath = path5.join(dir, filename);
|
|
1704
|
+
if (!fs10.existsSync(filePath)) {
|
|
1705
|
+
return { migrated: false, version: 1, warnings: ["No skaile.yaml found"] };
|
|
1706
|
+
}
|
|
1707
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
1565
1708
|
const raw = parse(content);
|
|
1566
|
-
if (!raw || typeof raw !== "object")
|
|
1567
|
-
|
|
1568
|
-
}
|
|
1569
|
-
function saveWorkspaceV2(dir, v2, filename = "skaile.yaml") {
|
|
1570
|
-
const filePath = path4.join(dir, filename);
|
|
1571
|
-
let existing = {};
|
|
1572
|
-
if (fs11.existsSync(filePath)) {
|
|
1573
|
-
const content = fs11.readFileSync(filePath, "utf-8");
|
|
1574
|
-
existing = parse(content) ?? {};
|
|
1709
|
+
if (!raw || typeof raw !== "object") {
|
|
1710
|
+
return { migrated: false, version: 1, warnings: ["Invalid YAML content"] };
|
|
1575
1711
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
if (v2.presets_applied !== void 0) existing.presets_applied = v2.presets_applied;
|
|
1579
|
-
if (!fs11.existsSync(dir)) {
|
|
1580
|
-
fs11.mkdirSync(dir, { recursive: true });
|
|
1712
|
+
if (raw.version === 2) {
|
|
1713
|
+
return { migrated: false, version: 2, warnings: [] };
|
|
1581
1714
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
if (!fs11.existsSync(filePath)) return null;
|
|
1588
|
-
const content = fs11.readFileSync(filePath, "utf-8");
|
|
1589
|
-
const raw = parse(content);
|
|
1590
|
-
if (raw !== null && !("assignments" in raw) && "subscriptions" in raw) {
|
|
1591
|
-
const legacySubs = raw["subscriptions"];
|
|
1592
|
-
raw["assignments"] = Array.isArray(legacySubs) ? legacySubs.map((s) => {
|
|
1593
|
-
if (s !== null && typeof s === "object" && "subscription_id" in s && !("assignment_id" in s)) {
|
|
1594
|
-
const { subscription_id, ...rest } = s;
|
|
1595
|
-
return { assignment_id: subscription_id, ...rest };
|
|
1596
|
-
}
|
|
1597
|
-
return s;
|
|
1598
|
-
}) : [];
|
|
1599
|
-
delete raw["subscriptions"];
|
|
1715
|
+
const warnings = [];
|
|
1716
|
+
if (raw.dependencies && Array.isArray(raw.dependencies)) {
|
|
1717
|
+
warnings.push(
|
|
1718
|
+
"v1 'dependencies' field found. In v2, use 'library.instances' to reference configured asset instances. Manual migration recommended."
|
|
1719
|
+
);
|
|
1600
1720
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
if (!fs11.existsSync(dir)) {
|
|
1606
|
-
fs11.mkdirSync(dir, { recursive: true });
|
|
1607
|
-
}
|
|
1608
|
-
fs11.writeFileSync(
|
|
1609
|
-
filePath,
|
|
1610
|
-
`# Auto-generated by skaile. Do not edit manually.
|
|
1611
|
-
${stringify(lock, { indent: 2 })}`,
|
|
1612
|
-
"utf-8"
|
|
1613
|
-
);
|
|
1614
|
-
}
|
|
1615
|
-
function createEmptyLockFile() {
|
|
1616
|
-
return {
|
|
1617
|
-
version: 2,
|
|
1618
|
-
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1619
|
-
assignments: [],
|
|
1620
|
-
requires_graph: [],
|
|
1621
|
-
presets_expanded: []
|
|
1622
|
-
};
|
|
1623
|
-
}
|
|
1624
|
-
function detectWorkspaceVersion(dir, filename = "skaile.yaml") {
|
|
1625
|
-
const filePath = path4.join(dir, filename);
|
|
1626
|
-
if (!fs11.existsSync(filePath)) return null;
|
|
1627
|
-
const content = fs11.readFileSync(filePath, "utf-8");
|
|
1628
|
-
const raw = parse(content);
|
|
1629
|
-
if (!raw || typeof raw !== "object") return null;
|
|
1630
|
-
return raw.version === 2 ? 2 : 1;
|
|
1631
|
-
}
|
|
1632
|
-
var MANIFEST_FILENAME = "skaile.manifest.yaml";
|
|
1633
|
-
async function writeManifestIfMissing(input) {
|
|
1634
|
-
const manifestPath = path4.join(input.libraryPath, MANIFEST_FILENAME);
|
|
1635
|
-
if (fs11.existsSync(manifestPath)) return { wrote: false, manifestPath };
|
|
1636
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1637
|
-
const yaml = `# Generated by skaile library add on ${now} (structure: ${input.structure})
|
|
1638
|
-
# Edit by hand to inline discovered assets; or run \`skaile library refresh-manifest\`.
|
|
1639
|
-
publisher: ""
|
|
1640
|
-
assets: []
|
|
1641
|
-
`;
|
|
1642
|
-
fs11.writeFileSync(manifestPath, yaml, "utf-8");
|
|
1643
|
-
if (input.ownership !== "owner") {
|
|
1644
|
-
const excludePath = path4.join(input.libraryPath, ".git", "info", "exclude");
|
|
1645
|
-
if (fs11.existsSync(path4.dirname(excludePath))) {
|
|
1646
|
-
const current = fs11.existsSync(excludePath) ? fs11.readFileSync(excludePath, "utf-8") : "";
|
|
1647
|
-
if (!current.includes(MANIFEST_FILENAME)) {
|
|
1648
|
-
fs11.writeFileSync(
|
|
1649
|
-
excludePath,
|
|
1650
|
-
(current.endsWith("\n") || current === "" ? current : `${current}
|
|
1651
|
-
`) + `# skaile-managed local-overlay manifest
|
|
1652
|
-
${MANIFEST_FILENAME}
|
|
1653
|
-
`,
|
|
1654
|
-
"utf-8"
|
|
1655
|
-
);
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
return { wrote: true, manifestPath };
|
|
1660
|
-
}
|
|
1661
|
-
function migrateWorkspaceConfig(dir, opts) {
|
|
1662
|
-
const filename = opts?.filename ?? "skaile.yaml";
|
|
1663
|
-
const filePath = path4.join(dir, filename);
|
|
1664
|
-
if (!fs11.existsSync(filePath)) {
|
|
1665
|
-
return { migrated: false, version: 1, warnings: ["No skaile.yaml found"] };
|
|
1666
|
-
}
|
|
1667
|
-
const content = fs11.readFileSync(filePath, "utf-8");
|
|
1668
|
-
const raw = parse(content);
|
|
1669
|
-
if (!raw || typeof raw !== "object") {
|
|
1670
|
-
return { migrated: false, version: 1, warnings: ["Invalid YAML content"] };
|
|
1671
|
-
}
|
|
1672
|
-
if (raw.version === 2) {
|
|
1673
|
-
return { migrated: false, version: 2, warnings: [] };
|
|
1674
|
-
}
|
|
1675
|
-
const warnings = [];
|
|
1676
|
-
if (raw.dependencies && Array.isArray(raw.dependencies)) {
|
|
1677
|
-
warnings.push(
|
|
1678
|
-
"v1 'dependencies' field found. In v2, use 'library.instances' to reference configured asset instances. Manual migration recommended."
|
|
1679
|
-
);
|
|
1680
|
-
}
|
|
1681
|
-
if (raw.ai_resources) {
|
|
1682
|
-
warnings.push(
|
|
1683
|
-
"v1 'ai_resources' field found. In v2, sources are managed via 'skaile source add'. The field still works but is deprecated."
|
|
1684
|
-
);
|
|
1721
|
+
if (raw.ai_resources) {
|
|
1722
|
+
warnings.push(
|
|
1723
|
+
"v1 'ai_resources' field found. In v2, sources are managed via 'skaile source add'. The field still works but is deprecated."
|
|
1724
|
+
);
|
|
1685
1725
|
}
|
|
1686
1726
|
if (!opts?.dryRun) {
|
|
1687
1727
|
const doc = parseDocument(content);
|
|
@@ -1696,7 +1736,7 @@ function migrateWorkspaceConfig(dir, opts) {
|
|
|
1696
1736
|
} else {
|
|
1697
1737
|
doc.set("version", 2);
|
|
1698
1738
|
}
|
|
1699
|
-
|
|
1739
|
+
fs10.writeFileSync(filePath, doc.toString(), "utf-8");
|
|
1700
1740
|
}
|
|
1701
1741
|
return {
|
|
1702
1742
|
migrated: !opts?.dryRun,
|
|
@@ -1714,37 +1754,50 @@ function resolvePin(pin, availableVersions) {
|
|
|
1714
1754
|
return { version: parsed[0].raw, match: "latest" };
|
|
1715
1755
|
}
|
|
1716
1756
|
if (pin.startsWith("exact:")) {
|
|
1717
|
-
|
|
1718
|
-
const found = parsed.find((v) => v.raw === target);
|
|
1719
|
-
return found ? { version: found.raw, match: "exact" } : null;
|
|
1757
|
+
return resolveExactPin(pin, parsed);
|
|
1720
1758
|
}
|
|
1721
1759
|
if (pin.startsWith("^")) {
|
|
1722
|
-
|
|
1723
|
-
if (!target) return null;
|
|
1724
|
-
const match = parsed.find(
|
|
1725
|
-
(v) => v.major === target.major && (v.minor > target.minor || v.minor === target.minor && v.patch >= target.patch)
|
|
1726
|
-
);
|
|
1727
|
-
return match ? { version: match.raw, match: "range" } : null;
|
|
1760
|
+
return resolveCaretPin(pin, parsed);
|
|
1728
1761
|
}
|
|
1729
1762
|
if (pin.startsWith("~")) {
|
|
1730
|
-
|
|
1731
|
-
if (!target) return null;
|
|
1732
|
-
const match = parsed.find(
|
|
1733
|
-
(v) => v.major === target.major && v.minor === target.minor && v.patch >= target.patch
|
|
1734
|
-
);
|
|
1735
|
-
return match ? { version: match.raw, match: "range" } : null;
|
|
1763
|
+
return resolveTildePin(pin, parsed);
|
|
1736
1764
|
}
|
|
1737
1765
|
if (pin.endsWith(".x")) {
|
|
1738
|
-
|
|
1739
|
-
const parts = base.split(".");
|
|
1740
|
-
const major = Number.parseInt(parts[0], 10);
|
|
1741
|
-
const minor = parts.length > 1 ? Number.parseInt(parts[1], 10) : void 0;
|
|
1742
|
-
if (Number.isNaN(major)) return null;
|
|
1743
|
-
const match = parsed.find(
|
|
1744
|
-
(v) => v.major === major && (minor === void 0 || v.minor === minor)
|
|
1745
|
-
);
|
|
1746
|
-
return match ? { version: match.raw, match: "range" } : null;
|
|
1766
|
+
return resolveWildcardPin(pin, parsed);
|
|
1747
1767
|
}
|
|
1768
|
+
return resolveBarePin(pin, parsed);
|
|
1769
|
+
}
|
|
1770
|
+
function resolveExactPin(pin, parsed) {
|
|
1771
|
+
const target = pin.slice(6);
|
|
1772
|
+
const found = parsed.find((v) => v.raw === target);
|
|
1773
|
+
return found ? { version: found.raw, match: "exact" } : null;
|
|
1774
|
+
}
|
|
1775
|
+
function resolveCaretPin(pin, parsed) {
|
|
1776
|
+
const target = parseRangeTarget(pin.slice(1));
|
|
1777
|
+
if (!target) return null;
|
|
1778
|
+
const match = parsed.find(
|
|
1779
|
+
(v) => v.major === target.major && (v.minor > target.minor || v.minor === target.minor && v.patch >= target.patch)
|
|
1780
|
+
);
|
|
1781
|
+
return match ? { version: match.raw, match: "range" } : null;
|
|
1782
|
+
}
|
|
1783
|
+
function resolveTildePin(pin, parsed) {
|
|
1784
|
+
const target = parseRangeTarget(pin.slice(1));
|
|
1785
|
+
if (!target) return null;
|
|
1786
|
+
const match = parsed.find(
|
|
1787
|
+
(v) => v.major === target.major && v.minor === target.minor && v.patch >= target.patch
|
|
1788
|
+
);
|
|
1789
|
+
return match ? { version: match.raw, match: "range" } : null;
|
|
1790
|
+
}
|
|
1791
|
+
function resolveWildcardPin(pin, parsed) {
|
|
1792
|
+
const base = pin.slice(0, -2);
|
|
1793
|
+
const parts = base.split(".");
|
|
1794
|
+
const major = Number.parseInt(parts[0], 10);
|
|
1795
|
+
const minor = parts.length > 1 ? Number.parseInt(parts[1], 10) : void 0;
|
|
1796
|
+
if (Number.isNaN(major)) return null;
|
|
1797
|
+
const match = parsed.find((v) => v.major === major && (minor === void 0 || v.minor === minor));
|
|
1798
|
+
return match ? { version: match.raw, match: "range" } : null;
|
|
1799
|
+
}
|
|
1800
|
+
function resolveBarePin(pin, parsed) {
|
|
1748
1801
|
const bareSemver = parseSemVer(pin);
|
|
1749
1802
|
if (bareSemver) {
|
|
1750
1803
|
const found = parsed.find((v) => v.raw === pin);
|
|
@@ -1780,1793 +1833,1795 @@ function parseRangeTarget(range) {
|
|
|
1780
1833
|
};
|
|
1781
1834
|
}
|
|
1782
1835
|
|
|
1783
|
-
// library/src/
|
|
1784
|
-
var
|
|
1785
|
-
constructor(
|
|
1786
|
-
|
|
1787
|
-
this.
|
|
1788
|
-
this.rootPath = rootPath;
|
|
1789
|
-
this.registry = registry;
|
|
1790
|
-
this.id = `local:${sourceId}`;
|
|
1791
|
-
this.sidecarPath = options?.sidecarPath;
|
|
1792
|
-
}
|
|
1793
|
-
library;
|
|
1794
|
-
sourceId;
|
|
1795
|
-
rootPath;
|
|
1796
|
-
registry;
|
|
1797
|
-
id;
|
|
1798
|
-
sidecarPath;
|
|
1799
|
-
async resolve(ref) {
|
|
1800
|
-
const def = await this.library.getAssetDef(ref);
|
|
1801
|
-
if (!def || def.libraryId !== this.sourceId) return null;
|
|
1802
|
-
return defToCatalogAsset(def);
|
|
1803
|
-
}
|
|
1804
|
-
async listAssets(filter) {
|
|
1805
|
-
const libraryFilter = { libraryId: this.sourceId };
|
|
1806
|
-
if (filter?.kind) libraryFilter.kind = filter.kind;
|
|
1807
|
-
if (filter?.publisher) libraryFilter.publisher = filter.publisher;
|
|
1808
|
-
if (filter?.prefix) libraryFilter.prefix = filter.prefix;
|
|
1809
|
-
const defs = await this.library.listAssetDefs(libraryFilter);
|
|
1810
|
-
return defs.map(defToCatalogAsset);
|
|
1836
|
+
// library/src/preset/interpolate.ts
|
|
1837
|
+
var InterpolationError = class extends Error {
|
|
1838
|
+
constructor(message) {
|
|
1839
|
+
super(message);
|
|
1840
|
+
this.name = "InterpolationError";
|
|
1811
1841
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
const
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1842
|
+
};
|
|
1843
|
+
function interpolate(materialize, baseConfig, context, credentialRefPrefix) {
|
|
1844
|
+
const config = { ...baseConfig ?? {} };
|
|
1845
|
+
const secretsToStore = [];
|
|
1846
|
+
let credentialRef;
|
|
1847
|
+
if (!materialize) {
|
|
1848
|
+
for (const [key, value] of Object.entries(context.inputs)) {
|
|
1849
|
+
config[key] = value;
|
|
1850
|
+
}
|
|
1851
|
+
if (Object.keys(context.secrets).length > 0) {
|
|
1852
|
+
credentialRef = `${credentialRefPrefix}/creds`;
|
|
1853
|
+
for (const [key, value] of Object.entries(context.secrets)) {
|
|
1854
|
+
secretsToStore.push({ ref: `${credentialRef}/${key}`, value });
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return { config, credentialRef, secretsToStore };
|
|
1822
1858
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1859
|
+
for (const [configPath, template] of Object.entries(materialize)) {
|
|
1860
|
+
const resolved = resolveTemplate(template, context);
|
|
1861
|
+
if (resolved.hasSecret) {
|
|
1862
|
+
if (!credentialRef) {
|
|
1863
|
+
credentialRef = `${credentialRefPrefix}/creds`;
|
|
1864
|
+
}
|
|
1865
|
+
const secretRef = `${credentialRef}/${configPath}`;
|
|
1866
|
+
secretsToStore.push({ ref: secretRef, value: resolved.value });
|
|
1867
|
+
setNestedValue(config, configPath, `secret://${secretRef}`);
|
|
1868
|
+
} else {
|
|
1869
|
+
setNestedValue(config, configPath, resolved.value);
|
|
1870
|
+
}
|
|
1827
1871
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1872
|
+
return { config, credentialRef, secretsToStore };
|
|
1873
|
+
}
|
|
1874
|
+
async function storeSecrets(router, secretsToStore) {
|
|
1875
|
+
for (const { ref, value } of secretsToStore) {
|
|
1876
|
+
const encoded = new TextEncoder().encode(value);
|
|
1877
|
+
await router.setSecret(ref, encoded);
|
|
1830
1878
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
sidecarPath: this.sidecarPath,
|
|
1846
|
-
includeDev: syncOpts?.includeDev ?? false
|
|
1847
|
-
};
|
|
1848
|
-
if (!this.sidecarPath) {
|
|
1849
|
-
opts.sourceConfig = readManifestSourceConfig(this.rootPath);
|
|
1879
|
+
}
|
|
1880
|
+
function hasInterpolationTokens(template) {
|
|
1881
|
+
return INTERPOLATION_PATTERN.test(template);
|
|
1882
|
+
}
|
|
1883
|
+
function extractReferencedKeys(materialize) {
|
|
1884
|
+
const inputKeys = /* @__PURE__ */ new Set();
|
|
1885
|
+
const secretKeys = /* @__PURE__ */ new Set();
|
|
1886
|
+
for (const template of Object.values(materialize)) {
|
|
1887
|
+
const pattern = new RegExp(INTERPOLATION_PATTERN.source, "g");
|
|
1888
|
+
for (const match of template.matchAll(pattern)) {
|
|
1889
|
+
const namespace = match[1];
|
|
1890
|
+
const key = match[2];
|
|
1891
|
+
if (namespace === "INPUT") inputKeys.add(key);
|
|
1892
|
+
else if (namespace === "SECRET") secretKeys.add(key);
|
|
1850
1893
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
version: asset.version,
|
|
1863
|
-
sha256: asset.sha256,
|
|
1864
|
-
description: typeof asset.manifest.description === "string" ? asset.manifest.description : void 0,
|
|
1865
|
-
license: typeof asset.manifest.license === "string" ? asset.manifest.license : void 0,
|
|
1866
|
-
category: typeof asset.manifest.category === "string" ? asset.manifest.category : void 0,
|
|
1867
|
-
manifest: asset.manifest,
|
|
1868
|
-
libraryId: this.sourceId,
|
|
1869
|
-
cachedAt: /* @__PURE__ */ new Date(),
|
|
1870
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1871
|
-
});
|
|
1872
|
-
assetsUpdated++;
|
|
1873
|
-
} catch (err) {
|
|
1874
|
-
errors.push(`${asset.ref}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1894
|
+
}
|
|
1895
|
+
return { inputKeys: [...inputKeys], secretKeys: [...secretKeys] };
|
|
1896
|
+
}
|
|
1897
|
+
var INTERPOLATION_PATTERN = /\$\{(INPUT|SECRET):([^}]+)\}/;
|
|
1898
|
+
function resolveTemplate(template, context) {
|
|
1899
|
+
const singleMatch = template.match(/^\$\{(INPUT|SECRET):([^}]+)\}$/);
|
|
1900
|
+
if (singleMatch) {
|
|
1901
|
+
const [, namespace, key] = singleMatch;
|
|
1902
|
+
if (namespace === "SECRET") {
|
|
1903
|
+
if (!(key in context.secrets)) {
|
|
1904
|
+
throw new InterpolationError(`Missing secret key "${key}" in interpolation context`);
|
|
1875
1905
|
}
|
|
1906
|
+
return { value: context.secrets[key], hasSecret: true };
|
|
1876
1907
|
}
|
|
1877
|
-
|
|
1878
|
-
|
|
1908
|
+
if (!(key in context.inputs)) {
|
|
1909
|
+
throw new InterpolationError(`Missing input key "${key}" in interpolation context`);
|
|
1879
1910
|
}
|
|
1880
|
-
return {
|
|
1911
|
+
return { value: context.inputs[key], hasSecret: false };
|
|
1881
1912
|
}
|
|
1882
|
-
};
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
};
|
|
1896
|
-
}
|
|
1897
|
-
function defaultCacheDir() {
|
|
1898
|
-
return path4.join(os.homedir(), ".skaile", "catalog-cache");
|
|
1913
|
+
const tokens = [...template.matchAll(/\$\{(INPUT|SECRET):([^}]+)\}/g)];
|
|
1914
|
+
const hasSecretToken = tokens.some(([, ns]) => ns === "SECRET");
|
|
1915
|
+
if (hasSecretToken) {
|
|
1916
|
+
throw new InterpolationError(
|
|
1917
|
+
`Template "${template}" mixes \${SECRET:...} with other tokens. SECRET must be the sole token in a template value.`
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
const result = template.replace(/\$\{INPUT:([^}]+)\}/g, (_, key) => {
|
|
1921
|
+
if (!(key in context.inputs)) {
|
|
1922
|
+
throw new InterpolationError(`Missing input key "${key}" in interpolation context`);
|
|
1923
|
+
}
|
|
1924
|
+
return String(context.inputs[key] ?? "");
|
|
1925
|
+
});
|
|
1926
|
+
return { value: result, hasSecret: false };
|
|
1899
1927
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
this.ttlMs = opts.ttlMs;
|
|
1910
|
-
this.nowFn = opts.now ?? Date.now;
|
|
1911
|
-
this.ensureDir();
|
|
1912
|
-
}
|
|
1913
|
-
// ── public: resolve cache ───────────────────────────────────────────────
|
|
1914
|
-
/**
|
|
1915
|
-
* Return a fresh resolve-cache entry for `ref`, or `null` if absent / stale.
|
|
1916
|
-
*
|
|
1917
|
-
* When `ttlMs === 0` (air-gapped), all entries are considered fresh
|
|
1918
|
-
* regardless of `fetchedAt`.
|
|
1919
|
-
*/
|
|
1920
|
-
getResolve(ref) {
|
|
1921
|
-
const idx = this.loadIndex();
|
|
1922
|
-
const entry = idx.resolve[ref];
|
|
1923
|
-
if (!entry) return null;
|
|
1924
|
-
if (this.ttlMs === 0) return entry;
|
|
1925
|
-
if (this.nowFn() - entry.fetchedAt > this.ttlMs) return null;
|
|
1926
|
-
return entry;
|
|
1927
|
-
}
|
|
1928
|
-
/**
|
|
1929
|
-
* Return a stale resolve-cache entry regardless of TTL — for stale-while-revalidate.
|
|
1930
|
-
*/
|
|
1931
|
-
getResolveStale(ref) {
|
|
1932
|
-
const idx = this.loadIndex();
|
|
1933
|
-
return idx.resolve[ref] ?? null;
|
|
1934
|
-
}
|
|
1935
|
-
/** Persist a resolve-cache entry (use `null` to negatively cache misses). */
|
|
1936
|
-
setResolve(ref, value) {
|
|
1937
|
-
const idx = this.loadIndex();
|
|
1938
|
-
idx.resolve[ref] = { fetchedAt: this.nowFn(), value };
|
|
1939
|
-
this.writeIndex(idx);
|
|
1940
|
-
}
|
|
1941
|
-
// ── public: list cache ──────────────────────────────────────────────────
|
|
1942
|
-
/**
|
|
1943
|
-
* Return a fresh list-cache entry for `filter`, or `null` if absent / stale.
|
|
1944
|
-
*/
|
|
1945
|
-
getList(filter) {
|
|
1946
|
-
const key = filterKey(filter);
|
|
1947
|
-
const idx = this.loadIndex();
|
|
1948
|
-
const entry = idx.list[key];
|
|
1949
|
-
if (!entry) return null;
|
|
1950
|
-
if (this.ttlMs === 0) return entry;
|
|
1951
|
-
if (this.nowFn() - entry.fetchedAt > this.ttlMs) return null;
|
|
1952
|
-
return entry;
|
|
1953
|
-
}
|
|
1954
|
-
/** Return a stale list-cache entry regardless of TTL — for stale-while-revalidate. */
|
|
1955
|
-
getListStale(filter) {
|
|
1956
|
-
const key = filterKey(filter);
|
|
1957
|
-
const idx = this.loadIndex();
|
|
1958
|
-
return idx.list[key] ?? null;
|
|
1959
|
-
}
|
|
1960
|
-
/** Persist a list-cache entry. */
|
|
1961
|
-
setList(filter, items) {
|
|
1962
|
-
const key = filterKey(filter);
|
|
1963
|
-
const idx = this.loadIndex();
|
|
1964
|
-
idx.list[key] = { fetchedAt: this.nowFn(), items };
|
|
1965
|
-
this.writeIndex(idx);
|
|
1966
|
-
}
|
|
1967
|
-
// ── public: tarball cache ───────────────────────────────────────────────
|
|
1968
|
-
/** Absolute path where a tarball with `sha256` would be cached. */
|
|
1969
|
-
tarballPath(sha256) {
|
|
1970
|
-
return path4.join(this.dir, TARBALL_DIR, `${sha256}.tgz`);
|
|
1971
|
-
}
|
|
1972
|
-
/** Read a cached tarball from disk if present, otherwise `null`. */
|
|
1973
|
-
readTarball(sha256) {
|
|
1974
|
-
const p = this.tarballPath(sha256);
|
|
1975
|
-
if (!fs11.existsSync(p)) return null;
|
|
1976
|
-
return fs11.readFileSync(p);
|
|
1928
|
+
function setNestedValue(obj, path13, value) {
|
|
1929
|
+
const parts = path13.split(".");
|
|
1930
|
+
let current = obj;
|
|
1931
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1932
|
+
const key = parts[i];
|
|
1933
|
+
if (current[key] === void 0 || current[key] === null || typeof current[key] !== "object") {
|
|
1934
|
+
current[key] = {};
|
|
1935
|
+
}
|
|
1936
|
+
current = current[key];
|
|
1977
1937
|
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1938
|
+
current[parts[parts.length - 1]] = value;
|
|
1939
|
+
}
|
|
1940
|
+
function parsePreset(data) {
|
|
1941
|
+
const zodResult = PresetManifestSchema.safeParse(data);
|
|
1942
|
+
if (!zodResult.success) {
|
|
1943
|
+
return {
|
|
1944
|
+
ok: false,
|
|
1945
|
+
errors: zodResult.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
1946
|
+
};
|
|
1986
1947
|
}
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
invalidateAll() {
|
|
1995
|
-
const idx = { version: 1, resolve: {}, list: {} };
|
|
1996
|
-
this.index = idx;
|
|
1997
|
-
this.writeIndex(idx);
|
|
1948
|
+
const preset = zodResult.data;
|
|
1949
|
+
const errors = [];
|
|
1950
|
+
const ids = validateUniqueIds(preset, errors);
|
|
1951
|
+
validateCrossItemRefs(preset, ids, errors);
|
|
1952
|
+
validatePlaceholderItems(preset, errors);
|
|
1953
|
+
if (errors.length > 0) {
|
|
1954
|
+
return { ok: false, preset, errors };
|
|
1998
1955
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
if (
|
|
2006
|
-
|
|
1956
|
+
return { ok: true, preset, errors: [] };
|
|
1957
|
+
}
|
|
1958
|
+
function validateUniqueIds(preset, errors) {
|
|
1959
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1960
|
+
const refCounts = /* @__PURE__ */ new Map();
|
|
1961
|
+
for (const item of preset.items) {
|
|
1962
|
+
if (item.id) {
|
|
1963
|
+
if (ids.has(item.id)) {
|
|
1964
|
+
errors.push(`Duplicate item id: "${item.id}"`);
|
|
1965
|
+
}
|
|
1966
|
+
ids.add(item.id);
|
|
1967
|
+
} else {
|
|
1968
|
+
refCounts.set(item.ref, (refCounts.get(item.ref) ?? 0) + 1);
|
|
2007
1969
|
}
|
|
2008
1970
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
if (!fs11.existsSync(this.dir)) fs11.mkdirSync(this.dir, { recursive: true });
|
|
2016
|
-
const tarballDir = path4.join(this.dir, TARBALL_DIR);
|
|
2017
|
-
if (!fs11.existsSync(tarballDir)) fs11.mkdirSync(tarballDir, { recursive: true });
|
|
2018
|
-
}
|
|
2019
|
-
indexPath() {
|
|
2020
|
-
return path4.join(this.dir, INDEX_FILE);
|
|
1971
|
+
for (const [ref, count] of refCounts) {
|
|
1972
|
+
if (count > 1) {
|
|
1973
|
+
errors.push(
|
|
1974
|
+
`${count} items share ref "${ref}" without distinct "id" fields \u2014 upgrade matching will be ambiguous`
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
2021
1977
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
1978
|
+
return ids;
|
|
1979
|
+
}
|
|
1980
|
+
function validateCrossItemRefs(preset, ids, errors) {
|
|
1981
|
+
for (const item of preset.items) {
|
|
1982
|
+
if (item.composes) {
|
|
1983
|
+
for (const compose of item.composes) {
|
|
1984
|
+
if (compose.ref.startsWith("#")) {
|
|
1985
|
+
const targetId = compose.ref.slice(1);
|
|
1986
|
+
if (!ids.has(targetId)) {
|
|
1987
|
+
errors.push(
|
|
1988
|
+
`Cross-item ref "${compose.ref}" in item "${item.id ?? item.ref}" references undeclared id "${targetId}"`
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
2028
1993
|
}
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
function validatePlaceholderItems(preset, errors) {
|
|
1997
|
+
for (const item of preset.items) {
|
|
1998
|
+
if (item.placeholders) {
|
|
1999
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2000
|
+
for (const ph of item.placeholders) {
|
|
2001
|
+
if (keys.has(ph.key)) {
|
|
2002
|
+
errors.push(`Duplicate placeholder key "${ph.key}" in item "${item.id ?? item.ref}"`);
|
|
2003
|
+
}
|
|
2004
|
+
keys.add(ph.key);
|
|
2005
|
+
if (ph.type === "enum" && (!ph.values || ph.values.length === 0)) {
|
|
2006
|
+
errors.push(`Placeholder "${ph.key}" has type "enum" but no values defined`);
|
|
2007
|
+
}
|
|
2038
2008
|
}
|
|
2039
|
-
} catch {
|
|
2040
2009
|
}
|
|
2041
|
-
this.index = { version: 1, resolve: {}, list: {} };
|
|
2042
|
-
return this.index;
|
|
2043
2010
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2011
|
+
}
|
|
2012
|
+
function parsePresetYaml(yamlContent) {
|
|
2013
|
+
let data;
|
|
2014
|
+
try {
|
|
2015
|
+
data = parse(yamlContent);
|
|
2016
|
+
} catch (err) {
|
|
2017
|
+
return {
|
|
2018
|
+
ok: false,
|
|
2019
|
+
errors: [`YAML parse error: ${err instanceof Error ? err.message : String(err)}`]
|
|
2020
|
+
};
|
|
2050
2021
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
const
|
|
2055
|
-
const
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2022
|
+
return parsePreset(data);
|
|
2023
|
+
}
|
|
2024
|
+
function detectNestedPresetRefs(preset, knownPresetRefs) {
|
|
2025
|
+
const nested = [];
|
|
2026
|
+
for (const item of preset.items) {
|
|
2027
|
+
if (item.ref.startsWith("library://") || item.ref.startsWith("#")) continue;
|
|
2028
|
+
if (knownPresetRefs?.has(item.ref)) {
|
|
2029
|
+
nested.push(item.ref);
|
|
2030
|
+
}
|
|
2059
2031
|
}
|
|
2060
|
-
return
|
|
2032
|
+
return nested;
|
|
2061
2033
|
}
|
|
2062
|
-
function
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
|
|
2034
|
+
function validateNestingDepth(presetRef, currentDepth) {
|
|
2035
|
+
const MAX_DEPTH = 3;
|
|
2036
|
+
if (currentDepth >= MAX_DEPTH) {
|
|
2037
|
+
return `Preset "${presetRef}" exceeds maximum nesting depth (${MAX_DEPTH} levels allowed, depth ${currentDepth} reached)`;
|
|
2038
|
+
}
|
|
2039
|
+
return void 0;
|
|
2066
2040
|
}
|
|
2067
2041
|
|
|
2068
|
-
// library/src/
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
};
|
|
2079
|
-
var OfflineError = class extends Error {
|
|
2080
|
-
constructor(message) {
|
|
2081
|
-
super(`${message} (air-gapped mode; run \`skaile update\` to refresh the cache)`);
|
|
2082
|
-
this.name = "OfflineError";
|
|
2042
|
+
// library/src/preset/placeholders.ts
|
|
2043
|
+
function validatePlaceholder(placeholder, value) {
|
|
2044
|
+
const { key, type = "text", required } = placeholder;
|
|
2045
|
+
if (value === void 0 || value === null || value === "") {
|
|
2046
|
+
if (placeholder.default !== void 0) {
|
|
2047
|
+
return { valid: true, errors: [], value: placeholder.default };
|
|
2048
|
+
}
|
|
2049
|
+
if (required === true || required === void 0) {
|
|
2050
|
+
return { valid: false, errors: [`Placeholder "${key}" is required`] };
|
|
2051
|
+
}
|
|
2052
|
+
return { valid: true, errors: [], value: void 0 };
|
|
2083
2053
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
httpClient;
|
|
2102
|
-
silentBackgroundErrors;
|
|
2103
|
-
/** Tracks in-flight background revalidations so callers can `await` them in tests. */
|
|
2104
|
-
inFlight = /* @__PURE__ */ new Map();
|
|
2105
|
-
constructor(opts) {
|
|
2106
|
-
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
2107
|
-
this.id = `remote:${this.baseUrl}`;
|
|
2108
|
-
this.cacheTtlMs = opts.cacheTtlMs ?? DEFAULT_TTL_MS;
|
|
2109
|
-
this.httpClient = opts.httpClient ?? defaultFetch;
|
|
2110
|
-
this.silentBackgroundErrors = opts.silentBackgroundErrors ?? false;
|
|
2111
|
-
this.cache = opts.cache ?? new CatalogCache({
|
|
2112
|
-
dir: opts.cacheDir,
|
|
2113
|
-
ttlMs: this.cacheTtlMs,
|
|
2114
|
-
now: opts.now
|
|
2115
|
-
});
|
|
2054
|
+
switch (type) {
|
|
2055
|
+
case "text":
|
|
2056
|
+
return validateText(key, value);
|
|
2057
|
+
case "multiline":
|
|
2058
|
+
return validateMultiline(key, value);
|
|
2059
|
+
case "secret":
|
|
2060
|
+
return validateSecret(key, value);
|
|
2061
|
+
case "number":
|
|
2062
|
+
return validateNumber(key, value);
|
|
2063
|
+
case "boolean":
|
|
2064
|
+
return validateBoolean(key, value);
|
|
2065
|
+
case "enum":
|
|
2066
|
+
return validateEnum(key, value, placeholder.values);
|
|
2067
|
+
case "oauth":
|
|
2068
|
+
return validateOauth(key, value);
|
|
2069
|
+
case "instance-picker":
|
|
2070
|
+
return validateInstancePicker(key, value);
|
|
2116
2071
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
const fresh = this.cache.getResolve(ref);
|
|
2128
|
-
if (fresh) return fresh.value;
|
|
2129
|
-
if (this.cache.airGapped) {
|
|
2130
|
-
const stale2 = this.cache.getResolveStale(ref);
|
|
2131
|
-
if (stale2) return stale2.value;
|
|
2132
|
-
throw new OfflineError(`Asset '${ref}' not in cache`);
|
|
2133
|
-
}
|
|
2134
|
-
const stale = this.cache.getResolveStale(ref);
|
|
2135
|
-
if (stale) {
|
|
2136
|
-
this.scheduleBackground(`resolve:${ref}`, () => this.fetchAndStoreResolve(ref));
|
|
2137
|
-
return stale.value;
|
|
2072
|
+
}
|
|
2073
|
+
function validateAllPlaceholders(placeholders, inputs) {
|
|
2074
|
+
const values = {};
|
|
2075
|
+
const errors = [];
|
|
2076
|
+
for (const ph of placeholders) {
|
|
2077
|
+
const result = validatePlaceholder(ph, inputs[ph.key]);
|
|
2078
|
+
if (!result.valid) {
|
|
2079
|
+
errors.push(...result.errors);
|
|
2080
|
+
} else if (result.value !== void 0) {
|
|
2081
|
+
values[ph.key] = result.value;
|
|
2138
2082
|
}
|
|
2139
|
-
return this.fetchAndStoreResolve(ref);
|
|
2140
2083
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
* Air-gapped: throws {@link OfflineError} on cache miss.
|
|
2148
|
-
*/
|
|
2149
|
-
async listAssets(filter) {
|
|
2150
|
-
const fresh = this.cache.getList(filter);
|
|
2151
|
-
if (fresh) return fresh.items;
|
|
2152
|
-
if (this.cache.airGapped) {
|
|
2153
|
-
const stale2 = this.cache.getListStale(filter);
|
|
2154
|
-
if (stale2) return stale2.items;
|
|
2155
|
-
throw new OfflineError("Catalog list not in cache");
|
|
2156
|
-
}
|
|
2157
|
-
const stale = this.cache.getListStale(filter);
|
|
2158
|
-
if (stale) {
|
|
2159
|
-
const filterKey2 = JSON.stringify(filter ?? null);
|
|
2160
|
-
this.scheduleBackground(`list:${filterKey2}`, () => this.fetchAndStoreList(filter));
|
|
2161
|
-
return stale.items;
|
|
2162
|
-
}
|
|
2163
|
-
return this.fetchAndStoreList(filter);
|
|
2084
|
+
return { valid: errors.length === 0, values, errors };
|
|
2085
|
+
}
|
|
2086
|
+
async function queryInstancePickerChoices(library, options) {
|
|
2087
|
+
const filter = {};
|
|
2088
|
+
if (options?.kind) {
|
|
2089
|
+
filter.kind = options.kind;
|
|
2164
2090
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
* via the REST endpoint `GET /tarball/:sha256` (which 307-redirects to the
|
|
2168
|
-
* storage tier), then verified against `sha256` on the client side.
|
|
2169
|
-
*
|
|
2170
|
-
* Cache: tarballs are content-addressed and cached forever once written.
|
|
2171
|
-
*
|
|
2172
|
-
* Air-gapped: throws {@link OfflineError} on cache miss.
|
|
2173
|
-
*
|
|
2174
|
-
* @throws {@link TarballHashMismatchError} when the downloaded SHA256 does not match.
|
|
2175
|
-
*/
|
|
2176
|
-
async fetchTarball(_ref, sha256) {
|
|
2177
|
-
const cached = this.cache.readTarball(sha256);
|
|
2178
|
-
if (cached) {
|
|
2179
|
-
const actual2 = sha256Hex(cached);
|
|
2180
|
-
if (actual2 !== sha256) {
|
|
2181
|
-
throw new TarballHashMismatchError(sha256, actual2);
|
|
2182
|
-
}
|
|
2183
|
-
return cached;
|
|
2184
|
-
}
|
|
2185
|
-
if (this.cache.airGapped) {
|
|
2186
|
-
throw new OfflineError(`Tarball '${sha256}' not in cache`);
|
|
2187
|
-
}
|
|
2188
|
-
const url = `${this.baseUrl}/tarball/${encodeURIComponent(sha256)}`;
|
|
2189
|
-
const res = await this.httpClient(url, { method: "GET", redirect: "follow" });
|
|
2190
|
-
if (!res.ok) {
|
|
2191
|
-
throw new CatalogHttpError(
|
|
2192
|
-
`Failed to fetch tarball ${sha256}: ${res.status} ${res.statusText}`,
|
|
2193
|
-
res.status,
|
|
2194
|
-
url
|
|
2195
|
-
);
|
|
2196
|
-
}
|
|
2197
|
-
const buf = new Uint8Array(await res.arrayBuffer());
|
|
2198
|
-
const actual = sha256Hex(buf);
|
|
2199
|
-
if (actual !== sha256) {
|
|
2200
|
-
throw new TarballHashMismatchError(sha256, actual);
|
|
2201
|
-
}
|
|
2202
|
-
this.cache.writeTarball(sha256, buf);
|
|
2203
|
-
return buf;
|
|
2091
|
+
if (options?.defRefPrefix) {
|
|
2092
|
+
filter.defRef = options.defRefPrefix;
|
|
2204
2093
|
}
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2094
|
+
const instances2 = await library.listInstances(filter);
|
|
2095
|
+
return instances2.map((inst) => ({
|
|
2096
|
+
instanceId: inst.id,
|
|
2097
|
+
defRef: inst.defRef,
|
|
2098
|
+
label: `${inst.defRef} (${inst.id.slice(0, 8)})`
|
|
2099
|
+
}));
|
|
2100
|
+
}
|
|
2101
|
+
function getDefaultValue(placeholder) {
|
|
2102
|
+
return placeholder.default;
|
|
2103
|
+
}
|
|
2104
|
+
function isSecretPlaceholder(placeholder) {
|
|
2105
|
+
return placeholder.type === "secret" || placeholder.type === "oauth";
|
|
2106
|
+
}
|
|
2107
|
+
function validateText(key, value) {
|
|
2108
|
+
if (typeof value !== "string") {
|
|
2109
|
+
return { valid: false, errors: [`Placeholder "${key}" must be a string`] };
|
|
2218
2110
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
return [{ id: this.id, type: "remote-catalog", url: this.baseUrl }];
|
|
2111
|
+
return { valid: true, errors: [], value };
|
|
2112
|
+
}
|
|
2113
|
+
function validateMultiline(key, value) {
|
|
2114
|
+
if (typeof value !== "string") {
|
|
2115
|
+
return { valid: false, errors: [`Placeholder "${key}" must be a string`] };
|
|
2225
2116
|
}
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2117
|
+
return { valid: true, errors: [], value };
|
|
2118
|
+
}
|
|
2119
|
+
function validateSecret(key, value) {
|
|
2120
|
+
if (typeof value !== "string") {
|
|
2121
|
+
return { valid: false, errors: [`Placeholder "${key}" must be a string`] };
|
|
2122
|
+
}
|
|
2123
|
+
if (value.length === 0) {
|
|
2124
|
+
return { valid: false, errors: [`Placeholder "${key}" (secret) cannot be empty`] };
|
|
2125
|
+
}
|
|
2126
|
+
return { valid: true, errors: [], value };
|
|
2127
|
+
}
|
|
2128
|
+
function validateNumber(key, value) {
|
|
2129
|
+
if (typeof value === "number" && !Number.isNaN(value)) {
|
|
2130
|
+
return { valid: true, errors: [], value };
|
|
2131
|
+
}
|
|
2132
|
+
if (typeof value === "string") {
|
|
2133
|
+
const parsed = Number(value);
|
|
2134
|
+
if (!Number.isNaN(parsed)) {
|
|
2135
|
+
return { valid: true, errors: [], value: parsed };
|
|
2243
2136
|
}
|
|
2244
|
-
return { assetsCached: items.length };
|
|
2245
2137
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
*
|
|
2257
|
-
* This is a `RemoteCatalogSource`-only method — it is not part of the
|
|
2258
|
-
* `ICatalogSource` contract. `LocalCatalogSource` has the asset bytes on disk
|
|
2259
|
-
* and has no pointer-triple concept, so adding it to the shared interface
|
|
2260
|
-
* would force a meaningless implementation there.
|
|
2261
|
-
*
|
|
2262
|
-
* Not cached: the install path is a one-shot read driven by `skaile install`.
|
|
2263
|
-
*
|
|
2264
|
-
* @throws {@link CatalogHttpError} on a non-2xx response or malformed envelope.
|
|
2265
|
-
*/
|
|
2266
|
-
async getInstallManifest(ref) {
|
|
2267
|
-
const url = trpcGetUrl(this.baseUrl, "catalog.getInstallManifest", { ref });
|
|
2268
|
-
const res = await this.httpClient(url, {
|
|
2269
|
-
method: "GET",
|
|
2270
|
-
headers: { Accept: "application/json" }
|
|
2271
|
-
});
|
|
2272
|
-
if (!res.ok) {
|
|
2273
|
-
throw new CatalogHttpError(
|
|
2274
|
-
`catalog.getInstallManifest ${ref} failed: ${res.status} ${res.statusText}`,
|
|
2275
|
-
res.status,
|
|
2276
|
-
url
|
|
2277
|
-
);
|
|
2138
|
+
return { valid: false, errors: [`Placeholder "${key}" must be a valid number`] };
|
|
2139
|
+
}
|
|
2140
|
+
function validateBoolean(key, value) {
|
|
2141
|
+
if (typeof value === "boolean") {
|
|
2142
|
+
return { valid: true, errors: [], value };
|
|
2143
|
+
}
|
|
2144
|
+
if (typeof value === "string") {
|
|
2145
|
+
const lower = value.toLowerCase();
|
|
2146
|
+
if (lower === "true" || lower === "1" || lower === "yes") {
|
|
2147
|
+
return { valid: true, errors: [], value: true };
|
|
2278
2148
|
}
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
throw new CatalogHttpError(
|
|
2282
|
-
`catalog.getInstallManifest ${ref} returned a non-object payload (got ${typeof data})`,
|
|
2283
|
-
200,
|
|
2284
|
-
url
|
|
2285
|
-
);
|
|
2149
|
+
if (lower === "false" || lower === "0" || lower === "no") {
|
|
2150
|
+
return { valid: true, errors: [], value: false };
|
|
2286
2151
|
}
|
|
2287
|
-
return data;
|
|
2288
2152
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2153
|
+
return { valid: false, errors: [`Placeholder "${key}" must be a boolean`] };
|
|
2154
|
+
}
|
|
2155
|
+
function validateEnum(key, value, allowedValues) {
|
|
2156
|
+
if (!allowedValues || allowedValues.length === 0) {
|
|
2157
|
+
return { valid: false, errors: [`Placeholder "${key}" (enum) has no allowed values defined`] };
|
|
2158
|
+
}
|
|
2159
|
+
const stringValue = String(value);
|
|
2160
|
+
const matches = allowedValues.some((v) => String(v) === stringValue);
|
|
2161
|
+
if (!matches) {
|
|
2162
|
+
return {
|
|
2163
|
+
valid: false,
|
|
2164
|
+
errors: [
|
|
2165
|
+
`Placeholder "${key}" value "${stringValue}" is not in allowed values: [${allowedValues.join(", ")}]`
|
|
2166
|
+
]
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
const matched = allowedValues.find((v) => String(v) === stringValue);
|
|
2170
|
+
return { valid: true, errors: [], value: matched };
|
|
2171
|
+
}
|
|
2172
|
+
function validateOauth(key, value) {
|
|
2173
|
+
if (typeof value !== "string") {
|
|
2174
|
+
return { valid: false, errors: [`Placeholder "${key}" (oauth) must be a string`] };
|
|
2175
|
+
}
|
|
2176
|
+
if (value.length === 0) {
|
|
2177
|
+
return { valid: false, errors: [`Placeholder "${key}" (oauth) cannot be empty`] };
|
|
2178
|
+
}
|
|
2179
|
+
return { valid: true, errors: [], value };
|
|
2180
|
+
}
|
|
2181
|
+
function validateInstancePicker(key, value) {
|
|
2182
|
+
if (typeof value !== "string") {
|
|
2183
|
+
return {
|
|
2184
|
+
valid: false,
|
|
2185
|
+
errors: [`Placeholder "${key}" (instance-picker) must be a string UUID`]
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2189
|
+
if (!uuidPattern.test(value)) {
|
|
2190
|
+
return {
|
|
2191
|
+
valid: false,
|
|
2192
|
+
errors: [`Placeholder "${key}" (instance-picker) must be a valid UUID`]
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
return { valid: true, errors: [], value };
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// library/src/preset/resolve-item.ts
|
|
2199
|
+
function classifyRef(ref) {
|
|
2200
|
+
if (ref.startsWith("library://")) return "library-instance";
|
|
2201
|
+
if (ref.startsWith("#")) return "cross-item";
|
|
2202
|
+
return "asset";
|
|
2203
|
+
}
|
|
2204
|
+
function extractLibraryInstanceId(ref) {
|
|
2205
|
+
const match = ref.match(/^library:\/\/[^/]+\/instances\/([a-f0-9-]+)$/i);
|
|
2206
|
+
return match ? match[1] : null;
|
|
2207
|
+
}
|
|
2208
|
+
async function resolveItem(item, library, knownPresetRefs) {
|
|
2209
|
+
const ref = item.ref;
|
|
2210
|
+
if (ref.startsWith("#")) {
|
|
2211
|
+
return { kind: "cross-item", item, targetId: ref.slice(1) };
|
|
2212
|
+
}
|
|
2213
|
+
if (ref.startsWith("library://")) {
|
|
2214
|
+
const instanceId = extractLibraryInstanceId(ref);
|
|
2215
|
+
if (!instanceId) {
|
|
2216
|
+
return { kind: "library-instance", item, instanceId: "", instance: null };
|
|
2324
2217
|
}
|
|
2325
|
-
const
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2218
|
+
const instance = await library.getInstance(instanceId);
|
|
2219
|
+
return { kind: "library-instance", item, instanceId, instance };
|
|
2220
|
+
}
|
|
2221
|
+
if (knownPresetRefs?.has(ref)) {
|
|
2222
|
+
return { kind: "nested-preset", item, presetRef: ref };
|
|
2223
|
+
}
|
|
2224
|
+
const assetDef = await library.getAssetDef(ref);
|
|
2225
|
+
return { kind: "asset", item, defRef: ref, assetDef };
|
|
2226
|
+
}
|
|
2227
|
+
async function resolveAllItems(items, library, knownPresetRefs) {
|
|
2228
|
+
return Promise.all(items.map((item) => resolveItem(item, library, knownPresetRefs)));
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
// library/src/preset/apply.ts
|
|
2232
|
+
async function applyPreset(presetRef, manifest, inputs, opts) {
|
|
2233
|
+
const depth = opts._depth ?? 0;
|
|
2234
|
+
const pinPolicy = opts.pinPolicy ?? "minor-track";
|
|
2235
|
+
const depthError = validateNestingDepth(presetRef, depth);
|
|
2236
|
+
if (depthError) {
|
|
2237
|
+
return { instances: [], assignments: [], errors: [depthError] };
|
|
2238
|
+
}
|
|
2239
|
+
const result = { instances: [], assignments: [], errors: [] };
|
|
2240
|
+
const itemResults = /* @__PURE__ */ new Map();
|
|
2241
|
+
for (const item of manifest.items) {
|
|
2242
|
+
const itemResult = await applyItemImpl(
|
|
2243
|
+
presetRef,
|
|
2244
|
+
item,
|
|
2245
|
+
inputs,
|
|
2246
|
+
opts,
|
|
2247
|
+
depth,
|
|
2248
|
+
pinPolicy,
|
|
2249
|
+
itemResults
|
|
2250
|
+
);
|
|
2251
|
+
result.errors.push(...itemResult.errors);
|
|
2252
|
+
result.instances.push(...itemResult.instances);
|
|
2253
|
+
if (item.id && itemResult.instances.length > 0) {
|
|
2254
|
+
itemResults.set(item.id, itemResult.instances[0]);
|
|
2332
2255
|
}
|
|
2333
|
-
|
|
2256
|
+
result.assignments.push(...itemResult.assignments);
|
|
2257
|
+
}
|
|
2258
|
+
return result;
|
|
2259
|
+
}
|
|
2260
|
+
async function applyPresetFromData(presetRef, data, inputs, opts) {
|
|
2261
|
+
const parseResult = parsePreset(data);
|
|
2262
|
+
if (!parseResult.ok || !parseResult.preset) {
|
|
2263
|
+
return {
|
|
2264
|
+
instances: [],
|
|
2265
|
+
assignments: [],
|
|
2266
|
+
errors: parseResult.errors.map((e) => `Parse: ${e}`)
|
|
2267
|
+
};
|
|
2334
2268
|
}
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2269
|
+
return applyPreset(presetRef, parseResult.preset, inputs, opts);
|
|
2270
|
+
}
|
|
2271
|
+
async function applyItemImpl(presetRef, item, inputs, opts, depth, pinPolicy, itemResults) {
|
|
2272
|
+
const { library } = opts;
|
|
2273
|
+
const resolved = await resolveItem(item, library, opts.knownPresetRefs);
|
|
2274
|
+
switch (resolved.kind) {
|
|
2275
|
+
case "cross-item":
|
|
2276
|
+
return applyCrossItem(resolved, itemResults);
|
|
2277
|
+
case "library-instance":
|
|
2278
|
+
return applyLibraryInstance(resolved, opts, pinPolicy);
|
|
2279
|
+
case "nested-preset":
|
|
2280
|
+
return applyNestedPreset(resolved, item, inputs, opts, depth);
|
|
2281
|
+
case "asset":
|
|
2282
|
+
return applyAssetItem(presetRef, resolved, item, inputs, opts, pinPolicy);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
function applyCrossItem(resolved, itemResults) {
|
|
2286
|
+
const target = itemResults.get(resolved.targetId);
|
|
2287
|
+
if (!target) {
|
|
2288
|
+
return {
|
|
2289
|
+
instances: [],
|
|
2290
|
+
assignments: [],
|
|
2291
|
+
errors: [`Cross-item ref "#${resolved.targetId}" not yet resolved (items must be ordered)`]
|
|
2292
|
+
};
|
|
2342
2293
|
}
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
`[skaile/catalog] Background refresh failed for ${key}: ${err instanceof Error ? err.message : String(err)}
|
|
2353
|
-
`
|
|
2354
|
-
);
|
|
2355
|
-
}
|
|
2356
|
-
} finally {
|
|
2357
|
-
this.inFlight.delete(key);
|
|
2358
|
-
}
|
|
2359
|
-
})();
|
|
2360
|
-
this.inFlight.set(key, p);
|
|
2294
|
+
return { instances: [target], assignments: [], errors: [] };
|
|
2295
|
+
}
|
|
2296
|
+
async function applyLibraryInstance(resolved, opts, pinPolicy) {
|
|
2297
|
+
if (!resolved.instance) {
|
|
2298
|
+
return {
|
|
2299
|
+
instances: [],
|
|
2300
|
+
assignments: [],
|
|
2301
|
+
errors: [`Library instance "${resolved.instanceId}" not found`]
|
|
2302
|
+
};
|
|
2361
2303
|
}
|
|
2362
|
-
|
|
2363
|
-
const
|
|
2364
|
-
|
|
2365
|
-
|
|
2304
|
+
try {
|
|
2305
|
+
const asgn = await opts.library.assign(opts.workspaceId, resolved.instanceId, pinPolicy);
|
|
2306
|
+
return { instances: [resolved.instance], assignments: [asgn], errors: [] };
|
|
2307
|
+
} catch {
|
|
2308
|
+
return { instances: [resolved.instance], assignments: [], errors: [] };
|
|
2366
2309
|
}
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
return
|
|
2310
|
+
}
|
|
2311
|
+
async function applyNestedPreset(resolved, item, inputs, opts, depth) {
|
|
2312
|
+
if (!opts.loadPreset) {
|
|
2313
|
+
return {
|
|
2314
|
+
instances: [],
|
|
2315
|
+
assignments: [],
|
|
2316
|
+
errors: [`Cannot resolve nested preset "${resolved.presetRef}" \u2014 no loader provided`]
|
|
2317
|
+
};
|
|
2371
2318
|
}
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2319
|
+
const nestedData = await opts.loadPreset(resolved.presetRef);
|
|
2320
|
+
if (!nestedData) {
|
|
2321
|
+
return {
|
|
2322
|
+
instances: [],
|
|
2323
|
+
assignments: [],
|
|
2324
|
+
errors: [`Nested preset "${resolved.presetRef}" not found`]
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
const parseResult = parsePreset(nestedData);
|
|
2328
|
+
if (!parseResult.ok || !parseResult.preset) {
|
|
2329
|
+
return { instances: [], assignments: [], errors: parseResult.errors };
|
|
2330
|
+
}
|
|
2331
|
+
const nestedInputs = {
|
|
2332
|
+
...item.inputs ?? {},
|
|
2333
|
+
...inputs
|
|
2334
|
+
};
|
|
2335
|
+
const nestedResult = await applyPreset(resolved.presetRef, parseResult.preset, nestedInputs, {
|
|
2336
|
+
...opts,
|
|
2337
|
+
_depth: depth + 1
|
|
2338
|
+
});
|
|
2339
|
+
return {
|
|
2340
|
+
instances: nestedResult.instances,
|
|
2341
|
+
assignments: nestedResult.assignments,
|
|
2342
|
+
errors: nestedResult.errors
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
function validateAssetPlaceholders(item, inputs) {
|
|
2346
|
+
const itemInputs = { ...inputs };
|
|
2347
|
+
let validatedValues = {};
|
|
2348
|
+
const secretValues = {};
|
|
2349
|
+
if (item.placeholders && item.placeholders.length > 0) {
|
|
2350
|
+
const nonSecretPhs = item.placeholders.filter((ph) => !isSecretPlaceholder(ph));
|
|
2351
|
+
const secretPhs = item.placeholders.filter((ph) => isSecretPlaceholder(ph));
|
|
2352
|
+
const validation = validateAllPlaceholders(nonSecretPhs, itemInputs);
|
|
2353
|
+
if (!validation.valid) {
|
|
2354
|
+
return { validatedValues: {}, secretValues: {}, errors: validation.errors };
|
|
2355
|
+
}
|
|
2356
|
+
validatedValues = validation.values;
|
|
2357
|
+
const secretValidation = validateAllPlaceholders(secretPhs, itemInputs);
|
|
2358
|
+
if (!secretValidation.valid) {
|
|
2359
|
+
return { validatedValues: {}, secretValues: {}, errors: secretValidation.errors };
|
|
2360
|
+
}
|
|
2361
|
+
for (const [key, value] of Object.entries(secretValidation.values)) {
|
|
2362
|
+
secretValues[key] = String(value);
|
|
2385
2363
|
}
|
|
2386
|
-
const data = await parseTrpcGet(res);
|
|
2387
|
-
if (data === null || data === void 0) return null;
|
|
2388
|
-
return normalizeCatalogAsset(data);
|
|
2389
2364
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2365
|
+
return { validatedValues, secretValues, errors: [] };
|
|
2366
|
+
}
|
|
2367
|
+
async function applyAssetItem(presetRef, resolved, item, inputs, opts, pinPolicy) {
|
|
2368
|
+
const { library } = opts;
|
|
2369
|
+
const validated = validateAssetPlaceholders(item, inputs);
|
|
2370
|
+
if (validated.errors.length > 0) {
|
|
2371
|
+
return { instances: [], assignments: [], errors: validated.errors };
|
|
2372
|
+
}
|
|
2373
|
+
const credentialRefPrefix = `preset/${presetRef}/${item.id ?? resolved.defRef}`;
|
|
2374
|
+
const context = {
|
|
2375
|
+
inputs: validated.validatedValues,
|
|
2376
|
+
secrets: validated.secretValues
|
|
2377
|
+
};
|
|
2378
|
+
const interpolated = interpolate(
|
|
2379
|
+
item.materialize,
|
|
2380
|
+
item.config,
|
|
2381
|
+
context,
|
|
2382
|
+
credentialRefPrefix
|
|
2383
|
+
);
|
|
2384
|
+
if (opts.secretsRouter && interpolated.secretsToStore.length > 0) {
|
|
2385
|
+
try {
|
|
2386
|
+
await storeSecrets(opts.secretsRouter, interpolated.secretsToStore);
|
|
2387
|
+
} catch (err) {
|
|
2388
|
+
return {
|
|
2389
|
+
instances: [],
|
|
2390
|
+
assignments: [],
|
|
2391
|
+
errors: [
|
|
2392
|
+
`Secret storage failed for "${item.ref}": ${err instanceof Error ? err.message : String(err)}`
|
|
2393
|
+
]
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
try {
|
|
2398
|
+
const instance = await library.createInstance({
|
|
2399
|
+
defRef: resolved.defRef,
|
|
2400
|
+
defPin: pinPolicy,
|
|
2401
|
+
config: interpolated.config,
|
|
2402
|
+
credentialRef: interpolated.credentialRef,
|
|
2403
|
+
createdBy: opts.createdBy ?? `preset:${presetRef}`
|
|
2397
2404
|
});
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
);
|
|
2405
|
+
const assignments2 = [];
|
|
2406
|
+
try {
|
|
2407
|
+
const asgn = await library.assign(opts.workspaceId, instance.id, pinPolicy);
|
|
2408
|
+
assignments2.push(asgn);
|
|
2409
|
+
} catch {
|
|
2404
2410
|
}
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2411
|
+
return { instances: [instance], assignments: assignments2, errors: [] };
|
|
2412
|
+
} catch (err) {
|
|
2413
|
+
return {
|
|
2414
|
+
instances: [],
|
|
2415
|
+
assignments: [],
|
|
2416
|
+
errors: [
|
|
2417
|
+
`Instance creation failed for "${item.ref}": ${err instanceof Error ? err.message : String(err)}`
|
|
2418
|
+
]
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// library/src/preset/upgrade.ts
|
|
2424
|
+
function detectChanges(oldManifest, newManifest) {
|
|
2425
|
+
const oldItems = indexItems(oldManifest.items);
|
|
2426
|
+
const newItems = indexItems(newManifest.items);
|
|
2427
|
+
const addedItems = [];
|
|
2428
|
+
const removedItems = [];
|
|
2429
|
+
const changedRefItems = [];
|
|
2430
|
+
const configChangedItems = [];
|
|
2431
|
+
for (const [key, oldItem] of oldItems) {
|
|
2432
|
+
if (!newItems.has(key)) {
|
|
2433
|
+
removedItems.push(oldItem);
|
|
2412
2434
|
}
|
|
2413
|
-
return data.map(normalizeCatalogAsset);
|
|
2414
2435
|
}
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2436
|
+
for (const [key, newItem] of newItems) {
|
|
2437
|
+
if (!oldItems.has(key)) {
|
|
2438
|
+
addedItems.push(newItem);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
for (const [key, newItem] of newItems) {
|
|
2442
|
+
const oldItem = oldItems.get(key);
|
|
2443
|
+
if (!oldItem) continue;
|
|
2444
|
+
if (oldItem.ref !== newItem.ref) {
|
|
2445
|
+
changedRefItems.push({ old: oldItem, new: newItem });
|
|
2446
|
+
} else if (!deepEqual(oldItem.placeholders, newItem.placeholders) || !deepEqual(oldItem.materialize, newItem.materialize)) {
|
|
2447
|
+
changedRefItems.push({ old: oldItem, new: newItem });
|
|
2448
|
+
} else if (!deepEqual(oldItem.config, newItem.config)) {
|
|
2449
|
+
configChangedItems.push({ old: oldItem, new: newItem });
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
const isStructural = addedItems.length > 0 || removedItems.length > 0 || changedRefItems.length > 0;
|
|
2453
|
+
const kind = isStructural ? "structural" : "non-structural";
|
|
2454
|
+
const summaryParts = [];
|
|
2455
|
+
if (addedItems.length > 0) summaryParts.push(`+${addedItems.length} items`);
|
|
2456
|
+
if (removedItems.length > 0) summaryParts.push(`-${removedItems.length} items`);
|
|
2457
|
+
if (changedRefItems.length > 0) summaryParts.push(`${changedRefItems.length} ref changes`);
|
|
2458
|
+
if (configChangedItems.length > 0)
|
|
2459
|
+
summaryParts.push(`${configChangedItems.length} config changes`);
|
|
2460
|
+
const summary = summaryParts.length > 0 ? `${kind}: ${summaryParts.join(", ")}` : "no changes";
|
|
2461
|
+
return {
|
|
2462
|
+
kind,
|
|
2463
|
+
addedItems,
|
|
2464
|
+
removedItems,
|
|
2465
|
+
changedRefItems,
|
|
2466
|
+
configChangedItems,
|
|
2467
|
+
summary
|
|
2468
|
+
};
|
|
2419
2469
|
}
|
|
2420
|
-
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
body = text2.length > 0 ? JSON.parse(text2) : null;
|
|
2425
|
-
} catch {
|
|
2426
|
-
throw new CatalogHttpError(
|
|
2427
|
-
`tRPC response is not valid JSON: ${text2.slice(0, 200)}`,
|
|
2428
|
-
res.status
|
|
2429
|
-
);
|
|
2470
|
+
function evaluateUpgrade(oldManifest, newManifest) {
|
|
2471
|
+
const diff = detectChanges(oldManifest, newManifest);
|
|
2472
|
+
if (diff.kind === "non-structural") {
|
|
2473
|
+
return { autoUpgradable: true, diff };
|
|
2430
2474
|
}
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
`
|
|
2435
|
-
res.status
|
|
2475
|
+
const requiredActions = [];
|
|
2476
|
+
if (diff.addedItems.length > 0) {
|
|
2477
|
+
requiredActions.push(
|
|
2478
|
+
`Create ${diff.addedItems.length} new instance(s): ${diff.addedItems.map((i) => i.ref).join(", ")}`
|
|
2436
2479
|
);
|
|
2437
2480
|
}
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2481
|
+
if (diff.removedItems.length > 0) {
|
|
2482
|
+
requiredActions.push(
|
|
2483
|
+
`Remove ${diff.removedItems.length} instance(s): ${diff.removedItems.map((i) => i.ref).join(", ")}`
|
|
2484
|
+
);
|
|
2441
2485
|
}
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
return data.json;
|
|
2486
|
+
if (diff.changedRefItems.length > 0) {
|
|
2487
|
+
requiredActions.push(`Replace ${diff.changedRefItems.length} instance(s) with new refs`);
|
|
2445
2488
|
}
|
|
2446
|
-
return
|
|
2489
|
+
return { autoUpgradable: false, diff, requiredActions };
|
|
2447
2490
|
}
|
|
2448
|
-
function
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
const description = pickString(r, "description");
|
|
2470
|
-
if (description) asset.description = description;
|
|
2471
|
-
const license = pickString(r, "license");
|
|
2472
|
-
if (license) asset.license = license;
|
|
2473
|
-
const category = pickString(r, "category");
|
|
2474
|
-
if (category) asset.category = category;
|
|
2475
|
-
const repo = pickString(r, "repo");
|
|
2476
|
-
if (repo) asset.repo = repo;
|
|
2477
|
-
return asset;
|
|
2491
|
+
async function applyNonStructuralUpgrade(newManifest, existingInstances, library) {
|
|
2492
|
+
let updated = 0;
|
|
2493
|
+
const errors = [];
|
|
2494
|
+
for (const item of newManifest.items) {
|
|
2495
|
+
const key = itemKey(item);
|
|
2496
|
+
const instanceId = existingInstances.get(key);
|
|
2497
|
+
if (!instanceId) continue;
|
|
2498
|
+
if (item.config) {
|
|
2499
|
+
try {
|
|
2500
|
+
await library.updateInstance(instanceId, {
|
|
2501
|
+
config: item.config
|
|
2502
|
+
});
|
|
2503
|
+
updated++;
|
|
2504
|
+
} catch (err) {
|
|
2505
|
+
errors.push(
|
|
2506
|
+
`Failed to update instance "${instanceId}": ${err instanceof Error ? err.message : String(err)}`
|
|
2507
|
+
);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
return { updated, errors };
|
|
2478
2512
|
}
|
|
2479
|
-
function
|
|
2480
|
-
|
|
2481
|
-
return typeof v === "string" ? v : void 0;
|
|
2513
|
+
function itemKey(item) {
|
|
2514
|
+
return item.id ?? item.ref;
|
|
2482
2515
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2516
|
+
function indexItems(items) {
|
|
2517
|
+
const map = /* @__PURE__ */ new Map();
|
|
2518
|
+
for (const item of items) {
|
|
2519
|
+
map.set(itemKey(item), item);
|
|
2487
2520
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2521
|
+
return map;
|
|
2522
|
+
}
|
|
2523
|
+
function deepEqual(a, b) {
|
|
2524
|
+
if (a === b) return true;
|
|
2525
|
+
if (a === void 0 && b === void 0) return true;
|
|
2526
|
+
if (a === null && b === null) return true;
|
|
2527
|
+
if (a === void 0 || b === void 0) return false;
|
|
2528
|
+
if (a === null || b === null) return false;
|
|
2529
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
2530
|
+
}
|
|
2531
|
+
function defaultCacheDir() {
|
|
2532
|
+
return path5.join(os.homedir(), ".skaile", "catalog-cache");
|
|
2533
|
+
}
|
|
2534
|
+
var INDEX_FILE = "index.json";
|
|
2535
|
+
var TARBALL_DIR = "tarballs";
|
|
2536
|
+
var CatalogCache = class {
|
|
2537
|
+
dir;
|
|
2538
|
+
ttlMs;
|
|
2539
|
+
nowFn;
|
|
2540
|
+
index = null;
|
|
2506
2541
|
constructor(opts) {
|
|
2507
|
-
this.
|
|
2508
|
-
this.
|
|
2509
|
-
this.
|
|
2510
|
-
this.
|
|
2511
|
-
this.silentBackgroundErrors = opts.silentBackgroundErrors ?? false;
|
|
2512
|
-
this.cache = opts.cache ?? new CatalogCache({
|
|
2513
|
-
dir: opts.cacheDir,
|
|
2514
|
-
ttlMs: this.cacheTtlMs,
|
|
2515
|
-
now: opts.now
|
|
2516
|
-
});
|
|
2542
|
+
this.dir = opts.dir ?? defaultCacheDir();
|
|
2543
|
+
this.ttlMs = opts.ttlMs;
|
|
2544
|
+
this.nowFn = opts.now ?? Date.now;
|
|
2545
|
+
this.ensureDir();
|
|
2517
2546
|
}
|
|
2518
|
-
// ──
|
|
2547
|
+
// ── public: resolve cache ───────────────────────────────────────────────
|
|
2519
2548
|
/**
|
|
2520
|
-
*
|
|
2521
|
-
*
|
|
2522
|
-
* Cache: hit returns immediately. Miss fetches `/api/catalog/get?ref=<ref>`. Stale
|
|
2523
|
-
* entry serves the cached value and triggers a background refresh.
|
|
2549
|
+
* Return a fresh resolve-cache entry for `ref`, or `null` if absent / stale.
|
|
2524
2550
|
*
|
|
2525
|
-
*
|
|
2551
|
+
* When `ttlMs === 0` (air-gapped), all entries are considered fresh
|
|
2552
|
+
* regardless of `fetchedAt`.
|
|
2526
2553
|
*/
|
|
2527
|
-
|
|
2528
|
-
const
|
|
2529
|
-
|
|
2530
|
-
if (
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
}
|
|
2535
|
-
const stale = this.cache.getResolveStale(ref);
|
|
2536
|
-
if (stale) {
|
|
2537
|
-
this.scheduleBackground(`resolve:${ref}`, () => this.fetchAndStoreResolve(ref));
|
|
2538
|
-
return stale.value;
|
|
2539
|
-
}
|
|
2540
|
-
return this.fetchAndStoreResolve(ref);
|
|
2554
|
+
getResolve(ref) {
|
|
2555
|
+
const idx = this.loadIndex();
|
|
2556
|
+
const entry = idx.resolve[ref];
|
|
2557
|
+
if (!entry) return null;
|
|
2558
|
+
if (this.ttlMs === 0) return entry;
|
|
2559
|
+
if (this.nowFn() - entry.fetchedAt > this.ttlMs) return null;
|
|
2560
|
+
return entry;
|
|
2541
2561
|
}
|
|
2542
2562
|
/**
|
|
2543
|
-
*
|
|
2544
|
-
*
|
|
2545
|
-
* Cache: hit returns immediately. Miss fetches `/api/catalog/list`. Stale
|
|
2546
|
-
* entry serves the cached array and triggers a background refresh.
|
|
2547
|
-
*
|
|
2548
|
-
* Air-gapped: throws {@link OfflineError} on cache miss.
|
|
2549
|
-
*
|
|
2550
|
-
* Note: `filter.prefix` is not supported by the forge-store REST API and is silently ignored.
|
|
2563
|
+
* Return a stale resolve-cache entry regardless of TTL — for stale-while-revalidate.
|
|
2551
2564
|
*/
|
|
2552
|
-
|
|
2553
|
-
const
|
|
2554
|
-
|
|
2555
|
-
if (this.cache.airGapped) {
|
|
2556
|
-
const stale2 = this.cache.getListStale(filter);
|
|
2557
|
-
if (stale2) return stale2.items;
|
|
2558
|
-
throw new OfflineError("Catalog list not in cache");
|
|
2559
|
-
}
|
|
2560
|
-
const stale = this.cache.getListStale(filter);
|
|
2561
|
-
if (stale) {
|
|
2562
|
-
const filterKey2 = JSON.stringify(filter ?? null);
|
|
2563
|
-
this.scheduleBackground(`list:${filterKey2}`, () => this.fetchAndStoreList(filter));
|
|
2564
|
-
return stale.items;
|
|
2565
|
-
}
|
|
2566
|
-
return this.fetchAndStoreList(filter);
|
|
2565
|
+
getResolveStale(ref) {
|
|
2566
|
+
const idx = this.loadIndex();
|
|
2567
|
+
return idx.resolve[ref] ?? null;
|
|
2567
2568
|
}
|
|
2568
|
-
/**
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
* 2. `GET <manifest.url>` following redirects → raw bytes
|
|
2574
|
-
* 3. Verify SHA256 client-side — hard fail on mismatch
|
|
2575
|
-
* 4. Write to cache on success
|
|
2576
|
-
*
|
|
2577
|
-
* Cache: tarballs are content-addressed and cached forever once written.
|
|
2578
|
-
*
|
|
2579
|
-
* Air-gapped: throws {@link OfflineError} on cache miss.
|
|
2580
|
-
*
|
|
2581
|
-
* @throws {@link TarballHashMismatchError} when the downloaded SHA256 does not match.
|
|
2582
|
-
*/
|
|
2583
|
-
async fetchTarball(_ref, sha256) {
|
|
2584
|
-
const cached = this.cache.readTarball(sha256);
|
|
2585
|
-
if (cached) {
|
|
2586
|
-
const actual2 = sha256Hex(cached);
|
|
2587
|
-
if (actual2 !== sha256) {
|
|
2588
|
-
throw new TarballHashMismatchError(sha256, actual2);
|
|
2589
|
-
}
|
|
2590
|
-
return cached;
|
|
2591
|
-
}
|
|
2592
|
-
if (this.cache.airGapped) {
|
|
2593
|
-
throw new OfflineError(`Tarball '${sha256}' not in cache`);
|
|
2594
|
-
}
|
|
2595
|
-
const manifestUrl = `${this.baseUrl}/api/tarball/manifest?sha256=${encodeURIComponent(sha256)}`;
|
|
2596
|
-
const manifestRes = await this.httpClient(manifestUrl, {
|
|
2597
|
-
method: "GET",
|
|
2598
|
-
headers: { Accept: "application/json" }
|
|
2599
|
-
});
|
|
2600
|
-
if (!manifestRes.ok) {
|
|
2601
|
-
throw new CatalogHttpError(
|
|
2602
|
-
`Failed to fetch tarball manifest for ${sha256}: ${manifestRes.status} ${manifestRes.statusText}`,
|
|
2603
|
-
manifestRes.status,
|
|
2604
|
-
manifestUrl
|
|
2605
|
-
);
|
|
2606
|
-
}
|
|
2607
|
-
const manifestText = await manifestRes.text();
|
|
2608
|
-
let manifest;
|
|
2609
|
-
try {
|
|
2610
|
-
manifest = JSON.parse(manifestText);
|
|
2611
|
-
} catch {
|
|
2612
|
-
throw new CatalogHttpError(
|
|
2613
|
-
`Tarball manifest response is not valid JSON: ${manifestText.slice(0, 200)}`,
|
|
2614
|
-
manifestRes.status,
|
|
2615
|
-
manifestUrl
|
|
2616
|
-
);
|
|
2617
|
-
}
|
|
2618
|
-
const downloadUrl = manifest && typeof manifest === "object" && "url" in manifest ? manifest.url : void 0;
|
|
2619
|
-
if (typeof downloadUrl !== "string") {
|
|
2620
|
-
throw new CatalogHttpError(
|
|
2621
|
-
`Tarball manifest missing 'url' field`,
|
|
2622
|
-
manifestRes.status,
|
|
2623
|
-
manifestUrl
|
|
2624
|
-
);
|
|
2625
|
-
}
|
|
2626
|
-
const tarballRes = await this.httpClient(downloadUrl, { method: "GET", redirect: "follow" });
|
|
2627
|
-
if (!tarballRes.ok) {
|
|
2628
|
-
throw new CatalogHttpError(
|
|
2629
|
-
`Failed to fetch tarball ${sha256}: ${tarballRes.status} ${tarballRes.statusText}`,
|
|
2630
|
-
tarballRes.status,
|
|
2631
|
-
downloadUrl
|
|
2632
|
-
);
|
|
2633
|
-
}
|
|
2634
|
-
const buf = new Uint8Array(await tarballRes.arrayBuffer());
|
|
2635
|
-
const actual = sha256Hex(buf);
|
|
2636
|
-
if (actual !== sha256) {
|
|
2637
|
-
throw new TarballHashMismatchError(sha256, actual);
|
|
2638
|
-
}
|
|
2639
|
-
this.cache.writeTarball(sha256, buf);
|
|
2640
|
-
return buf;
|
|
2569
|
+
/** Persist a resolve-cache entry (use `null` to negatively cache misses). */
|
|
2570
|
+
setResolve(ref, value) {
|
|
2571
|
+
const idx = this.loadIndex();
|
|
2572
|
+
idx.resolve[ref] = { fetchedAt: this.nowFn(), value };
|
|
2573
|
+
this.writeIndex(idx);
|
|
2641
2574
|
}
|
|
2575
|
+
// ── public: list cache ──────────────────────────────────────────────────
|
|
2642
2576
|
/**
|
|
2643
|
-
*
|
|
2644
|
-
* API has no version-list route, so this derives versions from `listAssets`
|
|
2645
|
-
* (filtered to the publisher) and keeps only the matching kind+name. Reuses
|
|
2646
|
-
* the cached list path, so repeated calls are cheap.
|
|
2577
|
+
* Return a fresh list-cache entry for `filter`, or `null` if absent / stale.
|
|
2647
2578
|
*/
|
|
2648
|
-
|
|
2649
|
-
const
|
|
2650
|
-
const
|
|
2651
|
-
const
|
|
2652
|
-
return
|
|
2653
|
-
|
|
2654
|
-
|
|
2579
|
+
getList(filter) {
|
|
2580
|
+
const key = filterKey(filter);
|
|
2581
|
+
const idx = this.loadIndex();
|
|
2582
|
+
const entry = idx.list[key];
|
|
2583
|
+
if (!entry) return null;
|
|
2584
|
+
if (this.ttlMs === 0) return entry;
|
|
2585
|
+
if (this.nowFn() - entry.fetchedAt > this.ttlMs) return null;
|
|
2586
|
+
return entry;
|
|
2587
|
+
}
|
|
2588
|
+
/** Return a stale list-cache entry regardless of TTL — for stale-while-revalidate. */
|
|
2589
|
+
getListStale(filter) {
|
|
2590
|
+
const key = filterKey(filter);
|
|
2591
|
+
const idx = this.loadIndex();
|
|
2592
|
+
return idx.list[key] ?? null;
|
|
2593
|
+
}
|
|
2594
|
+
/** Persist a list-cache entry. */
|
|
2595
|
+
setList(filter, items) {
|
|
2596
|
+
const key = filterKey(filter);
|
|
2597
|
+
const idx = this.loadIndex();
|
|
2598
|
+
idx.list[key] = { fetchedAt: this.nowFn(), items };
|
|
2599
|
+
this.writeIndex(idx);
|
|
2600
|
+
}
|
|
2601
|
+
// ── public: tarball cache ───────────────────────────────────────────────
|
|
2602
|
+
/** Absolute path where a tarball with `sha256` would be cached. */
|
|
2603
|
+
tarballPath(sha256) {
|
|
2604
|
+
return path5.join(this.dir, TARBALL_DIR, `${sha256}.tgz`);
|
|
2605
|
+
}
|
|
2606
|
+
/** Read a cached tarball from disk if present, otherwise `null`. */
|
|
2607
|
+
readTarball(sha256) {
|
|
2608
|
+
const p = this.tarballPath(sha256);
|
|
2609
|
+
if (!fs10.existsSync(p)) return null;
|
|
2610
|
+
return fs10.readFileSync(p);
|
|
2611
|
+
}
|
|
2612
|
+
/** Persist a tarball blob to disk (atomic write). */
|
|
2613
|
+
writeTarball(sha256, bytes) {
|
|
2614
|
+
const dir = path5.join(this.dir, TARBALL_DIR);
|
|
2615
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
2616
|
+
const finalPath = this.tarballPath(sha256);
|
|
2617
|
+
const tmpPath = `${finalPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
2618
|
+
fs10.writeFileSync(tmpPath, bytes);
|
|
2619
|
+
fs10.renameSync(tmpPath, finalPath);
|
|
2655
2620
|
}
|
|
2621
|
+
// ── public: invalidation ────────────────────────────────────────────────
|
|
2656
2622
|
/**
|
|
2657
|
-
*
|
|
2623
|
+
* Wipe all cached metadata (resolve + list). Tarballs are content-addressed
|
|
2624
|
+
* and remain valid — they are not invalidated.
|
|
2625
|
+
*
|
|
2626
|
+
* Called by `skaile update`.
|
|
2658
2627
|
*/
|
|
2659
|
-
|
|
2660
|
-
|
|
2628
|
+
invalidateAll() {
|
|
2629
|
+
const idx = { version: 1, resolve: {}, list: {} };
|
|
2630
|
+
this.index = idx;
|
|
2631
|
+
this.writeIndex(idx);
|
|
2661
2632
|
}
|
|
2662
|
-
// ── extension API ───────────────────────────────────────────────────────
|
|
2663
2633
|
/**
|
|
2664
|
-
*
|
|
2665
|
-
* re-fetches `listAssets()` (no filter) to pre-warm the canonical query.
|
|
2666
|
-
*
|
|
2667
|
-
* Driven by `skaile update`. Tarballs (content-addressed) are not touched.
|
|
2668
|
-
*
|
|
2669
|
-
* In air-gapped mode this method **does** make a network call (bypassing
|
|
2670
|
-
* the TTL=0 gate); it is the only way to repopulate the cache.
|
|
2634
|
+
* Wipe all cached metadata **and** delete tarball blobs. Used for full reset.
|
|
2671
2635
|
*/
|
|
2672
|
-
|
|
2673
|
-
this.
|
|
2674
|
-
const
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
this.cache.setResolve(item.id, item);
|
|
2636
|
+
invalidateEverything() {
|
|
2637
|
+
this.invalidateAll();
|
|
2638
|
+
const tarballDir = path5.join(this.dir, TARBALL_DIR);
|
|
2639
|
+
if (fs10.existsSync(tarballDir)) {
|
|
2640
|
+
fs10.rmSync(tarballDir, { recursive: true, force: true });
|
|
2678
2641
|
}
|
|
2679
|
-
return { assetsCached: items.length };
|
|
2680
2642
|
}
|
|
2681
|
-
/**
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
*/
|
|
2685
|
-
async waitForBackground() {
|
|
2686
|
-
const promises = Array.from(this.inFlight.values());
|
|
2687
|
-
await Promise.allSettled(promises);
|
|
2643
|
+
/** Return whether `ttlMs` is zero (air-gapped mode). */
|
|
2644
|
+
get airGapped() {
|
|
2645
|
+
return this.ttlMs === 0;
|
|
2688
2646
|
}
|
|
2689
2647
|
// ── private ─────────────────────────────────────────────────────────────
|
|
2690
|
-
|
|
2691
|
-
if (this.
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
await fn();
|
|
2695
|
-
} catch (err) {
|
|
2696
|
-
if (!this.silentBackgroundErrors) {
|
|
2697
|
-
process.stderr.write(
|
|
2698
|
-
`[skaile/catalog] Background refresh failed for ${key}: ${err instanceof Error ? err.message : String(err)}
|
|
2699
|
-
`
|
|
2700
|
-
);
|
|
2701
|
-
}
|
|
2702
|
-
} finally {
|
|
2703
|
-
this.inFlight.delete(key);
|
|
2704
|
-
}
|
|
2705
|
-
})();
|
|
2706
|
-
this.inFlight.set(key, p);
|
|
2707
|
-
}
|
|
2708
|
-
async fetchAndStoreResolve(ref) {
|
|
2709
|
-
const value = await this.fetchResolveNetwork(ref);
|
|
2710
|
-
this.cache.setResolve(ref, value);
|
|
2711
|
-
return value;
|
|
2712
|
-
}
|
|
2713
|
-
async fetchAndStoreList(filter) {
|
|
2714
|
-
const items = await this.fetchListNetwork(filter);
|
|
2715
|
-
this.cache.setList(filter, items);
|
|
2716
|
-
return items;
|
|
2648
|
+
ensureDir() {
|
|
2649
|
+
if (!fs10.existsSync(this.dir)) fs10.mkdirSync(this.dir, { recursive: true });
|
|
2650
|
+
const tarballDir = path5.join(this.dir, TARBALL_DIR);
|
|
2651
|
+
if (!fs10.existsSync(tarballDir)) fs10.mkdirSync(tarballDir, { recursive: true });
|
|
2717
2652
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
const url = `${this.baseUrl}/api/catalog/get?ref=${encodeURIComponent(ref)}`;
|
|
2721
|
-
const res = await this.httpClient(url, {
|
|
2722
|
-
method: "GET",
|
|
2723
|
-
headers: { Accept: "application/json" }
|
|
2724
|
-
});
|
|
2725
|
-
if (!res.ok) {
|
|
2726
|
-
throw new CatalogHttpError(
|
|
2727
|
-
`catalog/get ${ref} failed: ${res.status} ${res.statusText}`,
|
|
2728
|
-
res.status,
|
|
2729
|
-
url
|
|
2730
|
-
);
|
|
2731
|
-
}
|
|
2732
|
-
const data = await parseRestResponse(res, url);
|
|
2733
|
-
if (data === null || data === void 0) return null;
|
|
2734
|
-
return normalizeRestAsset(data);
|
|
2653
|
+
indexPath() {
|
|
2654
|
+
return path5.join(this.dir, INDEX_FILE);
|
|
2735
2655
|
}
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
const
|
|
2739
|
-
if (
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
const url = `${this.baseUrl}/api/catalog/list${qs ? `?${qs}` : ""}`;
|
|
2743
|
-
const res = await this.httpClient(url, {
|
|
2744
|
-
method: "GET",
|
|
2745
|
-
headers: { Accept: "application/json" }
|
|
2746
|
-
});
|
|
2747
|
-
if (!res.ok) {
|
|
2748
|
-
throw new CatalogHttpError(
|
|
2749
|
-
`catalog/list failed: ${res.status} ${res.statusText}`,
|
|
2750
|
-
res.status,
|
|
2751
|
-
url
|
|
2752
|
-
);
|
|
2753
|
-
}
|
|
2754
|
-
const data = await parseRestResponse(res, url);
|
|
2755
|
-
if (!Array.isArray(data)) {
|
|
2756
|
-
throw new CatalogHttpError(
|
|
2757
|
-
`catalog/list returned non-array payload (got ${typeof data})`,
|
|
2758
|
-
200,
|
|
2759
|
-
url
|
|
2760
|
-
);
|
|
2656
|
+
loadIndex() {
|
|
2657
|
+
if (this.index) return this.index;
|
|
2658
|
+
const p = this.indexPath();
|
|
2659
|
+
if (!fs10.existsSync(p)) {
|
|
2660
|
+
this.index = { version: 1, resolve: {}, list: {} };
|
|
2661
|
+
return this.index;
|
|
2761
2662
|
}
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
res.status,
|
|
2774
|
-
url
|
|
2775
|
-
);
|
|
2776
|
-
}
|
|
2777
|
-
}
|
|
2778
|
-
function normalizeRestAsset(raw) {
|
|
2779
|
-
if (!raw || typeof raw !== "object") {
|
|
2780
|
-
throw new CatalogHttpError("Catalog REST response item is not an object", 200);
|
|
2781
|
-
}
|
|
2782
|
-
const r = raw;
|
|
2783
|
-
const publisher = pickString2(r, "publisherNamespace") ?? pickString2(r, "publisher") ?? "";
|
|
2784
|
-
const name = pickString2(r, "name") ?? "";
|
|
2785
|
-
const version = pickString2(r, "version") ?? "";
|
|
2786
|
-
const kind = pickString2(r, "kind") ?? "";
|
|
2787
|
-
const id = `${publisher}/${name}@${version}`;
|
|
2788
|
-
const requiresParsed = tryParseJson(pickString2(r, "requiresJson"));
|
|
2789
|
-
const filesParsed = tryParseJson(pickString2(r, "filesJson"));
|
|
2790
|
-
const manifest = { ...requiresParsed, ...filesParsed };
|
|
2791
|
-
const asset = {
|
|
2792
|
-
id,
|
|
2793
|
-
kind,
|
|
2794
|
-
publisher,
|
|
2795
|
-
name,
|
|
2796
|
-
version,
|
|
2797
|
-
manifest
|
|
2798
|
-
};
|
|
2799
|
-
const sha = pickString2(r, "sha256");
|
|
2800
|
-
if (sha) asset.sha256 = sha;
|
|
2801
|
-
const description = pickString2(r, "description");
|
|
2802
|
-
if (description) asset.description = description;
|
|
2803
|
-
const license = pickString2(r, "license");
|
|
2804
|
-
if (license) asset.license = license;
|
|
2805
|
-
const category = pickString2(r, "category");
|
|
2806
|
-
if (category) asset.category = category;
|
|
2807
|
-
return asset;
|
|
2808
|
-
}
|
|
2809
|
-
function pickString2(r, key) {
|
|
2810
|
-
const v = r[key];
|
|
2811
|
-
return typeof v === "string" ? v : void 0;
|
|
2812
|
-
}
|
|
2813
|
-
function tryParseJson(s) {
|
|
2814
|
-
if (!s) return {};
|
|
2815
|
-
try {
|
|
2816
|
-
const parsed = JSON.parse(s);
|
|
2817
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2818
|
-
return parsed;
|
|
2663
|
+
try {
|
|
2664
|
+
const raw = JSON.parse(fs10.readFileSync(p, "utf-8"));
|
|
2665
|
+
if (raw && typeof raw === "object" && raw.version === 1) {
|
|
2666
|
+
this.index = {
|
|
2667
|
+
version: 1,
|
|
2668
|
+
resolve: raw.resolve ?? {},
|
|
2669
|
+
list: raw.list ?? {}
|
|
2670
|
+
};
|
|
2671
|
+
return this.index;
|
|
2672
|
+
}
|
|
2673
|
+
} catch {
|
|
2819
2674
|
}
|
|
2820
|
-
|
|
2675
|
+
this.index = { version: 1, resolve: {}, list: {} };
|
|
2676
|
+
return this.index;
|
|
2821
2677
|
}
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2678
|
+
writeIndex(idx) {
|
|
2679
|
+
this.index = idx;
|
|
2680
|
+
const p = this.indexPath();
|
|
2681
|
+
const tmp = `${p}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
2682
|
+
fs10.writeFileSync(tmp, JSON.stringify(idx, null, 2), "utf-8");
|
|
2683
|
+
fs10.renameSync(tmp, p);
|
|
2828
2684
|
}
|
|
2829
|
-
const requestInit = init ?? {};
|
|
2830
|
-
return fetch(url, {
|
|
2831
|
-
...requestInit,
|
|
2832
|
-
signal: requestInit.signal ?? AbortSignal.timeout(CATALOG_REQUEST_TIMEOUT_MS2)
|
|
2833
|
-
});
|
|
2834
2685
|
};
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
});
|
|
2843
|
-
function validateKnowledge(data) {
|
|
2844
|
-
const result = KnowledgeManifestSchema.safeParse(data);
|
|
2845
|
-
if (result.success) {
|
|
2846
|
-
return { ok: true, data: result.data };
|
|
2847
|
-
}
|
|
2848
|
-
return {
|
|
2849
|
-
ok: false,
|
|
2850
|
-
errors: result.error.issues.map((issue) => ({
|
|
2851
|
-
path: issue.path.join("."),
|
|
2852
|
-
message: issue.message
|
|
2853
|
-
}))
|
|
2854
|
-
};
|
|
2855
|
-
}
|
|
2856
|
-
function collectDirRecursive(baseDir, relativeDir) {
|
|
2857
|
-
const full = path4.join(baseDir, relativeDir);
|
|
2858
|
-
if (!fs11.existsSync(full) || !fs11.statSync(full).isDirectory()) return [];
|
|
2859
|
-
const results = [];
|
|
2860
|
-
for (const entry of fs11.readdirSync(full, { withFileTypes: true })) {
|
|
2861
|
-
const rel = path4.posix.join(relativeDir, entry.name);
|
|
2862
|
-
if (entry.isDirectory()) {
|
|
2863
|
-
results.push(...collectDirRecursive(baseDir, rel));
|
|
2864
|
-
} else if (entry.isFile()) {
|
|
2865
|
-
results.push(rel);
|
|
2866
|
-
}
|
|
2686
|
+
function filterKey(filter) {
|
|
2687
|
+
if (!filter) return "__all__";
|
|
2688
|
+
const keys = Object.keys(filter).sort();
|
|
2689
|
+
const norm = {};
|
|
2690
|
+
for (const k of keys) {
|
|
2691
|
+
const v = filter[k];
|
|
2692
|
+
if (v !== void 0) norm[k] = v;
|
|
2867
2693
|
}
|
|
2868
|
-
return
|
|
2869
|
-
}
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
},
|
|
2884
|
-
validateManifest(data) {
|
|
2885
|
-
return validateKnowledge(data);
|
|
2886
|
-
},
|
|
2887
|
-
defaultFileFilter(manifestPath) {
|
|
2888
|
-
const dir = path4.dirname(manifestPath);
|
|
2889
|
-
const manifestName = path4.basename(manifestPath);
|
|
2890
|
-
const files = [manifestName];
|
|
2891
|
-
files.push(...collectDirRecursive(dir, "documents"));
|
|
2892
|
-
files.sort();
|
|
2893
|
-
return { assetRoot: dir, files };
|
|
2694
|
+
return Object.keys(norm).length === 0 ? "__all__" : JSON.stringify(norm);
|
|
2695
|
+
}
|
|
2696
|
+
function sha256Hex(bytes) {
|
|
2697
|
+
const h = crypto.createHash("sha256");
|
|
2698
|
+
h.update(bytes);
|
|
2699
|
+
return h.digest("hex");
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
// library/src/remote/remote-catalog-source.ts
|
|
2703
|
+
var TarballHashMismatchError = class extends Error {
|
|
2704
|
+
constructor(expected, actual) {
|
|
2705
|
+
super(`SHA256 mismatch: expected ${expected}, got ${actual}`);
|
|
2706
|
+
this.expected = expected;
|
|
2707
|
+
this.actual = actual;
|
|
2708
|
+
this.name = "TarballHashMismatchError";
|
|
2894
2709
|
}
|
|
2895
|
-
|
|
2710
|
+
expected;
|
|
2711
|
+
actual;
|
|
2896
2712
|
};
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
ok: false,
|
|
2902
|
-
errors: zodResult.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
2903
|
-
};
|
|
2713
|
+
var OfflineError = class extends Error {
|
|
2714
|
+
constructor(message) {
|
|
2715
|
+
super(`${message} (air-gapped mode; run \`skaile update\` to refresh the cache)`);
|
|
2716
|
+
this.name = "OfflineError";
|
|
2904
2717
|
}
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
errors.push(`Duplicate item id: "${item.id}"`);
|
|
2913
|
-
}
|
|
2914
|
-
ids.add(item.id);
|
|
2915
|
-
} else {
|
|
2916
|
-
refCounts.set(item.ref, (refCounts.get(item.ref) ?? 0) + 1);
|
|
2917
|
-
}
|
|
2718
|
+
};
|
|
2719
|
+
var CatalogHttpError = class extends Error {
|
|
2720
|
+
constructor(message, status, url) {
|
|
2721
|
+
super(message);
|
|
2722
|
+
this.status = status;
|
|
2723
|
+
this.url = url;
|
|
2724
|
+
this.name = "CatalogHttpError";
|
|
2918
2725
|
}
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2726
|
+
status;
|
|
2727
|
+
url;
|
|
2728
|
+
};
|
|
2729
|
+
var DEFAULT_TTL_MS = 864e5;
|
|
2730
|
+
var RemoteCatalogSource = class {
|
|
2731
|
+
id;
|
|
2732
|
+
baseUrl;
|
|
2733
|
+
cache;
|
|
2734
|
+
cacheTtlMs;
|
|
2735
|
+
httpClient;
|
|
2736
|
+
silentBackgroundErrors;
|
|
2737
|
+
/** Tracks in-flight background revalidations so callers can `await` them in tests. */
|
|
2738
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
2739
|
+
constructor(opts) {
|
|
2740
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
2741
|
+
this.id = `remote:${this.baseUrl}`;
|
|
2742
|
+
this.cacheTtlMs = opts.cacheTtlMs ?? DEFAULT_TTL_MS;
|
|
2743
|
+
this.httpClient = opts.httpClient ?? defaultFetch;
|
|
2744
|
+
this.silentBackgroundErrors = opts.silentBackgroundErrors ?? false;
|
|
2745
|
+
this.cache = opts.cache ?? new CatalogCache({
|
|
2746
|
+
dir: opts.cacheDir,
|
|
2747
|
+
ttlMs: this.cacheTtlMs,
|
|
2748
|
+
now: opts.now
|
|
2749
|
+
});
|
|
2750
|
+
}
|
|
2751
|
+
// ── ICatalogSource ──────────────────────────────────────────────────────
|
|
2752
|
+
/**
|
|
2753
|
+
* Resolve a single asset by canonical ref (`<publisher>/<name>@<version>`).
|
|
2754
|
+
*
|
|
2755
|
+
* Cache: hit returns immediately. Miss fetches `/trpc/catalog.get`. Stale
|
|
2756
|
+
* entry serves the cached value and triggers a background refresh.
|
|
2757
|
+
*
|
|
2758
|
+
* Air-gapped: throws {@link OfflineError} on cache miss.
|
|
2759
|
+
*/
|
|
2760
|
+
async resolve(ref) {
|
|
2761
|
+
const fresh = this.cache.getResolve(ref);
|
|
2762
|
+
if (fresh) return fresh.value;
|
|
2763
|
+
if (this.cache.airGapped) {
|
|
2764
|
+
const stale2 = this.cache.getResolveStale(ref);
|
|
2765
|
+
if (stale2) return stale2.value;
|
|
2766
|
+
throw new OfflineError(`Asset '${ref}' not in cache`);
|
|
2767
|
+
}
|
|
2768
|
+
const stale = this.cache.getResolveStale(ref);
|
|
2769
|
+
if (stale) {
|
|
2770
|
+
this.scheduleBackground(`resolve:${ref}`, () => this.fetchAndStoreResolve(ref));
|
|
2771
|
+
return stale.value;
|
|
2924
2772
|
}
|
|
2773
|
+
return this.fetchAndStoreResolve(ref);
|
|
2925
2774
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2775
|
+
/**
|
|
2776
|
+
* List assets matching `filter`.
|
|
2777
|
+
*
|
|
2778
|
+
* Cache: hit returns immediately. Miss fetches `/trpc/catalog.list`. Stale
|
|
2779
|
+
* entry serves the cached array and triggers a background refresh.
|
|
2780
|
+
*
|
|
2781
|
+
* Air-gapped: throws {@link OfflineError} on cache miss.
|
|
2782
|
+
*/
|
|
2783
|
+
async listAssets(filter) {
|
|
2784
|
+
const fresh = this.cache.getList(filter);
|
|
2785
|
+
if (fresh) return fresh.items;
|
|
2786
|
+
if (this.cache.airGapped) {
|
|
2787
|
+
const stale2 = this.cache.getListStale(filter);
|
|
2788
|
+
if (stale2) return stale2.items;
|
|
2789
|
+
throw new OfflineError("Catalog list not in cache");
|
|
2790
|
+
}
|
|
2791
|
+
const stale = this.cache.getListStale(filter);
|
|
2792
|
+
if (stale) {
|
|
2793
|
+
const filterKey2 = JSON.stringify(filter ?? null);
|
|
2794
|
+
this.scheduleBackground(`list:${filterKey2}`, () => this.fetchAndStoreList(filter));
|
|
2795
|
+
return stale.items;
|
|
2938
2796
|
}
|
|
2797
|
+
return this.fetchAndStoreList(filter);
|
|
2939
2798
|
}
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2799
|
+
/**
|
|
2800
|
+
* Fetch the content-addressed tarball for an asset. The bytes are downloaded
|
|
2801
|
+
* via the REST endpoint `GET /tarball/:sha256` (which 307-redirects to the
|
|
2802
|
+
* storage tier), then verified against `sha256` on the client side.
|
|
2803
|
+
*
|
|
2804
|
+
* Cache: tarballs are content-addressed and cached forever once written.
|
|
2805
|
+
*
|
|
2806
|
+
* Air-gapped: throws {@link OfflineError} on cache miss.
|
|
2807
|
+
*
|
|
2808
|
+
* @throws {@link TarballHashMismatchError} when the downloaded SHA256 does not match.
|
|
2809
|
+
*/
|
|
2810
|
+
async fetchTarball(_ref, sha256) {
|
|
2811
|
+
const cached = this.cache.readTarball(sha256);
|
|
2812
|
+
if (cached) {
|
|
2813
|
+
const actual2 = sha256Hex(cached);
|
|
2814
|
+
if (actual2 !== sha256) {
|
|
2815
|
+
throw new TarballHashMismatchError(sha256, actual2);
|
|
2951
2816
|
}
|
|
2817
|
+
return cached;
|
|
2952
2818
|
}
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
return parsePreset(data);
|
|
2970
|
-
}
|
|
2971
|
-
function detectNestedPresetRefs(preset, knownPresetRefs) {
|
|
2972
|
-
const nested = [];
|
|
2973
|
-
for (const item of preset.items) {
|
|
2974
|
-
if (item.ref.startsWith("library://") || item.ref.startsWith("#")) continue;
|
|
2975
|
-
if (knownPresetRefs?.has(item.ref)) {
|
|
2976
|
-
nested.push(item.ref);
|
|
2819
|
+
if (this.cache.airGapped) {
|
|
2820
|
+
throw new OfflineError(`Tarball '${sha256}' not in cache`);
|
|
2821
|
+
}
|
|
2822
|
+
const url = `${this.baseUrl}/tarball/${encodeURIComponent(sha256)}`;
|
|
2823
|
+
const res = await this.httpClient(url, { method: "GET", redirect: "follow" });
|
|
2824
|
+
if (!res.ok) {
|
|
2825
|
+
throw new CatalogHttpError(
|
|
2826
|
+
`Failed to fetch tarball ${sha256}: ${res.status} ${res.statusText}`,
|
|
2827
|
+
res.status,
|
|
2828
|
+
url
|
|
2829
|
+
);
|
|
2830
|
+
}
|
|
2831
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
2832
|
+
const actual = sha256Hex(buf);
|
|
2833
|
+
if (actual !== sha256) {
|
|
2834
|
+
throw new TarballHashMismatchError(sha256, actual);
|
|
2977
2835
|
}
|
|
2836
|
+
this.cache.writeTarball(sha256, buf);
|
|
2837
|
+
return buf;
|
|
2978
2838
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2839
|
+
/**
|
|
2840
|
+
* Enumerate available versions for a version-less ref. The Catalog has no
|
|
2841
|
+
* dedicated version-list route, so this derives versions from `listAssets`
|
|
2842
|
+
* (filtered to the publisher) and keeps only the matching kind+name. Reuses
|
|
2843
|
+
* the cached list path, so repeated calls are cheap.
|
|
2844
|
+
*/
|
|
2845
|
+
async listVersions(ref) {
|
|
2846
|
+
const parsed = parseAssetRef(ref);
|
|
2847
|
+
const wantPublisher = parsed.publisher?.replace(/^@/, "");
|
|
2848
|
+
const assets = await this.listAssets(wantPublisher ? { publisher: wantPublisher } : void 0);
|
|
2849
|
+
return assets.filter(
|
|
2850
|
+
(a) => a.kind === parsed.kind && a.name === parsed.name && (wantPublisher === void 0 || a.publisher.replace(/^@/, "") === wantPublisher)
|
|
2851
|
+
).map((a) => a.version);
|
|
2985
2852
|
}
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
if (ref.startsWith("#")) return "cross-item";
|
|
2993
|
-
return "asset";
|
|
2994
|
-
}
|
|
2995
|
-
function extractLibraryInstanceId(ref) {
|
|
2996
|
-
const match = ref.match(/^library:\/\/[^/]+\/instances\/([a-f0-9-]+)$/i);
|
|
2997
|
-
return match ? match[1] : null;
|
|
2998
|
-
}
|
|
2999
|
-
async function resolveItem(item, library, knownPresetRefs) {
|
|
3000
|
-
const ref = item.ref;
|
|
3001
|
-
if (ref.startsWith("#")) {
|
|
3002
|
-
return { kind: "cross-item", item, targetId: ref.slice(1) };
|
|
2853
|
+
/**
|
|
2854
|
+
* Single synthetic Source pointing at the configured Catalog URL. Phase 2
|
|
2855
|
+
* is single-Source per spec; multi-source federation is Phase 4.8.
|
|
2856
|
+
*/
|
|
2857
|
+
async listSources() {
|
|
2858
|
+
return [{ id: this.id, type: "remote-catalog", url: this.baseUrl }];
|
|
3003
2859
|
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
2860
|
+
// ── extension API ───────────────────────────────────────────────────────
|
|
2861
|
+
/**
|
|
2862
|
+
* Force a metadata-cache refresh. Wipes all resolve/list entries and
|
|
2863
|
+
* re-fetches `listAssets()` (no filter) to pre-warm the canonical query.
|
|
2864
|
+
*
|
|
2865
|
+
* Driven by `skaile update`. Tarballs (content-addressed) are not touched
|
|
2866
|
+
* — they remain valid across refreshes.
|
|
2867
|
+
*
|
|
2868
|
+
* In air-gapped mode this method **does** make a network call (bypassing
|
|
2869
|
+
* the TTL=0 gate); it is the only way to repopulate the cache.
|
|
2870
|
+
*/
|
|
2871
|
+
async refresh() {
|
|
2872
|
+
this.cache.invalidateAll();
|
|
2873
|
+
const items = await this.fetchListNetwork(void 0);
|
|
2874
|
+
this.cache.setList(void 0, items);
|
|
2875
|
+
for (const item of items) {
|
|
2876
|
+
this.cache.setResolve(item.id, item);
|
|
3008
2877
|
}
|
|
3009
|
-
|
|
3010
|
-
return { kind: "library-instance", item, instanceId, instance };
|
|
3011
|
-
}
|
|
3012
|
-
if (knownPresetRefs?.has(ref)) {
|
|
3013
|
-
return { kind: "nested-preset", item, presetRef: ref };
|
|
2878
|
+
return { assetsCached: items.length };
|
|
3014
2879
|
}
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
2880
|
+
/**
|
|
2881
|
+
* Fetch the pointer-only install manifest for an asset ref.
|
|
2882
|
+
*
|
|
2883
|
+
* The install manifest is the Pointer Triple: the immutable upstream
|
|
2884
|
+
* `source.url` + `source.commitSha`, the per-file `files[].sha256` list, a
|
|
2885
|
+
* composite `sha256` rollup, plus `kind` and `tier`. Callers fetch the asset
|
|
2886
|
+
* bytes directly from the upstream repo at that commit and verify each file
|
|
2887
|
+
* against the recorded hashes — the Catalog itself never serves asset bytes.
|
|
2888
|
+
*
|
|
2889
|
+
* Wire surface: `GET /trpc/catalog.getInstallManifest?input=<superjson({ ref })>`.
|
|
2890
|
+
*
|
|
2891
|
+
* This is a `RemoteCatalogSource`-only method — it is not part of the
|
|
2892
|
+
* `ICatalogSource` contract. `LocalCatalogSource` has the asset bytes on disk
|
|
2893
|
+
* and has no pointer-triple concept, so adding it to the shared interface
|
|
2894
|
+
* would force a meaningless implementation there.
|
|
2895
|
+
*
|
|
2896
|
+
* Not cached: the install path is a one-shot read driven by `skaile install`.
|
|
2897
|
+
*
|
|
2898
|
+
* @throws {@link CatalogHttpError} on a non-2xx response or malformed envelope.
|
|
2899
|
+
*/
|
|
2900
|
+
async getInstallManifest(ref) {
|
|
2901
|
+
const url = trpcGetUrl(this.baseUrl, "catalog.getInstallManifest", { ref });
|
|
2902
|
+
const res = await this.httpClient(url, {
|
|
2903
|
+
method: "GET",
|
|
2904
|
+
headers: { Accept: "application/json" }
|
|
2905
|
+
});
|
|
2906
|
+
if (!res.ok) {
|
|
2907
|
+
throw new CatalogHttpError(
|
|
2908
|
+
`catalog.getInstallManifest ${ref} failed: ${res.status} ${res.statusText}`,
|
|
2909
|
+
res.status,
|
|
2910
|
+
url
|
|
2911
|
+
);
|
|
3028
2912
|
}
|
|
3029
|
-
|
|
3030
|
-
|
|
2913
|
+
const data = await parseTrpcGet(res);
|
|
2914
|
+
if (data === null || data === void 0 || typeof data !== "object") {
|
|
2915
|
+
throw new CatalogHttpError(
|
|
2916
|
+
`catalog.getInstallManifest ${ref} returned a non-object payload (got ${typeof data})`,
|
|
2917
|
+
200,
|
|
2918
|
+
url
|
|
2919
|
+
);
|
|
3031
2920
|
}
|
|
3032
|
-
return
|
|
3033
|
-
}
|
|
3034
|
-
switch (type) {
|
|
3035
|
-
case "text":
|
|
3036
|
-
return validateText(key, value);
|
|
3037
|
-
case "multiline":
|
|
3038
|
-
return validateMultiline(key, value);
|
|
3039
|
-
case "secret":
|
|
3040
|
-
return validateSecret(key, value);
|
|
3041
|
-
case "number":
|
|
3042
|
-
return validateNumber(key, value);
|
|
3043
|
-
case "boolean":
|
|
3044
|
-
return validateBoolean(key, value);
|
|
3045
|
-
case "enum":
|
|
3046
|
-
return validateEnum(key, value, placeholder.values);
|
|
3047
|
-
case "oauth":
|
|
3048
|
-
return validateOauth(key, value);
|
|
3049
|
-
case "instance-picker":
|
|
3050
|
-
return validateInstancePicker(key, value);
|
|
2921
|
+
return data;
|
|
3051
2922
|
}
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
2923
|
+
/**
|
|
2924
|
+
* Cheap probe used during cross-check when both a source-side and a store-side
|
|
2925
|
+
* candidate at the same version need to be verified for sha256 equality.
|
|
2926
|
+
* Returns only the composite sha256 — no source URL, no file list.
|
|
2927
|
+
*
|
|
2928
|
+
* Wire: `GET /trpc/catalog.getCanonicalDigest?input=<superjson({ ref })>`.
|
|
2929
|
+
*
|
|
2930
|
+
* Not cached: one-shot read during install resolution.
|
|
2931
|
+
*
|
|
2932
|
+
* @param ref - Canonical asset ref `<publisher>/<kind>:<name>@<version>`.
|
|
2933
|
+
* @returns `{ sha256 }` on a 200, or `null` on a 404.
|
|
2934
|
+
* @throws {@link CatalogHttpError} on a non-2xx/non-404 response or malformed payload.
|
|
2935
|
+
* @docLink packages/library/api-reference#remote-catalog-source
|
|
2936
|
+
*/
|
|
2937
|
+
async getCanonicalDigest(ref) {
|
|
2938
|
+
const url = trpcGetUrl(this.baseUrl, "catalog.getCanonicalDigest", { ref });
|
|
2939
|
+
const res = await this.httpClient(url, {
|
|
2940
|
+
method: "GET",
|
|
2941
|
+
headers: { Accept: "application/json" }
|
|
2942
|
+
});
|
|
2943
|
+
if (res.status === 404) return null;
|
|
2944
|
+
if (!res.ok) {
|
|
2945
|
+
throw new CatalogHttpError(
|
|
2946
|
+
`catalog.getCanonicalDigest ${ref} failed: ${res.status} ${res.statusText}`,
|
|
2947
|
+
res.status,
|
|
2948
|
+
url
|
|
2949
|
+
);
|
|
3062
2950
|
}
|
|
2951
|
+
const data = await parseTrpcGet(res);
|
|
2952
|
+
if (!data || typeof data !== "object") {
|
|
2953
|
+
throw new CatalogHttpError(
|
|
2954
|
+
`catalog.getCanonicalDigest ${ref} returned a non-object payload`,
|
|
2955
|
+
200,
|
|
2956
|
+
url
|
|
2957
|
+
);
|
|
2958
|
+
}
|
|
2959
|
+
const d = data;
|
|
2960
|
+
if (typeof d.sha256 !== "string") {
|
|
2961
|
+
throw new CatalogHttpError(
|
|
2962
|
+
`catalog.getCanonicalDigest ${ref} missing sha256 string`,
|
|
2963
|
+
200,
|
|
2964
|
+
url
|
|
2965
|
+
);
|
|
2966
|
+
}
|
|
2967
|
+
return { sha256: d.sha256 };
|
|
3063
2968
|
}
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
if (options?.defRefPrefix) {
|
|
3072
|
-
filter.defRef = options.defRefPrefix;
|
|
3073
|
-
}
|
|
3074
|
-
const instances2 = await library.listInstances(filter);
|
|
3075
|
-
return instances2.map((inst) => ({
|
|
3076
|
-
instanceId: inst.id,
|
|
3077
|
-
defRef: inst.defRef,
|
|
3078
|
-
label: `${inst.defRef} (${inst.id.slice(0, 8)})`
|
|
3079
|
-
}));
|
|
3080
|
-
}
|
|
3081
|
-
function getDefaultValue(placeholder) {
|
|
3082
|
-
return placeholder.default;
|
|
3083
|
-
}
|
|
3084
|
-
function isSecretPlaceholder(placeholder) {
|
|
3085
|
-
return placeholder.type === "secret" || placeholder.type === "oauth";
|
|
3086
|
-
}
|
|
3087
|
-
function validateText(key, value) {
|
|
3088
|
-
if (typeof value !== "string") {
|
|
3089
|
-
return { valid: false, errors: [`Placeholder "${key}" must be a string`] };
|
|
3090
|
-
}
|
|
3091
|
-
return { valid: true, errors: [], value };
|
|
3092
|
-
}
|
|
3093
|
-
function validateMultiline(key, value) {
|
|
3094
|
-
if (typeof value !== "string") {
|
|
3095
|
-
return { valid: false, errors: [`Placeholder "${key}" must be a string`] };
|
|
2969
|
+
/**
|
|
2970
|
+
* Wait for any in-flight stale-while-revalidate background fetches to settle.
|
|
2971
|
+
* Test-only escape hatch.
|
|
2972
|
+
*/
|
|
2973
|
+
async waitForBackground() {
|
|
2974
|
+
const promises = Array.from(this.inFlight.values());
|
|
2975
|
+
await Promise.allSettled(promises);
|
|
3096
2976
|
}
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
2977
|
+
// ── private ─────────────────────────────────────────────────────────────
|
|
2978
|
+
scheduleBackground(key, fn) {
|
|
2979
|
+
if (this.inFlight.has(key)) return;
|
|
2980
|
+
const p = (async () => {
|
|
2981
|
+
try {
|
|
2982
|
+
await fn();
|
|
2983
|
+
} catch (err) {
|
|
2984
|
+
if (!this.silentBackgroundErrors) {
|
|
2985
|
+
process.stderr.write(
|
|
2986
|
+
`[skaile/catalog] Background refresh failed for ${key}: ${err instanceof Error ? err.message : String(err)}
|
|
2987
|
+
`
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
} finally {
|
|
2991
|
+
this.inFlight.delete(key);
|
|
2992
|
+
}
|
|
2993
|
+
})();
|
|
2994
|
+
this.inFlight.set(key, p);
|
|
3102
2995
|
}
|
|
3103
|
-
|
|
3104
|
-
|
|
2996
|
+
async fetchAndStoreResolve(ref) {
|
|
2997
|
+
const value = await this.fetchResolveNetwork(ref);
|
|
2998
|
+
this.cache.setResolve(ref, value);
|
|
2999
|
+
return value;
|
|
3105
3000
|
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
return { valid: true, errors: [], value };
|
|
3001
|
+
async fetchAndStoreList(filter) {
|
|
3002
|
+
const items = await this.fetchListNetwork(filter);
|
|
3003
|
+
this.cache.setList(filter, items);
|
|
3004
|
+
return items;
|
|
3111
3005
|
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3006
|
+
/** Raw network call: GET /trpc/catalog.get?input=<superjson> */
|
|
3007
|
+
async fetchResolveNetwork(ref) {
|
|
3008
|
+
const url = trpcGetUrl(this.baseUrl, "catalog.get", { ref });
|
|
3009
|
+
const res = await this.httpClient(url, {
|
|
3010
|
+
method: "GET",
|
|
3011
|
+
headers: { Accept: "application/json" }
|
|
3012
|
+
});
|
|
3013
|
+
if (!res.ok) {
|
|
3014
|
+
throw new CatalogHttpError(
|
|
3015
|
+
`catalog.get ${ref} failed: ${res.status} ${res.statusText}`,
|
|
3016
|
+
res.status,
|
|
3017
|
+
url
|
|
3018
|
+
);
|
|
3116
3019
|
}
|
|
3020
|
+
const data = await parseTrpcGet(res);
|
|
3021
|
+
if (data === null || data === void 0) return null;
|
|
3022
|
+
return normalizeCatalogAsset(data);
|
|
3117
3023
|
}
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
if (
|
|
3127
|
-
|
|
3024
|
+
/** Raw network call: GET /trpc/catalog.list?input=<superjson> */
|
|
3025
|
+
async fetchListNetwork(filter) {
|
|
3026
|
+
const input = filter ? { filter } : {};
|
|
3027
|
+
const url = trpcGetUrl(this.baseUrl, "catalog.list", input);
|
|
3028
|
+
const res = await this.httpClient(url, {
|
|
3029
|
+
method: "GET",
|
|
3030
|
+
headers: { Accept: "application/json" }
|
|
3031
|
+
});
|
|
3032
|
+
if (!res.ok) {
|
|
3033
|
+
throw new CatalogHttpError(
|
|
3034
|
+
`catalog.list failed: ${res.status} ${res.statusText}`,
|
|
3035
|
+
res.status,
|
|
3036
|
+
url
|
|
3037
|
+
);
|
|
3128
3038
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3039
|
+
const data = await parseTrpcGet(res);
|
|
3040
|
+
if (!Array.isArray(data)) {
|
|
3041
|
+
throw new CatalogHttpError(
|
|
3042
|
+
`catalog.list returned non-array payload (got ${typeof data})`,
|
|
3043
|
+
200,
|
|
3044
|
+
url
|
|
3045
|
+
);
|
|
3131
3046
|
}
|
|
3047
|
+
return data.map(normalizeCatalogAsset);
|
|
3132
3048
|
}
|
|
3133
|
-
|
|
3049
|
+
};
|
|
3050
|
+
function trpcGetUrl(baseUrl, procedure, input) {
|
|
3051
|
+
const wrapped = JSON.stringify({ json: input });
|
|
3052
|
+
return `${baseUrl.replace(/\/+$/, "")}/trpc/${procedure}?input=${encodeURIComponent(wrapped)}`;
|
|
3134
3053
|
}
|
|
3135
|
-
function
|
|
3136
|
-
|
|
3137
|
-
|
|
3054
|
+
async function parseTrpcGet(res) {
|
|
3055
|
+
const text2 = await res.text();
|
|
3056
|
+
let body;
|
|
3057
|
+
try {
|
|
3058
|
+
body = text2.length > 0 ? JSON.parse(text2) : null;
|
|
3059
|
+
} catch {
|
|
3060
|
+
throw new CatalogHttpError(
|
|
3061
|
+
`tRPC response is not valid JSON: ${text2.slice(0, 200)}`,
|
|
3062
|
+
res.status
|
|
3063
|
+
);
|
|
3138
3064
|
}
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
`Placeholder "${key}" value "${stringValue}" is not in allowed values: [${allowedValues.join(", ")}]`
|
|
3146
|
-
]
|
|
3147
|
-
};
|
|
3065
|
+
if (body && typeof body === "object" && "error" in body) {
|
|
3066
|
+
const err = body.error;
|
|
3067
|
+
throw new CatalogHttpError(
|
|
3068
|
+
`tRPC error: ${err.message ?? "unknown"}${err.data?.code ? ` (${err.data.code})` : ""}`,
|
|
3069
|
+
res.status
|
|
3070
|
+
);
|
|
3148
3071
|
}
|
|
3149
|
-
const
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
function validateOauth(key, value) {
|
|
3153
|
-
if (typeof value !== "string") {
|
|
3154
|
-
return { valid: false, errors: [`Placeholder "${key}" (oauth) must be a string`] };
|
|
3072
|
+
const result = body?.result;
|
|
3073
|
+
if (!result || !("data" in result)) {
|
|
3074
|
+
throw new CatalogHttpError(`tRPC response missing result.data envelope`, res.status);
|
|
3155
3075
|
}
|
|
3156
|
-
|
|
3157
|
-
|
|
3076
|
+
const data = result.data;
|
|
3077
|
+
if (data && typeof data === "object" && !Array.isArray(data) && "json" in data) {
|
|
3078
|
+
return data.json;
|
|
3158
3079
|
}
|
|
3159
|
-
return
|
|
3080
|
+
return data;
|
|
3160
3081
|
}
|
|
3161
|
-
function
|
|
3162
|
-
if (typeof
|
|
3163
|
-
|
|
3164
|
-
valid: false,
|
|
3165
|
-
errors: [`Placeholder "${key}" (instance-picker) must be a string UUID`]
|
|
3166
|
-
};
|
|
3167
|
-
}
|
|
3168
|
-
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3169
|
-
if (!uuidPattern.test(value)) {
|
|
3170
|
-
return {
|
|
3171
|
-
valid: false,
|
|
3172
|
-
errors: [`Placeholder "${key}" (instance-picker) must be a valid UUID`]
|
|
3173
|
-
};
|
|
3082
|
+
function normalizeCatalogAsset(raw) {
|
|
3083
|
+
if (!raw || typeof raw !== "object") {
|
|
3084
|
+
throw new CatalogHttpError("Catalog response item is not an object", 200);
|
|
3174
3085
|
}
|
|
3175
|
-
|
|
3086
|
+
const r = raw;
|
|
3087
|
+
const publisher = pickString(r, "publisher") ?? pickString(r, "publisherSlug") ?? "";
|
|
3088
|
+
const name = pickString(r, "name") ?? "";
|
|
3089
|
+
const version = pickString(r, "version") ?? "";
|
|
3090
|
+
const id = pickString(r, "id") ?? `${publisher}/${name}@${version}`;
|
|
3091
|
+
const kind = pickString(r, "kind") ?? "";
|
|
3092
|
+
const manifest = typeof r.manifest === "object" && r.manifest !== null ? r.manifest : typeof r.manifestJson === "object" && r.manifestJson !== null ? r.manifestJson : {};
|
|
3093
|
+
const asset = {
|
|
3094
|
+
id,
|
|
3095
|
+
kind,
|
|
3096
|
+
publisher,
|
|
3097
|
+
name,
|
|
3098
|
+
version,
|
|
3099
|
+
manifest
|
|
3100
|
+
};
|
|
3101
|
+
const sha = pickString(r, "sha256") ?? pickString(r, "tarballSha256");
|
|
3102
|
+
if (sha) asset.sha256 = sha;
|
|
3103
|
+
const description = pickString(r, "description");
|
|
3104
|
+
if (description) asset.description = description;
|
|
3105
|
+
const license = pickString(r, "license");
|
|
3106
|
+
if (license) asset.license = license;
|
|
3107
|
+
const category = pickString(r, "category");
|
|
3108
|
+
if (category) asset.category = category;
|
|
3109
|
+
const repo = pickString(r, "repo");
|
|
3110
|
+
if (repo) asset.repo = repo;
|
|
3111
|
+
const domainSlug = pickString(r, "domainSlug");
|
|
3112
|
+
if (domainSlug) asset.domainSlug = domainSlug;
|
|
3113
|
+
return asset;
|
|
3176
3114
|
}
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3115
|
+
function pickString(r, key) {
|
|
3116
|
+
const v = r[key];
|
|
3117
|
+
return typeof v === "string" ? v : void 0;
|
|
3118
|
+
}
|
|
3119
|
+
var CATALOG_REQUEST_TIMEOUT_MS = 1e4;
|
|
3120
|
+
var defaultFetch = async (url, init) => {
|
|
3121
|
+
if (typeof fetch === "undefined") {
|
|
3122
|
+
throw new Error("global fetch is not available; pass an httpClient explicitly");
|
|
3183
3123
|
}
|
|
3124
|
+
const requestInit = init ?? {};
|
|
3125
|
+
return fetch(url, {
|
|
3126
|
+
...requestInit,
|
|
3127
|
+
signal: requestInit.signal ?? AbortSignal.timeout(CATALOG_REQUEST_TIMEOUT_MS)
|
|
3128
|
+
});
|
|
3184
3129
|
};
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3130
|
+
|
|
3131
|
+
// library/src/remote/rest-catalog-source.ts
|
|
3132
|
+
var DEFAULT_TTL_MS2 = 864e5;
|
|
3133
|
+
var RestCatalogSource = class {
|
|
3134
|
+
id;
|
|
3135
|
+
baseUrl;
|
|
3136
|
+
cache;
|
|
3137
|
+
cacheTtlMs;
|
|
3138
|
+
httpClient;
|
|
3139
|
+
silentBackgroundErrors;
|
|
3140
|
+
/** Tracks in-flight background revalidations so callers can `await` them in tests. */
|
|
3141
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
3142
|
+
constructor(opts) {
|
|
3143
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
3144
|
+
this.id = `rest:${this.baseUrl}`;
|
|
3145
|
+
this.cacheTtlMs = opts.cacheTtlMs ?? DEFAULT_TTL_MS2;
|
|
3146
|
+
this.httpClient = opts.httpClient ?? defaultFetch2;
|
|
3147
|
+
this.silentBackgroundErrors = opts.silentBackgroundErrors ?? false;
|
|
3148
|
+
this.cache = opts.cache ?? new CatalogCache({
|
|
3149
|
+
dir: opts.cacheDir,
|
|
3150
|
+
ttlMs: this.cacheTtlMs,
|
|
3151
|
+
now: opts.now
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
// ── ICatalogSource ──────────────────────────────────────────────────────
|
|
3155
|
+
/**
|
|
3156
|
+
* Resolve a single asset by canonical ref (`<publisher>/<name>@<version>`).
|
|
3157
|
+
*
|
|
3158
|
+
* Cache: hit returns immediately. Miss fetches `/api/catalog/get?ref=<ref>`. Stale
|
|
3159
|
+
* entry serves the cached value and triggers a background refresh.
|
|
3160
|
+
*
|
|
3161
|
+
* Air-gapped: throws {@link OfflineError} on cache miss.
|
|
3162
|
+
*/
|
|
3163
|
+
async resolve(ref) {
|
|
3164
|
+
const fresh = this.cache.getResolve(ref);
|
|
3165
|
+
if (fresh) return fresh.value;
|
|
3166
|
+
if (this.cache.airGapped) {
|
|
3167
|
+
const stale2 = this.cache.getResolveStale(ref);
|
|
3168
|
+
if (stale2) return stale2.value;
|
|
3169
|
+
throw new OfflineError(`Asset '${ref}' not in cache`);
|
|
3192
3170
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
}
|
|
3171
|
+
const stale = this.cache.getResolveStale(ref);
|
|
3172
|
+
if (stale) {
|
|
3173
|
+
this.scheduleBackground(`resolve:${ref}`, () => this.fetchAndStoreResolve(ref));
|
|
3174
|
+
return stale.value;
|
|
3198
3175
|
}
|
|
3199
|
-
return
|
|
3176
|
+
return this.fetchAndStoreResolve(ref);
|
|
3200
3177
|
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3178
|
+
/**
|
|
3179
|
+
* List assets matching `filter`.
|
|
3180
|
+
*
|
|
3181
|
+
* Cache: hit returns immediately. Miss fetches `/api/catalog/list`. Stale
|
|
3182
|
+
* entry serves the cached array and triggers a background refresh.
|
|
3183
|
+
*
|
|
3184
|
+
* Air-gapped: throws {@link OfflineError} on cache miss.
|
|
3185
|
+
*
|
|
3186
|
+
* Note: `filter.prefix` is not supported by the forge-store REST API and is silently ignored.
|
|
3187
|
+
*/
|
|
3188
|
+
async listAssets(filter) {
|
|
3189
|
+
const fresh = this.cache.getList(filter);
|
|
3190
|
+
if (fresh) return fresh.items;
|
|
3191
|
+
if (this.cache.airGapped) {
|
|
3192
|
+
const stale2 = this.cache.getListStale(filter);
|
|
3193
|
+
if (stale2) return stale2.items;
|
|
3194
|
+
throw new OfflineError("Catalog list not in cache");
|
|
3212
3195
|
}
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
const encoded = new TextEncoder().encode(value);
|
|
3219
|
-
await router.setSecret(ref, encoded);
|
|
3220
|
-
}
|
|
3221
|
-
}
|
|
3222
|
-
function hasInterpolationTokens(template) {
|
|
3223
|
-
return INTERPOLATION_PATTERN.test(template);
|
|
3224
|
-
}
|
|
3225
|
-
function extractReferencedKeys(materialize) {
|
|
3226
|
-
const inputKeys = /* @__PURE__ */ new Set();
|
|
3227
|
-
const secretKeys = /* @__PURE__ */ new Set();
|
|
3228
|
-
for (const template of Object.values(materialize)) {
|
|
3229
|
-
const pattern = new RegExp(INTERPOLATION_PATTERN.source, "g");
|
|
3230
|
-
for (const match of template.matchAll(pattern)) {
|
|
3231
|
-
const namespace = match[1];
|
|
3232
|
-
const key = match[2];
|
|
3233
|
-
if (namespace === "INPUT") inputKeys.add(key);
|
|
3234
|
-
else if (namespace === "SECRET") secretKeys.add(key);
|
|
3196
|
+
const stale = this.cache.getListStale(filter);
|
|
3197
|
+
if (stale) {
|
|
3198
|
+
const filterKey2 = JSON.stringify(filter ?? null);
|
|
3199
|
+
this.scheduleBackground(`list:${filterKey2}`, () => this.fetchAndStoreList(filter));
|
|
3200
|
+
return stale.items;
|
|
3235
3201
|
}
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3202
|
+
return this.fetchAndStoreList(filter);
|
|
3203
|
+
}
|
|
3204
|
+
/**
|
|
3205
|
+
* Fetch the content-addressed tarball for an asset.
|
|
3206
|
+
*
|
|
3207
|
+
* Steps:
|
|
3208
|
+
* 1. `GET /api/tarball/manifest?sha256=<sha256>` → JSON with `{ url, ... }`
|
|
3209
|
+
* 2. `GET <manifest.url>` following redirects → raw bytes
|
|
3210
|
+
* 3. Verify SHA256 client-side — hard fail on mismatch
|
|
3211
|
+
* 4. Write to cache on success
|
|
3212
|
+
*
|
|
3213
|
+
* Cache: tarballs are content-addressed and cached forever once written.
|
|
3214
|
+
*
|
|
3215
|
+
* Air-gapped: throws {@link OfflineError} on cache miss.
|
|
3216
|
+
*
|
|
3217
|
+
* @throws {@link TarballHashMismatchError} when the downloaded SHA256 does not match.
|
|
3218
|
+
*/
|
|
3219
|
+
async fetchTarball(_ref, sha256) {
|
|
3220
|
+
const cached = this.cache.readTarball(sha256);
|
|
3221
|
+
if (cached) {
|
|
3222
|
+
const actual2 = sha256Hex(cached);
|
|
3223
|
+
if (actual2 !== sha256) {
|
|
3224
|
+
throw new TarballHashMismatchError(sha256, actual2);
|
|
3247
3225
|
}
|
|
3248
|
-
return
|
|
3226
|
+
return cached;
|
|
3249
3227
|
}
|
|
3250
|
-
if (
|
|
3251
|
-
throw new
|
|
3228
|
+
if (this.cache.airGapped) {
|
|
3229
|
+
throw new OfflineError(`Tarball '${sha256}' not in cache`);
|
|
3252
3230
|
}
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
throw new InterpolationError(`Missing input key "${key}" in interpolation context`);
|
|
3231
|
+
const manifestUrl = `${this.baseUrl}/api/tarball/manifest?sha256=${encodeURIComponent(sha256)}`;
|
|
3232
|
+
const manifestRes = await this.httpClient(manifestUrl, {
|
|
3233
|
+
method: "GET",
|
|
3234
|
+
headers: { Accept: "application/json" }
|
|
3235
|
+
});
|
|
3236
|
+
if (!manifestRes.ok) {
|
|
3237
|
+
throw new CatalogHttpError(
|
|
3238
|
+
`Failed to fetch tarball manifest for ${sha256}: ${manifestRes.status} ${manifestRes.statusText}`,
|
|
3239
|
+
manifestRes.status,
|
|
3240
|
+
manifestUrl
|
|
3241
|
+
);
|
|
3265
3242
|
}
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
current[key] = {};
|
|
3243
|
+
const manifestText = await manifestRes.text();
|
|
3244
|
+
let manifest;
|
|
3245
|
+
try {
|
|
3246
|
+
manifest = JSON.parse(manifestText);
|
|
3247
|
+
} catch {
|
|
3248
|
+
throw new CatalogHttpError(
|
|
3249
|
+
`Tarball manifest response is not valid JSON: ${manifestText.slice(0, 200)}`,
|
|
3250
|
+
manifestRes.status,
|
|
3251
|
+
manifestUrl
|
|
3252
|
+
);
|
|
3277
3253
|
}
|
|
3278
|
-
|
|
3254
|
+
const downloadUrl = manifest && typeof manifest === "object" && "url" in manifest ? manifest.url : void 0;
|
|
3255
|
+
if (typeof downloadUrl !== "string") {
|
|
3256
|
+
throw new CatalogHttpError(
|
|
3257
|
+
`Tarball manifest missing 'url' field`,
|
|
3258
|
+
manifestRes.status,
|
|
3259
|
+
manifestUrl
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
3262
|
+
const tarballRes = await this.httpClient(downloadUrl, { method: "GET", redirect: "follow" });
|
|
3263
|
+
if (!tarballRes.ok) {
|
|
3264
|
+
throw new CatalogHttpError(
|
|
3265
|
+
`Failed to fetch tarball ${sha256}: ${tarballRes.status} ${tarballRes.statusText}`,
|
|
3266
|
+
tarballRes.status,
|
|
3267
|
+
downloadUrl
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
3270
|
+
const buf = new Uint8Array(await tarballRes.arrayBuffer());
|
|
3271
|
+
const actual = sha256Hex(buf);
|
|
3272
|
+
if (actual !== sha256) {
|
|
3273
|
+
throw new TarballHashMismatchError(sha256, actual);
|
|
3274
|
+
}
|
|
3275
|
+
this.cache.writeTarball(sha256, buf);
|
|
3276
|
+
return buf;
|
|
3279
3277
|
}
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3278
|
+
/**
|
|
3279
|
+
* Enumerate available versions for a version-less ref. The forge-store REST
|
|
3280
|
+
* API has no version-list route, so this derives versions from `listAssets`
|
|
3281
|
+
* (filtered to the publisher) and keeps only the matching kind+name. Reuses
|
|
3282
|
+
* the cached list path, so repeated calls are cheap.
|
|
3283
|
+
*/
|
|
3284
|
+
async listVersions(ref) {
|
|
3285
|
+
const parsed = parseAssetRef(ref);
|
|
3286
|
+
const wantPublisher = parsed.publisher?.replace(/^@/, "");
|
|
3287
|
+
const assets = await this.listAssets(wantPublisher ? { publisher: wantPublisher } : void 0);
|
|
3288
|
+
return assets.filter(
|
|
3289
|
+
(a) => a.kind === parsed.kind && a.name === parsed.name && (wantPublisher === void 0 || a.publisher.replace(/^@/, "") === wantPublisher)
|
|
3290
|
+
).map((a) => a.version);
|
|
3290
3291
|
}
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3292
|
+
/**
|
|
3293
|
+
* Single synthetic Source pointing at the configured forge-store URL.
|
|
3294
|
+
*/
|
|
3295
|
+
async listSources() {
|
|
3296
|
+
return [{ id: this.id, type: "rest-catalog", url: this.baseUrl }];
|
|
3297
|
+
}
|
|
3298
|
+
// ── extension API ───────────────────────────────────────────────────────
|
|
3299
|
+
/**
|
|
3300
|
+
* Force a metadata-cache refresh. Wipes all resolve/list entries and
|
|
3301
|
+
* re-fetches `listAssets()` (no filter) to pre-warm the canonical query.
|
|
3302
|
+
*
|
|
3303
|
+
* Driven by `skaile update`. Tarballs (content-addressed) are not touched.
|
|
3304
|
+
*
|
|
3305
|
+
* In air-gapped mode this method **does** make a network call (bypassing
|
|
3306
|
+
* the TTL=0 gate); it is the only way to repopulate the cache.
|
|
3307
|
+
*/
|
|
3308
|
+
async refresh() {
|
|
3309
|
+
this.cache.invalidateAll();
|
|
3310
|
+
const items = await this.fetchListNetwork(void 0);
|
|
3311
|
+
this.cache.setList(void 0, items);
|
|
3312
|
+
for (const item of items) {
|
|
3313
|
+
this.cache.setResolve(item.id, item);
|
|
3307
3314
|
}
|
|
3308
|
-
|
|
3315
|
+
return { assetsCached: items.length };
|
|
3309
3316
|
}
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
assignments: [],
|
|
3318
|
-
errors: parseResult.errors.map((e) => `Parse: ${e}`)
|
|
3319
|
-
};
|
|
3317
|
+
/**
|
|
3318
|
+
* Wait for any in-flight stale-while-revalidate background fetches to settle.
|
|
3319
|
+
* Test-only escape hatch.
|
|
3320
|
+
*/
|
|
3321
|
+
async waitForBackground() {
|
|
3322
|
+
const promises = Array.from(this.inFlight.values());
|
|
3323
|
+
await Promise.allSettled(promises);
|
|
3320
3324
|
}
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
const resolved = await resolveItem(item, library, opts.knownPresetRefs);
|
|
3326
|
-
switch (resolved.kind) {
|
|
3327
|
-
case "cross-item": {
|
|
3328
|
-
const target = itemResults.get(resolved.targetId);
|
|
3329
|
-
if (!target) {
|
|
3330
|
-
return {
|
|
3331
|
-
instances: [],
|
|
3332
|
-
assignments: [],
|
|
3333
|
-
errors: [
|
|
3334
|
-
`Cross-item ref "#${resolved.targetId}" not yet resolved (items must be ordered)`
|
|
3335
|
-
]
|
|
3336
|
-
};
|
|
3337
|
-
}
|
|
3338
|
-
return { instances: [target], assignments: [], errors: [] };
|
|
3339
|
-
}
|
|
3340
|
-
case "library-instance": {
|
|
3341
|
-
if (!resolved.instance) {
|
|
3342
|
-
return {
|
|
3343
|
-
instances: [],
|
|
3344
|
-
assignments: [],
|
|
3345
|
-
errors: [`Library instance "${resolved.instanceId}" not found`]
|
|
3346
|
-
};
|
|
3347
|
-
}
|
|
3325
|
+
// ── private ─────────────────────────────────────────────────────────────
|
|
3326
|
+
scheduleBackground(key, fn) {
|
|
3327
|
+
if (this.inFlight.has(key)) return;
|
|
3328
|
+
const p = (async () => {
|
|
3348
3329
|
try {
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
if (!opts.loadPreset) {
|
|
3357
|
-
return {
|
|
3358
|
-
instances: [],
|
|
3359
|
-
assignments: [],
|
|
3360
|
-
errors: [`Cannot resolve nested preset "${resolved.presetRef}" \u2014 no loader provided`]
|
|
3361
|
-
};
|
|
3362
|
-
}
|
|
3363
|
-
const nestedData = await opts.loadPreset(resolved.presetRef);
|
|
3364
|
-
if (!nestedData) {
|
|
3365
|
-
return {
|
|
3366
|
-
instances: [],
|
|
3367
|
-
assignments: [],
|
|
3368
|
-
errors: [`Nested preset "${resolved.presetRef}" not found`]
|
|
3369
|
-
};
|
|
3370
|
-
}
|
|
3371
|
-
const parseResult = parsePreset(nestedData);
|
|
3372
|
-
if (!parseResult.ok || !parseResult.preset) {
|
|
3373
|
-
return { instances: [], assignments: [], errors: parseResult.errors };
|
|
3374
|
-
}
|
|
3375
|
-
const nestedInputs = {
|
|
3376
|
-
...item.inputs ?? {},
|
|
3377
|
-
...inputs
|
|
3378
|
-
};
|
|
3379
|
-
const nestedResult = await applyPreset(resolved.presetRef, parseResult.preset, nestedInputs, {
|
|
3380
|
-
...opts,
|
|
3381
|
-
_depth: depth + 1
|
|
3382
|
-
});
|
|
3383
|
-
return {
|
|
3384
|
-
instances: nestedResult.instances,
|
|
3385
|
-
assignments: nestedResult.assignments,
|
|
3386
|
-
errors: nestedResult.errors
|
|
3387
|
-
};
|
|
3388
|
-
}
|
|
3389
|
-
case "asset": {
|
|
3390
|
-
const itemInputs = { ...inputs };
|
|
3391
|
-
let validatedValues = {};
|
|
3392
|
-
const secretValues = {};
|
|
3393
|
-
if (item.placeholders && item.placeholders.length > 0) {
|
|
3394
|
-
const nonSecretPhs = item.placeholders.filter((ph) => !isSecretPlaceholder(ph));
|
|
3395
|
-
const secretPhs = item.placeholders.filter((ph) => isSecretPlaceholder(ph));
|
|
3396
|
-
const validation = validateAllPlaceholders(nonSecretPhs, itemInputs);
|
|
3397
|
-
if (!validation.valid) {
|
|
3398
|
-
return { instances: [], assignments: [], errors: validation.errors };
|
|
3399
|
-
}
|
|
3400
|
-
validatedValues = validation.values;
|
|
3401
|
-
const secretValidation = validateAllPlaceholders(secretPhs, itemInputs);
|
|
3402
|
-
if (!secretValidation.valid) {
|
|
3403
|
-
return { instances: [], assignments: [], errors: secretValidation.errors };
|
|
3404
|
-
}
|
|
3405
|
-
for (const [key, value] of Object.entries(secretValidation.values)) {
|
|
3406
|
-
secretValues[key] = String(value);
|
|
3330
|
+
await fn();
|
|
3331
|
+
} catch (err) {
|
|
3332
|
+
if (!this.silentBackgroundErrors) {
|
|
3333
|
+
process.stderr.write(
|
|
3334
|
+
`[skaile/catalog] Background refresh failed for ${key}: ${err instanceof Error ? err.message : String(err)}
|
|
3335
|
+
`
|
|
3336
|
+
);
|
|
3407
3337
|
}
|
|
3338
|
+
} finally {
|
|
3339
|
+
this.inFlight.delete(key);
|
|
3408
3340
|
}
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3341
|
+
})();
|
|
3342
|
+
this.inFlight.set(key, p);
|
|
3343
|
+
}
|
|
3344
|
+
async fetchAndStoreResolve(ref) {
|
|
3345
|
+
const value = await this.fetchResolveNetwork(ref);
|
|
3346
|
+
this.cache.setResolve(ref, value);
|
|
3347
|
+
return value;
|
|
3348
|
+
}
|
|
3349
|
+
async fetchAndStoreList(filter) {
|
|
3350
|
+
const items = await this.fetchListNetwork(filter);
|
|
3351
|
+
this.cache.setList(filter, items);
|
|
3352
|
+
return items;
|
|
3353
|
+
}
|
|
3354
|
+
/** Raw network call: GET /api/catalog/get?ref=<ref> */
|
|
3355
|
+
async fetchResolveNetwork(ref) {
|
|
3356
|
+
const url = `${this.baseUrl}/api/catalog/get?ref=${encodeURIComponent(ref)}`;
|
|
3357
|
+
const res = await this.httpClient(url, {
|
|
3358
|
+
method: "GET",
|
|
3359
|
+
headers: { Accept: "application/json" }
|
|
3360
|
+
});
|
|
3361
|
+
if (!res.ok) {
|
|
3362
|
+
throw new CatalogHttpError(
|
|
3363
|
+
`catalog/get ${ref} failed: ${res.status} ${res.statusText}`,
|
|
3364
|
+
res.status,
|
|
3365
|
+
url
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3368
|
+
const data = await parseRestResponse(res, url);
|
|
3369
|
+
if (data === null || data === void 0) return null;
|
|
3370
|
+
return normalizeRestAsset(data);
|
|
3371
|
+
}
|
|
3372
|
+
/** Raw network call: GET /api/catalog/list?publisher=&kind=&category= */
|
|
3373
|
+
async fetchListNetwork(filter) {
|
|
3374
|
+
const params = new URLSearchParams();
|
|
3375
|
+
if (filter?.publisher) params.set("publisher", filter.publisher);
|
|
3376
|
+
if (filter?.kind) params.set("kind", filter.kind);
|
|
3377
|
+
const qs = params.toString();
|
|
3378
|
+
const url = `${this.baseUrl}/api/catalog/list${qs ? `?${qs}` : ""}`;
|
|
3379
|
+
const res = await this.httpClient(url, {
|
|
3380
|
+
method: "GET",
|
|
3381
|
+
headers: { Accept: "application/json" }
|
|
3382
|
+
});
|
|
3383
|
+
if (!res.ok) {
|
|
3384
|
+
throw new CatalogHttpError(
|
|
3385
|
+
`catalog/list failed: ${res.status} ${res.statusText}`,
|
|
3386
|
+
res.status,
|
|
3387
|
+
url
|
|
3419
3388
|
);
|
|
3420
|
-
if (opts.secretsRouter && interpolated.secretsToStore.length > 0) {
|
|
3421
|
-
try {
|
|
3422
|
-
await storeSecrets(opts.secretsRouter, interpolated.secretsToStore);
|
|
3423
|
-
} catch (err) {
|
|
3424
|
-
return {
|
|
3425
|
-
instances: [],
|
|
3426
|
-
assignments: [],
|
|
3427
|
-
errors: [
|
|
3428
|
-
`Secret storage failed for "${item.ref}": ${err instanceof Error ? err.message : String(err)}`
|
|
3429
|
-
]
|
|
3430
|
-
};
|
|
3431
|
-
}
|
|
3432
|
-
}
|
|
3433
|
-
try {
|
|
3434
|
-
const instance = await library.createInstance({
|
|
3435
|
-
defRef: resolved.defRef,
|
|
3436
|
-
defPin: pinPolicy,
|
|
3437
|
-
config: interpolated.config,
|
|
3438
|
-
credentialRef: interpolated.credentialRef,
|
|
3439
|
-
createdBy: opts.createdBy ?? `preset:${presetRef}`
|
|
3440
|
-
});
|
|
3441
|
-
const assignments2 = [];
|
|
3442
|
-
try {
|
|
3443
|
-
const asgn = await library.assign(opts.workspaceId, instance.id, pinPolicy);
|
|
3444
|
-
assignments2.push(asgn);
|
|
3445
|
-
} catch {
|
|
3446
|
-
}
|
|
3447
|
-
return { instances: [instance], assignments: assignments2, errors: [] };
|
|
3448
|
-
} catch (err) {
|
|
3449
|
-
return {
|
|
3450
|
-
instances: [],
|
|
3451
|
-
assignments: [],
|
|
3452
|
-
errors: [
|
|
3453
|
-
`Instance creation failed for "${item.ref}": ${err instanceof Error ? err.message : String(err)}`
|
|
3454
|
-
]
|
|
3455
|
-
};
|
|
3456
|
-
}
|
|
3457
3389
|
}
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
const addedItems = [];
|
|
3466
|
-
const removedItems = [];
|
|
3467
|
-
const changedRefItems = [];
|
|
3468
|
-
const configChangedItems = [];
|
|
3469
|
-
for (const [key, oldItem] of oldItems) {
|
|
3470
|
-
if (!newItems.has(key)) {
|
|
3471
|
-
removedItems.push(oldItem);
|
|
3390
|
+
const data = await parseRestResponse(res, url);
|
|
3391
|
+
if (!Array.isArray(data)) {
|
|
3392
|
+
throw new CatalogHttpError(
|
|
3393
|
+
`catalog/list returned non-array payload (got ${typeof data})`,
|
|
3394
|
+
200,
|
|
3395
|
+
url
|
|
3396
|
+
);
|
|
3472
3397
|
}
|
|
3398
|
+
return data.map(normalizeRestAsset);
|
|
3473
3399
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3400
|
+
};
|
|
3401
|
+
async function parseRestResponse(res, url) {
|
|
3402
|
+
const text2 = await res.text();
|
|
3403
|
+
if (text2.length === 0) return null;
|
|
3404
|
+
try {
|
|
3405
|
+
return JSON.parse(text2);
|
|
3406
|
+
} catch {
|
|
3407
|
+
throw new CatalogHttpError(
|
|
3408
|
+
`REST response is not valid JSON: ${text2.slice(0, 200)}`,
|
|
3409
|
+
res.status,
|
|
3410
|
+
url
|
|
3411
|
+
);
|
|
3478
3412
|
}
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
changedRefItems.push({ old: oldItem, new: newItem });
|
|
3484
|
-
} else if (!deepEqual(oldItem.placeholders, newItem.placeholders) || !deepEqual(oldItem.materialize, newItem.materialize)) {
|
|
3485
|
-
changedRefItems.push({ old: oldItem, new: newItem });
|
|
3486
|
-
} else if (!deepEqual(oldItem.config, newItem.config)) {
|
|
3487
|
-
configChangedItems.push({ old: oldItem, new: newItem });
|
|
3488
|
-
}
|
|
3413
|
+
}
|
|
3414
|
+
function normalizeRestAsset(raw) {
|
|
3415
|
+
if (!raw || typeof raw !== "object") {
|
|
3416
|
+
throw new CatalogHttpError("Catalog REST response item is not an object", 200);
|
|
3489
3417
|
}
|
|
3490
|
-
const
|
|
3491
|
-
const
|
|
3492
|
-
const
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
const
|
|
3499
|
-
|
|
3418
|
+
const r = raw;
|
|
3419
|
+
const publisher = pickString2(r, "publisherNamespace") ?? pickString2(r, "publisher") ?? "";
|
|
3420
|
+
const name = pickString2(r, "name") ?? "";
|
|
3421
|
+
const version = pickString2(r, "version") ?? "";
|
|
3422
|
+
const kind = pickString2(r, "kind") ?? "";
|
|
3423
|
+
const id = `${publisher}/${name}@${version}`;
|
|
3424
|
+
const requiresParsed = tryParseJson(pickString2(r, "requiresJson"));
|
|
3425
|
+
const filesParsed = tryParseJson(pickString2(r, "filesJson"));
|
|
3426
|
+
const manifest = { ...requiresParsed, ...filesParsed };
|
|
3427
|
+
const asset = {
|
|
3428
|
+
id,
|
|
3500
3429
|
kind,
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
summary
|
|
3430
|
+
publisher,
|
|
3431
|
+
name,
|
|
3432
|
+
version,
|
|
3433
|
+
manifest
|
|
3506
3434
|
};
|
|
3435
|
+
const sha = pickString2(r, "sha256");
|
|
3436
|
+
if (sha) asset.sha256 = sha;
|
|
3437
|
+
const description = pickString2(r, "description");
|
|
3438
|
+
if (description) asset.description = description;
|
|
3439
|
+
const license = pickString2(r, "license");
|
|
3440
|
+
if (license) asset.license = license;
|
|
3441
|
+
const category = pickString2(r, "category");
|
|
3442
|
+
if (category) asset.category = category;
|
|
3443
|
+
const domainSlug = pickString2(r, "domainSlug");
|
|
3444
|
+
if (domainSlug) asset.domainSlug = domainSlug;
|
|
3445
|
+
return asset;
|
|
3507
3446
|
}
|
|
3508
|
-
function
|
|
3509
|
-
const
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
requiredActions.push(
|
|
3521
|
-
`Remove ${diff.removedItems.length} instance(s): ${diff.removedItems.map((i) => i.ref).join(", ")}`
|
|
3522
|
-
);
|
|
3523
|
-
}
|
|
3524
|
-
if (diff.changedRefItems.length > 0) {
|
|
3525
|
-
requiredActions.push(`Replace ${diff.changedRefItems.length} instance(s) with new refs`);
|
|
3447
|
+
function pickString2(r, key) {
|
|
3448
|
+
const v = r[key];
|
|
3449
|
+
return typeof v === "string" ? v : void 0;
|
|
3450
|
+
}
|
|
3451
|
+
function tryParseJson(s) {
|
|
3452
|
+
if (!s) return {};
|
|
3453
|
+
try {
|
|
3454
|
+
const parsed = JSON.parse(s);
|
|
3455
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
3456
|
+
return parsed;
|
|
3457
|
+
}
|
|
3458
|
+
} catch {
|
|
3526
3459
|
}
|
|
3527
|
-
return {
|
|
3460
|
+
return {};
|
|
3528
3461
|
}
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3462
|
+
var CATALOG_REQUEST_TIMEOUT_MS2 = 1e4;
|
|
3463
|
+
var defaultFetch2 = async (url, init) => {
|
|
3464
|
+
if (typeof fetch === "undefined") {
|
|
3465
|
+
throw new Error("global fetch is not available; pass an httpClient explicitly");
|
|
3466
|
+
}
|
|
3467
|
+
const requestInit = init ?? {};
|
|
3468
|
+
return fetch(url, {
|
|
3469
|
+
...requestInit,
|
|
3470
|
+
signal: requestInit.signal ?? AbortSignal.timeout(CATALOG_REQUEST_TIMEOUT_MS2)
|
|
3471
|
+
});
|
|
3472
|
+
};
|
|
3473
|
+
var MANIFEST_FILENAME = "skaile.manifest.yaml";
|
|
3474
|
+
async function writeManifestIfMissing(input) {
|
|
3475
|
+
const manifestPath = path5.join(input.libraryPath, MANIFEST_FILENAME);
|
|
3476
|
+
if (fs10.existsSync(manifestPath)) return { wrote: false, manifestPath };
|
|
3477
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3478
|
+
const yaml = `# Generated by skaile library add on ${now} (structure: ${input.structure})
|
|
3479
|
+
# Edit by hand to inline discovered assets; or run \`skaile library refresh-manifest\`.
|
|
3480
|
+
publisher: ""
|
|
3481
|
+
assets: []
|
|
3482
|
+
`;
|
|
3483
|
+
fs10.writeFileSync(manifestPath, yaml, "utf-8");
|
|
3484
|
+
if (input.ownership !== "owner") {
|
|
3485
|
+
const excludePath = path5.join(input.libraryPath, ".git", "info", "exclude");
|
|
3486
|
+
if (fs10.existsSync(path5.dirname(excludePath))) {
|
|
3487
|
+
const current = fs10.existsSync(excludePath) ? fs10.readFileSync(excludePath, "utf-8") : "";
|
|
3488
|
+
if (!current.includes(MANIFEST_FILENAME)) {
|
|
3489
|
+
fs10.writeFileSync(
|
|
3490
|
+
excludePath,
|
|
3491
|
+
(current.endsWith("\n") || current === "" ? current : `${current}
|
|
3492
|
+
`) + `# skaile-managed local-overlay manifest
|
|
3493
|
+
${MANIFEST_FILENAME}
|
|
3494
|
+
`,
|
|
3495
|
+
"utf-8"
|
|
3545
3496
|
);
|
|
3546
3497
|
}
|
|
3547
3498
|
}
|
|
3548
3499
|
}
|
|
3549
|
-
return {
|
|
3500
|
+
return { wrote: true, manifestPath };
|
|
3550
3501
|
}
|
|
3551
|
-
|
|
3552
|
-
|
|
3502
|
+
var PinPolicySchema = z3.enum(PIN_POLICIES);
|
|
3503
|
+
var LibraryInstanceRefSchema = z3.object({
|
|
3504
|
+
id: z3.string().uuid(),
|
|
3505
|
+
alias: z3.string().optional()
|
|
3506
|
+
});
|
|
3507
|
+
var LibrarySectionSchema = z3.object({
|
|
3508
|
+
pin_policy: PinPolicySchema.default("minor-track"),
|
|
3509
|
+
instances: z3.array(LibraryInstanceRefSchema).default([])
|
|
3510
|
+
});
|
|
3511
|
+
var PresetAppliedItemSchema = z3.object({
|
|
3512
|
+
instance_id: z3.string().uuid(),
|
|
3513
|
+
def_ref: z3.string()
|
|
3514
|
+
});
|
|
3515
|
+
var PresetAppliedSchema = z3.object({
|
|
3516
|
+
ref: z3.string(),
|
|
3517
|
+
applied_at: z3.string(),
|
|
3518
|
+
items: z3.array(PresetAppliedItemSchema).default([])
|
|
3519
|
+
});
|
|
3520
|
+
var WorkspaceConfigV2Schema = z3.object({
|
|
3521
|
+
version: z3.literal(2),
|
|
3522
|
+
library: LibrarySectionSchema.optional(),
|
|
3523
|
+
presets_applied: z3.array(PresetAppliedSchema).optional()
|
|
3524
|
+
});
|
|
3525
|
+
var LockAssignmentSchema = z3.object({
|
|
3526
|
+
assignment_id: z3.string().uuid(),
|
|
3527
|
+
instance_id: z3.string().uuid(),
|
|
3528
|
+
def_ref: z3.string(),
|
|
3529
|
+
resolved_version: z3.string(),
|
|
3530
|
+
resolved_sha256: z3.string().optional(),
|
|
3531
|
+
pin_policy: PinPolicySchema
|
|
3532
|
+
});
|
|
3533
|
+
var RequiresEdgeSchema = z3.object({
|
|
3534
|
+
from: z3.string(),
|
|
3535
|
+
to: z3.string(),
|
|
3536
|
+
kind: z3.string().optional()
|
|
3537
|
+
});
|
|
3538
|
+
var PresetExpandedSchema = z3.object({
|
|
3539
|
+
preset_ref: z3.string(),
|
|
3540
|
+
instance_ids: z3.array(z3.string().uuid())
|
|
3541
|
+
});
|
|
3542
|
+
var LockFileSchema = z3.object({
|
|
3543
|
+
version: z3.literal(2),
|
|
3544
|
+
generated_at: z3.string(),
|
|
3545
|
+
assignments: z3.array(LockAssignmentSchema).default([]),
|
|
3546
|
+
requires_graph: z3.array(RequiresEdgeSchema).default([]),
|
|
3547
|
+
presets_expanded: z3.array(PresetExpandedSchema).default([])
|
|
3548
|
+
});
|
|
3549
|
+
function parseWorkspaceV2(raw) {
|
|
3550
|
+
if (raw.version !== 2) return null;
|
|
3551
|
+
return WorkspaceConfigV2Schema.parse(raw);
|
|
3553
3552
|
}
|
|
3554
|
-
function
|
|
3555
|
-
const
|
|
3556
|
-
|
|
3557
|
-
|
|
3553
|
+
function loadWorkspaceV2(dir, filename = "skaile.yaml") {
|
|
3554
|
+
const filePath = path5.join(dir, filename);
|
|
3555
|
+
if (!fs10.existsSync(filePath)) return null;
|
|
3556
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
3557
|
+
const raw = parse(content);
|
|
3558
|
+
if (!raw || typeof raw !== "object") return null;
|
|
3559
|
+
return parseWorkspaceV2(raw);
|
|
3560
|
+
}
|
|
3561
|
+
function saveWorkspaceV2(dir, v2, filename = "skaile.yaml") {
|
|
3562
|
+
const filePath = path5.join(dir, filename);
|
|
3563
|
+
let existing = {};
|
|
3564
|
+
if (fs10.existsSync(filePath)) {
|
|
3565
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
3566
|
+
existing = parse(content) ?? {};
|
|
3558
3567
|
}
|
|
3559
|
-
|
|
3568
|
+
existing.version = 2;
|
|
3569
|
+
if (v2.library !== void 0) existing.library = v2.library;
|
|
3570
|
+
if (v2.presets_applied !== void 0) existing.presets_applied = v2.presets_applied;
|
|
3571
|
+
if (!fs10.existsSync(dir)) {
|
|
3572
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
3573
|
+
}
|
|
3574
|
+
fs10.writeFileSync(filePath, stringify(existing, { indent: 2 }), "utf-8");
|
|
3560
3575
|
}
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
if (
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3576
|
+
var LOCK_FILENAME = "skaile.lock.yaml";
|
|
3577
|
+
function loadLockFile(dir) {
|
|
3578
|
+
const filePath = path5.join(dir, LOCK_FILENAME);
|
|
3579
|
+
if (!fs10.existsSync(filePath)) return null;
|
|
3580
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
3581
|
+
const raw = parse(content);
|
|
3582
|
+
if (raw !== null && !("assignments" in raw) && "subscriptions" in raw) {
|
|
3583
|
+
const legacySubs = raw.subscriptions;
|
|
3584
|
+
raw.assignments = Array.isArray(legacySubs) ? legacySubs.map((s) => {
|
|
3585
|
+
if (s !== null && typeof s === "object" && "subscription_id" in s && !("assignment_id" in s)) {
|
|
3586
|
+
const { subscription_id, ...rest } = s;
|
|
3587
|
+
return { assignment_id: subscription_id, ...rest };
|
|
3588
|
+
}
|
|
3589
|
+
return s;
|
|
3590
|
+
}) : [];
|
|
3591
|
+
delete raw.subscriptions;
|
|
3592
|
+
}
|
|
3593
|
+
return LockFileSchema.parse(raw);
|
|
3594
|
+
}
|
|
3595
|
+
function saveLockFile(dir, lock) {
|
|
3596
|
+
const filePath = path5.join(dir, LOCK_FILENAME);
|
|
3597
|
+
if (!fs10.existsSync(dir)) {
|
|
3598
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
3599
|
+
}
|
|
3600
|
+
fs10.writeFileSync(
|
|
3601
|
+
filePath,
|
|
3602
|
+
`# Auto-generated by skaile. Do not edit manually.
|
|
3603
|
+
${stringify(lock, { indent: 2 })}`,
|
|
3604
|
+
"utf-8"
|
|
3605
|
+
);
|
|
3606
|
+
}
|
|
3607
|
+
function createEmptyLockFile() {
|
|
3608
|
+
return {
|
|
3609
|
+
version: 2,
|
|
3610
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3611
|
+
assignments: [],
|
|
3612
|
+
requires_graph: [],
|
|
3613
|
+
presets_expanded: []
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
function detectWorkspaceVersion(dir, filename = "skaile.yaml") {
|
|
3617
|
+
const filePath = path5.join(dir, filename);
|
|
3618
|
+
if (!fs10.existsSync(filePath)) return null;
|
|
3619
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
3620
|
+
const raw = parse(content);
|
|
3621
|
+
if (!raw || typeof raw !== "object") return null;
|
|
3622
|
+
return raw.version === 2 ? 2 : 1;
|
|
3568
3623
|
}
|
|
3569
3624
|
|
|
3570
3625
|
export { AssignmentNotFoundError, CatalogCache, CatalogConfigSchema, CatalogHttpError, DuplicateAssignmentError, DuplicateLibraryNameError, GitSyncDriver, InstanceHasConsumersError, InstanceNotFoundError, InterpolationError, KNOWN_ASSET_KINDS, KnowledgeManifestSchema, LIBRARY_BACKENDS, LIBRARY_OWNERSHIPS, LIBRARY_STRUCTURES, LOCAL_CATALOG_URL, LibraryConfigSchema, LibraryError, LibraryInstanceRefSchema, LibraryManager, LibrarySectionSchema, LibrarySyncError, LocalCatalogSource, LocalIndex, LocalSyncDriver, LockAssignmentSchema, LockFileSchema, OfflineError, OperationNotPermittedError, PIN_POLICIES, PinPolicySchema, PresetAppliedSchema, PresetExpandedSchema, RemoteCatalogSource, RequiresEdgeSchema, RestCatalogSource, SidecarGit, SkaileConfigSchema, SourceNotFoundError, TarballHashMismatchError, UserLibraryNotFoundError, WorkspaceConfigV2Schema, applyNonStructuralUpgrade, applyPreset, applyPresetFromData, classifyRef, createEmptyLockFile, deriveSlug, detectChanges, detectNestedPresetRefs, detectWorkspaceVersion, evaluateUpgrade, extractLibraryInstanceId, extractReferencedKeys, filterKey, getConfigDefaults, getDefaultValue, getSidecarRoot, getStoreManifestsDir, getStoreRoot, hasInterpolationTokens, interpolate, isLocalCatalogUrl, isSecretPlaceholder, knowledgeKindProvider, listSidecarSlugsOnDisk, loadLockFile, loadWorkspaceV2, migrateSidecarsToStoreIfNeeded, migrateWorkspaceConfig, parsePreset, parsePresetYaml, parseWorkspaceV2, projectConfigPath, queryInstancePickerChoices, resolveAllItems, resolveConfig, resolveIndexPath, resolveItem, resolveLibraryDir, resolvePin, resolveSidecarPaths, saveConfig, saveLockFile, saveWorkspaceV2, sha256Hex, skaileHomeDir, storeSecrets, trpcGetUrl, userConfigPath, validateAllPlaceholders, validateKnowledge, validateNestingDepth, validatePlaceholder, writeManifestIfMissing };
|
|
3571
|
-
//# sourceMappingURL=chunk-
|
|
3572
|
-
//# sourceMappingURL=chunk-
|
|
3626
|
+
//# sourceMappingURL=chunk-LA2YSXEE.js.map
|
|
3627
|
+
//# sourceMappingURL=chunk-LA2YSXEE.js.map
|