opkg 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -186
- package/assets/openpackage_ascii_dark.png +0 -0
- package/assets/openpackage_ascii_light.png +0 -0
- package/dist/commands/add.js +34 -10
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/apply.js +16 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/delete.js +1 -1
- package/dist/commands/delete.js.map +1 -1
- package/dist/commands/install.js +177 -8
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/list.js +2 -2
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.js +1 -1
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/new.js +125 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/pack.js +7 -13
- package/dist/commands/pack.js.map +1 -1
- package/dist/commands/pull.js +1 -1
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.js +1 -1
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/remove.js +63 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/save.js +11 -17
- package/dist/commands/save.js.map +1 -1
- package/dist/commands/set.js +33 -0
- package/dist/commands/set.js.map +1 -0
- package/dist/commands/show.js +16 -94
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/status.js +26 -701
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/uninstall.js +14 -427
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/constants/index.js +72 -16
- package/dist/constants/index.js.map +1 -1
- package/dist/core/add/add-conflict-handler.js +1 -8
- package/dist/core/add/add-conflict-handler.js.map +1 -1
- package/dist/core/add/add-pipeline.js +12 -10
- package/dist/core/add/add-pipeline.js.map +1 -1
- package/dist/core/add/add-to-source-pipeline.js +123 -0
- package/dist/core/add/add-to-source-pipeline.js.map +1 -0
- package/dist/core/add/package-index-updater.js +77 -78
- package/dist/core/add/package-index-updater.js.map +1 -1
- package/dist/core/add/platform-path-transformer.js +6 -4
- package/dist/core/add/platform-path-transformer.js.map +1 -1
- package/dist/core/add/source-collector.js +2 -3
- package/dist/core/add/source-collector.js.map +1 -1
- package/dist/core/apply/apply-pipeline.js +110 -0
- package/dist/core/apply/apply-pipeline.js.map +1 -0
- package/dist/core/dependency-resolver.js +263 -21
- package/dist/core/dependency-resolver.js.map +1 -1
- package/dist/core/discovery/file-discovery.js +1 -2
- package/dist/core/discovery/file-discovery.js.map +1 -1
- package/dist/core/discovery/platform-files-discovery.js +33 -18
- package/dist/core/discovery/platform-files-discovery.js.map +1 -1
- package/dist/core/flows/flow-executor.js +974 -0
- package/dist/core/flows/flow-executor.js.map +1 -0
- package/dist/core/flows/flow-inverter.js +442 -0
- package/dist/core/flows/flow-inverter.js.map +1 -0
- package/dist/core/flows/flow-key-extractor.js +101 -0
- package/dist/core/flows/flow-key-extractor.js.map +1 -0
- package/dist/core/flows/flow-key-mapper.js +382 -0
- package/dist/core/flows/flow-key-mapper.js.map +1 -0
- package/dist/core/flows/flow-transforms.js +632 -0
- package/dist/core/flows/flow-transforms.js.map +1 -0
- package/dist/core/flows/map-pipeline/context.js +73 -0
- package/dist/core/flows/map-pipeline/context.js.map +1 -0
- package/dist/core/flows/map-pipeline/index.js +156 -0
- package/dist/core/flows/map-pipeline/index.js.map +1 -0
- package/dist/core/flows/map-pipeline/operations/copy.js +104 -0
- package/dist/core/flows/map-pipeline/operations/copy.js.map +1 -0
- package/dist/core/flows/map-pipeline/operations/pipe.js +70 -0
- package/dist/core/flows/map-pipeline/operations/pipe.js.map +1 -0
- package/dist/core/flows/map-pipeline/operations/rename.js +102 -0
- package/dist/core/flows/map-pipeline/operations/rename.js.map +1 -0
- package/dist/core/flows/map-pipeline/operations/set.js +50 -0
- package/dist/core/flows/map-pipeline/operations/set.js.map +1 -0
- package/dist/core/flows/map-pipeline/operations/switch.js +79 -0
- package/dist/core/flows/map-pipeline/operations/switch.js.map +1 -0
- package/dist/core/flows/map-pipeline/operations/transform.js +543 -0
- package/dist/core/flows/map-pipeline/operations/transform.js.map +1 -0
- package/dist/core/flows/map-pipeline/operations/unset.js +65 -0
- package/dist/core/flows/map-pipeline/operations/unset.js.map +1 -0
- package/dist/core/flows/map-pipeline/types.js +8 -0
- package/dist/core/flows/map-pipeline/types.js.map +1 -0
- package/dist/core/flows/map-pipeline/utils.js +278 -0
- package/dist/core/flows/map-pipeline/utils.js.map +1 -0
- package/dist/core/flows/platform-converter.js +328 -0
- package/dist/core/flows/platform-converter.js.map +1 -0
- package/dist/core/flows/source-resolver.js +192 -0
- package/dist/core/flows/source-resolver.js.map +1 -0
- package/dist/core/flows/toml-domain-transforms.js +23 -0
- package/dist/core/flows/toml-domain-transforms.js.map +1 -0
- package/dist/core/install/bulk-install-pipeline.js +68 -7
- package/dist/core/install/bulk-install-pipeline.js.map +1 -1
- package/dist/core/install/canonical-plan.js +3 -3
- package/dist/core/install/canonical-plan.js.map +1 -1
- package/dist/core/install/dry-run.js +3 -3
- package/dist/core/install/dry-run.js.map +1 -1
- package/dist/core/install/flow-based-installer.js +1158 -0
- package/dist/core/install/flow-based-installer.js.map +1 -0
- package/dist/core/install/flow-workspace-tracker.js +111 -0
- package/dist/core/install/flow-workspace-tracker.js.map +1 -0
- package/dist/core/install/format-detector.js +228 -0
- package/dist/core/install/format-detector.js.map +1 -0
- package/dist/core/install/git-package-loader.js +20 -0
- package/dist/core/install/git-package-loader.js.map +1 -0
- package/dist/core/install/install-errors.js +1 -1
- package/dist/core/install/install-errors.js.map +1 -1
- package/dist/core/install/install-flow.js +34 -14
- package/dist/core/install/install-flow.js.map +1 -1
- package/dist/core/install/install-pipeline.js +52 -17
- package/dist/core/install/install-pipeline.js.map +1 -1
- package/dist/core/install/install-reporting.js +26 -8
- package/dist/core/install/install-reporting.js.map +1 -1
- package/dist/core/install/local-source-resolution.js +103 -0
- package/dist/core/install/local-source-resolution.js.map +1 -0
- package/dist/core/install/marketplace-handler.js +221 -0
- package/dist/core/install/marketplace-handler.js.map +1 -0
- package/dist/core/install/path-install-pipeline.js +241 -0
- package/dist/core/install/path-install-pipeline.js.map +1 -0
- package/dist/core/install/path-package-loader.js +116 -0
- package/dist/core/install/path-package-loader.js.map +1 -0
- package/dist/core/install/plugin-detector.js +72 -0
- package/dist/core/install/plugin-detector.js.map +1 -0
- package/dist/core/install/plugin-to-universal-converter.js +218 -0
- package/dist/core/install/plugin-to-universal-converter.js.map +1 -0
- package/dist/core/install/plugin-transformer.js +191 -0
- package/dist/core/install/plugin-transformer.js.map +1 -0
- package/dist/core/install/version-selection.js +1 -1
- package/dist/core/install/version-selection.js.map +1 -1
- package/dist/core/openpackage.js +40 -22
- package/dist/core/openpackage.js.map +1 -1
- package/dist/core/pack/pack-output.js +62 -0
- package/dist/core/pack/pack-output.js.map +1 -0
- package/dist/core/pack/pack-pipeline.js +186 -0
- package/dist/core/pack/pack-pipeline.js.map +1 -0
- package/dist/core/package-context.js +45 -70
- package/dist/core/package-context.js.map +1 -1
- package/dist/core/package-creation.js +203 -0
- package/dist/core/package-creation.js.map +1 -0
- package/dist/core/package.js +20 -6
- package/dist/core/package.js.map +1 -1
- package/dist/core/platforms.js +665 -209
- package/dist/core/platforms.js.map +1 -1
- package/dist/core/push/push-context.js +1 -1
- package/dist/core/push/push-context.js.map +1 -1
- package/dist/core/push/push-upload.js +2 -2
- package/dist/core/push/push-upload.js.map +1 -1
- package/dist/core/registry.js +6 -6
- package/dist/core/registry.js.map +1 -1
- package/dist/core/remote-pull.js +2 -2
- package/dist/core/remote-pull.js.map +1 -1
- package/dist/core/remove/removal-collector.js +52 -0
- package/dist/core/remove/removal-collector.js.map +1 -0
- package/dist/core/remove/removal-confirmation.js +39 -0
- package/dist/core/remove/removal-confirmation.js.map +1 -0
- package/dist/core/remove/remove-from-source-pipeline.js +173 -0
- package/dist/core/remove/remove-from-source-pipeline.js.map +1 -0
- package/dist/core/save/constants.js +3 -3
- package/dist/core/save/constants.js.map +1 -1
- package/dist/core/save/flow-based-saver.js +270 -0
- package/dist/core/save/flow-based-saver.js.map +1 -0
- package/dist/core/save/name-resolution.js +1 -1
- package/dist/core/save/name-resolution.js.map +1 -1
- package/dist/core/save/package-yml-generator.js +4 -5
- package/dist/core/save/package-yml-generator.js.map +1 -1
- package/dist/core/save/save-candidate-builder.js +215 -0
- package/dist/core/save/save-candidate-builder.js.map +1 -0
- package/dist/core/save/save-candidate-loader.js +12 -11
- package/dist/core/save/save-candidate-loader.js.map +1 -1
- package/dist/core/save/save-conflict-analyzer.js +150 -0
- package/dist/core/save/save-conflict-analyzer.js.map +1 -0
- package/dist/core/save/save-conflict-resolution.js +28 -14
- package/dist/core/save/save-conflict-resolution.js.map +1 -1
- package/dist/core/save/save-conflict-resolver.js +31 -275
- package/dist/core/save/save-conflict-resolver.js.map +1 -1
- package/dist/core/save/save-group-builder.js +52 -0
- package/dist/core/save/save-group-builder.js.map +1 -0
- package/dist/core/save/save-interactive-resolver.js +190 -0
- package/dist/core/save/save-interactive-resolver.js.map +1 -0
- package/dist/core/save/save-pipeline.js +58 -34
- package/dist/core/save/save-pipeline.js.map +1 -1
- package/dist/core/save/save-platform-handler.js +53 -0
- package/dist/core/save/save-platform-handler.js.map +1 -0
- package/dist/core/save/save-resolution-executor.js +145 -0
- package/dist/core/save/save-resolution-executor.js.map +1 -0
- package/dist/core/save/save-result-reporter.js +167 -0
- package/dist/core/save/save-result-reporter.js.map +1 -0
- package/dist/core/save/save-to-source-pipeline.js +154 -0
- package/dist/core/save/save-to-source-pipeline.js.map +1 -0
- package/dist/core/save/save-versioning.js +4 -4
- package/dist/core/save/save-versioning.js.map +1 -1
- package/dist/core/save/save-write-coordinator.js +204 -0
- package/dist/core/save/save-write-coordinator.js.map +1 -0
- package/dist/core/save/save-yml-resolution.js +28 -216
- package/dist/core/save/save-yml-resolution.js.map +1 -1
- package/dist/core/save/workspace-rename.js +7 -8
- package/dist/core/save/workspace-rename.js.map +1 -1
- package/dist/core/set/set-output.js +72 -0
- package/dist/core/set/set-output.js.map +1 -0
- package/dist/core/set/set-pipeline.js +361 -0
- package/dist/core/set/set-pipeline.js.map +1 -0
- package/dist/core/set/set-types.js +5 -0
- package/dist/core/set/set-types.js.map +1 -0
- package/dist/core/show/package-resolver.js +257 -0
- package/dist/core/show/package-resolver.js.map +1 -0
- package/dist/core/show/scope-discovery.js +165 -0
- package/dist/core/show/scope-discovery.js.map +1 -0
- package/dist/core/show/show-output.js +168 -0
- package/dist/core/show/show-output.js.map +1 -0
- package/dist/core/show/show-pipeline.js +113 -0
- package/dist/core/show/show-pipeline.js.map +1 -0
- package/dist/core/show/show-types.js +5 -0
- package/dist/core/show/show-types.js.map +1 -0
- package/dist/core/source-resolution/dependency-graph.js +104 -0
- package/dist/core/source-resolution/dependency-graph.js.map +1 -0
- package/dist/core/source-resolution/resolve-mutable-source.js +109 -0
- package/dist/core/source-resolution/resolve-mutable-source.js.map +1 -0
- package/dist/core/source-resolution/resolve-package-source.js +29 -0
- package/dist/core/source-resolution/resolve-package-source.js.map +1 -0
- package/dist/core/source-resolution/resolve-registry-version.js +35 -0
- package/dist/core/source-resolution/resolve-registry-version.js.map +1 -0
- package/dist/core/source-resolution/types.js.map +1 -0
- package/dist/core/status/status-file-discovery.js +23 -12
- package/dist/core/status/status-file-discovery.js.map +1 -1
- package/dist/core/status/status-pipeline.js +134 -0
- package/dist/core/status/status-pipeline.js.map +1 -0
- package/dist/core/sync/platform-sync-summary.js +27 -0
- package/dist/core/sync/platform-sync-summary.js.map +1 -0
- package/dist/core/uninstall/flow-aware-uninstaller.js +189 -0
- package/dist/core/uninstall/flow-aware-uninstaller.js.map +1 -0
- package/dist/core/uninstall/uninstall-file-discovery.js +11 -6
- package/dist/core/uninstall/uninstall-file-discovery.js.map +1 -1
- package/dist/core/uninstall/uninstall-pipeline.js +141 -0
- package/dist/core/uninstall/uninstall-pipeline.js.map +1 -0
- package/dist/core/universal-patterns.js +64 -0
- package/dist/core/universal-patterns.js.map +1 -0
- package/dist/index.js +99 -6
- package/dist/index.js.map +1 -1
- package/dist/types/flows.js +8 -0
- package/dist/types/flows.js.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/platform-flows.js +8 -0
- package/dist/types/platform-flows.js.map +1 -0
- package/dist/types/workspace-index.js +6 -0
- package/dist/types/workspace-index.js.map +1 -0
- package/dist/utils/custom-path-resolution.js +160 -0
- package/dist/utils/custom-path-resolution.js.map +1 -0
- package/dist/utils/dependency-coverage.js +1 -1
- package/dist/utils/dependency-coverage.js.map +1 -1
- package/dist/utils/file-processing.js +1 -1
- package/dist/utils/flow-index-installer.js +209 -0
- package/dist/utils/flow-index-installer.js.map +1 -0
- package/dist/utils/formatters.js +47 -1
- package/dist/utils/formatters.js.map +1 -1
- package/dist/utils/fs.js +17 -0
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/git-clone-registry.js +88 -0
- package/dist/utils/git-clone-registry.js.map +1 -0
- package/dist/utils/git-clone.js +69 -0
- package/dist/utils/git-clone.js.map +1 -0
- package/dist/utils/git-spec.js +96 -0
- package/dist/utils/git-spec.js.map +1 -0
- package/dist/utils/http-client.js +7 -0
- package/dist/utils/http-client.js.map +1 -1
- package/dist/utils/index-based-installer.js +356 -163
- package/dist/utils/index-based-installer.js.map +1 -1
- package/dist/utils/install-conflict-handler.js +2 -2
- package/dist/utils/install-conflict-handler.js.map +1 -1
- package/dist/utils/install-file-discovery.js +18 -13
- package/dist/utils/install-file-discovery.js.map +1 -1
- package/dist/utils/install-helpers.js +43 -20
- package/dist/utils/install-helpers.js.map +1 -1
- package/dist/utils/jsonc.js +23 -1
- package/dist/utils/jsonc.js.map +1 -1
- package/dist/utils/manifest-paths.js +1 -1
- package/dist/utils/manifest-paths.js.map +1 -1
- package/dist/utils/markdown-frontmatter.js +46 -0
- package/dist/utils/markdown-frontmatter.js.map +1 -1
- package/dist/utils/package-copy.js +5 -103
- package/dist/utils/package-copy.js.map +1 -1
- package/dist/utils/package-filters.js +9 -105
- package/dist/utils/package-filters.js.map +1 -1
- package/dist/utils/package-index-yml.js +27 -6
- package/dist/utils/package-index-yml.js.map +1 -1
- package/dist/utils/package-input.js +98 -0
- package/dist/utils/package-input.js.map +1 -0
- package/dist/utils/package-management.js +80 -28
- package/dist/utils/package-management.js.map +1 -1
- package/dist/utils/package-name-resolution.js +327 -0
- package/dist/utils/package-name-resolution.js.map +1 -0
- package/dist/utils/package-name.js +18 -16
- package/dist/utils/package-name.js.map +1 -1
- package/dist/utils/package-versioning.js +2 -33
- package/dist/utils/package-versioning.js.map +1 -1
- package/dist/utils/package-yml.js +19 -28
- package/dist/utils/package-yml.js.map +1 -1
- package/dist/utils/path-resolution.js +102 -0
- package/dist/utils/path-resolution.js.map +1 -0
- package/dist/utils/paths.js +6 -6
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/platform-file.js +36 -24
- package/dist/utils/platform-file.js.map +1 -1
- package/dist/utils/platform-mapper.js +222 -68
- package/dist/utils/platform-mapper.js.map +1 -1
- package/dist/utils/platform-root-files.js +44 -0
- package/dist/utils/platform-root-files.js.map +1 -0
- package/dist/utils/platform-utils.js +35 -54
- package/dist/utils/platform-utils.js.map +1 -1
- package/dist/utils/platform-yaml-merge.js +20 -140
- package/dist/utils/platform-yaml-merge.js.map +1 -1
- package/dist/utils/prompts.js +92 -7
- package/dist/utils/prompts.js.map +1 -1
- package/dist/utils/registry-entry-filter.js +50 -27
- package/dist/utils/registry-entry-filter.js.map +1 -1
- package/dist/utils/registry-paths.js +5 -4
- package/dist/utils/registry-paths.js.map +1 -1
- package/dist/utils/scope-resolution.js +156 -0
- package/dist/utils/scope-resolution.js.map +1 -0
- package/dist/utils/source-mutability.js +15 -0
- package/dist/utils/source-mutability.js.map +1 -0
- package/dist/utils/tarball.js +29 -4
- package/dist/utils/tarball.js.map +1 -1
- package/dist/utils/version-ranges.js +1 -32
- package/dist/utils/version-ranges.js.map +1 -1
- package/dist/utils/workspace-index-helpers.js +28 -0
- package/dist/utils/workspace-index-helpers.js.map +1 -0
- package/dist/utils/workspace-index-ownership.js +100 -0
- package/dist/utils/workspace-index-ownership.js.map +1 -0
- package/dist/utils/workspace-index-yml.js +173 -0
- package/dist/utils/workspace-index-yml.js.map +1 -0
- package/examples/custom-subdirs-platform.jsonc +157 -0
- package/package.json +7 -2
- package/platforms.jsonc +531 -84
- package/schemas/map-pipeline-v1.json +256 -0
- package/schemas/platforms-v1.json +400 -0
- package/specs/README.md +88 -0
- package/specs/add/README.md +166 -0
- package/specs/agents-claude.md +570 -0
- package/specs/agents-opencode.md +622 -0
- package/specs/apply/README.md +21 -0
- package/specs/apply/apply-behavior.md +58 -0
- package/specs/apply/apply-command.md +51 -0
- package/specs/apply/conflicts.md +41 -0
- package/specs/apply/index-effects.md +81 -0
- package/specs/architecture.md +107 -0
- package/specs/auth/README.md +17 -0
- package/specs/auth/auth-http-contract.md +25 -0
- package/specs/auth/cli/credentials.md +39 -0
- package/specs/auth/cli/login.md +32 -0
- package/specs/auth/cli/logout.md +16 -0
- package/specs/claude-mcp.md +1065 -0
- package/specs/claude-plugins-marketplace.md +363 -0
- package/specs/claude-plugins.md +413 -0
- package/specs/cli-options.md +52 -0
- package/specs/codex-mcp.md +114 -0
- package/specs/commands-overview.md +175 -0
- package/specs/directory-layout.md +95 -0
- package/specs/install/README.md +12 -4
- package/specs/install/git-sources.md +230 -0
- package/specs/install/install-behavior.md +483 -73
- package/specs/install/package-yml-canonical.md +67 -35
- package/specs/install/version-resolution.md +69 -115
- package/specs/new/README.md +769 -0
- package/specs/new/SUMMARY.md +310 -0
- package/specs/new/scope-behavior.md +793 -0
- package/specs/pack/README.md +77 -0
- package/specs/pack/package-name-resolution.md +330 -0
- package/specs/package/README.md +18 -17
- package/specs/package/nested-packages-and-parent-packages.md +32 -31
- package/specs/package/package-index-yml.md +95 -101
- package/specs/package/package-root-layout.md +64 -46
- package/specs/package/registry-payload-and-copy.md +50 -44
- package/specs/package/universal-content.md +33 -56
- package/specs/package-sources.md +248 -0
- package/specs/platforms/README.md +52 -0
- package/specs/platforms/configuration.md +571 -0
- package/specs/platforms/detection.md +552 -0
- package/specs/platforms/directory-layout.md +599 -0
- package/specs/platforms/examples.md +1146 -0
- package/specs/platforms/flow-reference.md +1240 -0
- package/specs/platforms/flows.md +1488 -0
- package/specs/platforms/map-pipeline.md +801 -0
- package/specs/platforms/overview.md +349 -0
- package/specs/platforms/specification.md +700 -0
- package/specs/platforms/troubleshooting.md +697 -0
- package/specs/platforms/universal-converter.md +520 -0
- package/specs/push/README.md +1 -0
- package/specs/push/push-behavior.md +11 -3
- package/specs/push/push-remote-upload.md +1 -1
- package/specs/push/push-scoping.md +1 -1
- package/specs/push/push-version-selection.md +1 -1
- package/specs/registry.md +111 -0
- package/specs/remove/README.md +257 -0
- package/specs/save/README.md +21 -17
- package/specs/save/save-conflict-resolution.md +205 -83
- package/specs/save/save-file-discovery.md +6 -4
- package/specs/save/save-frontmatter-overrides.md +11 -15
- package/specs/save/save-modes-inputs.md +9 -39
- package/specs/save/save-naming-scoping.md +4 -4
- package/specs/save/save-package-detection.md +13 -13
- package/specs/save/save-registry-sync.md +16 -106
- package/specs/save/save-versioning.md +80 -0
- package/specs/scope-management.md +92 -0
- package/specs/set/README.md +520 -0
- package/specs/set/set-behavior.md +563 -0
- package/specs/show/README.md +483 -0
- package/specs/show/show-remote.md +494 -0
- package/specs/status/README.md +38 -0
- package/specs/uninstall/README.md +231 -0
- package/dist/commands/duplicate.js +0 -69
- package/dist/commands/duplicate.js.map +0 -1
- package/dist/commands/init.js +0 -117
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/prune.js +0 -357
- package/dist/commands/prune.js.map +0 -1
- package/dist/commands/tui.js +0 -61
- package/dist/commands/tui.js.map +0 -1
- package/dist/core/install/index.js +0 -3
- package/dist/core/install/index.js.map +0 -1
- package/dist/core/push/push-single-file.js +0 -56
- package/dist/core/push/push-single-file.js.map +0 -1
- package/dist/core/save/package-detection.js +0 -147
- package/dist/core/save/package-detection.js.map +0 -1
- package/dist/core/save/save-single-file.js +0 -124
- package/dist/core/save/save-single-file.js.map +0 -1
- package/dist/core/token-store.js +0 -73
- package/dist/core/token-store.js.map +0 -1
- package/dist/tui/app.js +0 -95
- package/dist/tui/app.js.map +0 -1
- package/dist/tui/components/package-list.js +0 -73
- package/dist/tui/components/package-list.js.map +0 -1
- package/dist/tui/controller.js +0 -365
- package/dist/tui/controller.js.map +0 -1
- package/dist/tui/index.js +0 -12
- package/dist/tui/index.js.map +0 -1
- package/dist/tui/services/file-index.js +0 -64
- package/dist/tui/services/file-index.js.map +0 -1
- package/dist/tui/services/packages.js +0 -18
- package/dist/tui/services/packages.js.map +0 -1
- package/dist/tui/services/save.js +0 -21
- package/dist/tui/services/save.js.map +0 -1
- package/dist/tui/state/app-state.js +0 -15
- package/dist/tui/state/app-state.js.map +0 -1
- package/dist/tui/state.js +0 -17
- package/dist/tui/state.js.map +0 -1
- package/dist/tui/types.js.map +0 -1
- package/dist/tui/views/add-file-modal.js +0 -129
- package/dist/tui/views/add-file-modal.js.map +0 -1
- package/dist/tui/views/file-preview.js +0 -44
- package/dist/tui/views/file-preview.js.map +0 -1
- package/dist/tui/views/list-packages.js +0 -73
- package/dist/tui/views/list-packages.js.map +0 -1
- package/dist/tui/views/main-menu.js +0 -29
- package/dist/tui/views/main-menu.js.map +0 -1
- package/dist/tui/views/manage-view.js +0 -81
- package/dist/tui/views/manage-view.js.map +0 -1
- package/dist/tui/views/package-hub.js +0 -120
- package/dist/tui/views/package-hub.js.map +0 -1
- package/dist/tui/views/placeholder.js +0 -24
- package/dist/tui/views/placeholder.js.map +0 -1
- package/dist/utils/bun-bootstrap.js +0 -72
- package/dist/utils/bun-bootstrap.js.map +0 -1
- package/dist/utils/entity-id.js +0 -19
- package/dist/utils/entity-id.js.map +0 -1
- package/dist/utils/package-local-files.js +0 -5
- package/dist/utils/package-local-files.js.map +0 -1
- package/dist/utils/path-matching.js +0 -74
- package/dist/utils/path-matching.js.map +0 -1
- package/dist/utils/root-file-operations.js +0 -39
- package/dist/utils/root-file-operations.js.map +0 -1
- package/dist/utils/root-file-transformer.js +0 -27
- package/dist/utils/root-file-transformer.js.map +0 -1
- package/dist/utils/yaml-frontmatter.js +0 -25
- package/dist/utils/yaml-frontmatter.js.map +0 -1
- package/specs/auth/auth-device-flow.md +0 -70
- package/specs/login/login-device-flow.md +0 -70
- package/specs/platforms.md +0 -193
- package/specs/save-pack-versioning.md +0 -224
- package/specs/save-pack.md +0 -68
- /package/dist/{tui → core/source-resolution}/types.js +0 -0
package/dist/core/platforms.js
CHANGED
|
@@ -2,219 +2,658 @@
|
|
|
2
2
|
* Platform Management Module
|
|
3
3
|
* Centralized platform definitions, directory mappings, and file patterns
|
|
4
4
|
* for all 13 supported AI coding platforms
|
|
5
|
+
*
|
|
6
|
+
* Now supports flow-based configurations for declarative transformations.
|
|
5
7
|
*/
|
|
6
|
-
import { join, relative } from
|
|
7
|
-
import { exists, ensureDir } from
|
|
8
|
-
import { logger } from
|
|
9
|
-
import { getPathLeaf } from
|
|
10
|
-
import { DIR_PATTERNS, FILE_PATTERNS,
|
|
11
|
-
import { mapPlatformFileToUniversal } from
|
|
12
|
-
import { parseUniversalPath } from
|
|
13
|
-
import { readJsoncFileSync } from
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const normalized = {};
|
|
22
|
-
for (const [subdirKey, subdirConfig] of Object.entries(subdirs)) {
|
|
23
|
-
// Validate that the subdir key is a valid universal subdir
|
|
24
|
-
if (!isValidUniversalSubdir(subdirKey)) {
|
|
25
|
-
logger.warn(`Invalid universal subdir key in platforms.jsonc: ${subdirKey}`);
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
// Skip if subdirConfig is undefined
|
|
29
|
-
if (!subdirConfig) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
normalized[subdirKey] = subdirConfig;
|
|
33
|
-
}
|
|
34
|
-
return normalized;
|
|
8
|
+
import { join, relative } from "path";
|
|
9
|
+
import { exists, ensureDir } from "../utils/fs.js";
|
|
10
|
+
import { logger } from "../utils/logger.js";
|
|
11
|
+
import { getPathLeaf } from "../utils/path-normalization.js";
|
|
12
|
+
import { DIR_PATTERNS, FILE_PATTERNS, } from "../constants/index.js";
|
|
13
|
+
import { mapPlatformFileToUniversal } from "../utils/platform-mapper.js";
|
|
14
|
+
import { parseUniversalPath } from "../utils/platform-file.js";
|
|
15
|
+
import { readJsoncFileSync, readJsoncOrJson } from "../utils/jsonc.js";
|
|
16
|
+
import * as os from "os";
|
|
17
|
+
import { matchesAnyPattern, extractSubdirectoriesFromPatterns } from "./universal-patterns.js";
|
|
18
|
+
/**
|
|
19
|
+
* Check if a config entry is a global flows config
|
|
20
|
+
*/
|
|
21
|
+
function isGlobalFlowsConfig(cfg) {
|
|
22
|
+
return ('export' in cfg || 'import' in cfg) && !('name' in cfg) && !('rootDir' in cfg);
|
|
35
23
|
}
|
|
36
24
|
/**
|
|
37
|
-
*
|
|
25
|
+
* Create platform definitions from a PlatformsConfig object
|
|
26
|
+
* Export/import flows configuration (no legacy subdirs support)
|
|
27
|
+
* Assumes config is already validated.
|
|
28
|
+
* @param config - The merged platforms configuration
|
|
38
29
|
*/
|
|
39
|
-
function
|
|
40
|
-
const raw = readJsoncFileSync('platforms.jsonc');
|
|
30
|
+
function createPlatformDefinitions(config) {
|
|
41
31
|
const result = {};
|
|
42
|
-
for (const [id, cfg] of Object.entries(
|
|
32
|
+
for (const [id, cfg] of Object.entries(config)) {
|
|
33
|
+
// Skip special keys
|
|
34
|
+
if (id === '$schema')
|
|
35
|
+
continue;
|
|
36
|
+
// Skip global config entry
|
|
37
|
+
if (id === 'global')
|
|
38
|
+
continue;
|
|
39
|
+
// Type guard for platform config
|
|
40
|
+
if (isGlobalFlowsConfig(cfg))
|
|
41
|
+
continue;
|
|
42
|
+
// Now we know cfg is PlatformConfig
|
|
43
|
+
const platformConfig = cfg;
|
|
43
44
|
const platformId = id;
|
|
44
45
|
result[platformId] = {
|
|
45
46
|
id: platformId,
|
|
46
|
-
name:
|
|
47
|
-
rootDir:
|
|
48
|
-
rootFile:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
name: platformConfig.name,
|
|
48
|
+
rootDir: platformConfig.rootDir,
|
|
49
|
+
rootFile: platformConfig.rootFile,
|
|
50
|
+
export: platformConfig.export || [],
|
|
51
|
+
import: platformConfig.import || [],
|
|
52
|
+
aliases: platformConfig.aliases,
|
|
53
|
+
enabled: platformConfig.enabled !== false,
|
|
54
|
+
description: platformConfig.description,
|
|
55
|
+
variables: platformConfig.variables,
|
|
52
56
|
};
|
|
53
57
|
}
|
|
54
58
|
return result;
|
|
55
59
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*/
|
|
65
|
-
export
|
|
66
|
-
const
|
|
67
|
-
for (const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
60
|
+
const BUILT_IN_CONFIG = readJsoncFileSync("platforms.jsonc");
|
|
61
|
+
const builtinErrors = validatePlatformsConfig(BUILT_IN_CONFIG);
|
|
62
|
+
if (builtinErrors.length > 0) {
|
|
63
|
+
throw new Error(`Built-in platforms.jsonc validation failed:\n - ${builtinErrors.join('\n - ')}`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Merge two PlatformsConfig objects, handling per-platform fields and subdirs arrays properly.
|
|
67
|
+
* Adds new platforms from override; merges existing with override preferences.
|
|
68
|
+
*/
|
|
69
|
+
export function mergePlatformsConfig(base, override) {
|
|
70
|
+
const merged = { ...base };
|
|
71
|
+
for (const [platformId, overridePlat] of Object.entries(override)) {
|
|
72
|
+
// Skip special keys
|
|
73
|
+
if (platformId === '$schema') {
|
|
74
|
+
merged[platformId] = overridePlat;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const basePlat = base[platformId];
|
|
78
|
+
if (!basePlat) {
|
|
79
|
+
merged[platformId] = overridePlat;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Handle global config separately
|
|
83
|
+
if (platformId === 'global') {
|
|
84
|
+
if (isGlobalFlowsConfig(overridePlat)) {
|
|
85
|
+
merged[platformId] = overridePlat; // Replace global config entirely
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Type guard for platform config
|
|
90
|
+
if (isGlobalFlowsConfig(overridePlat) || isGlobalFlowsConfig(basePlat)) {
|
|
91
|
+
merged[platformId] = overridePlat;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// Now we know both are PlatformConfig
|
|
95
|
+
const overrideCfg = overridePlat;
|
|
96
|
+
const baseCfg = basePlat;
|
|
97
|
+
merged[platformId] = {
|
|
98
|
+
name: overrideCfg.name ?? baseCfg.name,
|
|
99
|
+
rootDir: overrideCfg.rootDir ?? baseCfg.rootDir,
|
|
100
|
+
rootFile: overrideCfg.rootFile ?? baseCfg.rootFile,
|
|
101
|
+
aliases: overrideCfg.aliases ?? baseCfg.aliases, // replace array
|
|
102
|
+
enabled: overrideCfg.enabled ?? baseCfg.enabled,
|
|
103
|
+
description: overrideCfg.description ?? baseCfg.description,
|
|
104
|
+
variables: overrideCfg.variables ?? baseCfg.variables,
|
|
105
|
+
export: overrideCfg.export ?? baseCfg.export, // replace array (no merge)
|
|
106
|
+
import: overrideCfg.import ?? baseCfg.import, // replace array (no merge)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return merged;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Validate a PlatformsConfig object and return any validation errors.
|
|
113
|
+
* Export/import flows configuration (no legacy subdirs support).
|
|
114
|
+
* @param config - The config to validate
|
|
115
|
+
* @returns Array of error messages; empty if valid
|
|
116
|
+
*/
|
|
117
|
+
export function validatePlatformsConfig(config) {
|
|
118
|
+
const errors = [];
|
|
119
|
+
for (const [platformId, platConfig] of Object.entries(config)) {
|
|
120
|
+
// Skip special keys
|
|
121
|
+
if (platformId === '$schema') {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
// Skip global config - validate separately
|
|
125
|
+
if (platformId === 'global') {
|
|
126
|
+
if (isGlobalFlowsConfig(platConfig)) {
|
|
127
|
+
errors.push(...validateGlobalFlowsConfig(platConfig));
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// Type guard for platform config
|
|
132
|
+
if (isGlobalFlowsConfig(platConfig)) {
|
|
133
|
+
errors.push(`Platform '${platformId}': Cannot use global flows config format for platform entry`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// Now we know platConfig is PlatformConfig
|
|
137
|
+
const cfg = platConfig;
|
|
138
|
+
if (!cfg.rootDir || cfg.rootDir.trim() === '') {
|
|
139
|
+
errors.push(`Platform '${platformId}': Missing or empty rootDir`);
|
|
140
|
+
}
|
|
141
|
+
if (!cfg.name || cfg.name.trim() === '') {
|
|
142
|
+
errors.push(`Platform '${platformId}': Missing or empty name`);
|
|
143
|
+
}
|
|
144
|
+
// Validate export flows
|
|
145
|
+
if (cfg.export !== undefined) {
|
|
146
|
+
if (!Array.isArray(cfg.export)) {
|
|
147
|
+
errors.push(`Platform '${platformId}': export must be array or undefined`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
errors.push(...validateFlows(cfg.export, `${platformId}.export`));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Validate import flows
|
|
154
|
+
if (cfg.import !== undefined) {
|
|
155
|
+
if (!Array.isArray(cfg.import)) {
|
|
156
|
+
errors.push(`Platform '${platformId}': import must be array or undefined`);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
errors.push(...validateFlows(cfg.import, `${platformId}.import`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Validate that export or import or rootFile is present
|
|
163
|
+
const hasExport = cfg.export && cfg.export.length > 0;
|
|
164
|
+
const hasImport = cfg.import && cfg.import.length > 0;
|
|
165
|
+
if (!hasExport && !hasImport && !cfg.rootFile) {
|
|
166
|
+
errors.push(`Platform '${platformId}': Must define at least one of 'export', 'import', or 'rootFile'`);
|
|
167
|
+
}
|
|
168
|
+
if (cfg.aliases !== undefined && (!Array.isArray(cfg.aliases) || cfg.aliases.some((a) => typeof a !== 'string'))) {
|
|
169
|
+
errors.push(`Platform '${platformId}': aliases must be array of strings or undefined`);
|
|
170
|
+
}
|
|
171
|
+
if (typeof cfg.enabled !== 'boolean' && cfg.enabled !== undefined) {
|
|
172
|
+
errors.push(`Platform '${platformId}': enabled must be boolean or undefined`);
|
|
173
|
+
}
|
|
174
|
+
if (cfg.variables !== undefined && (typeof cfg.variables !== 'object' || Array.isArray(cfg.variables))) {
|
|
175
|
+
errors.push(`Platform '${platformId}': variables must be object or undefined`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return errors;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Validate global flows configuration
|
|
182
|
+
*/
|
|
183
|
+
function validateGlobalFlowsConfig(config) {
|
|
184
|
+
const errors = [];
|
|
185
|
+
if (config.export !== undefined) {
|
|
186
|
+
if (!Array.isArray(config.export)) {
|
|
187
|
+
errors.push(`Global config: export must be array or undefined`);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
errors.push(...validateFlows(config.export, 'global.export'));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (config.import !== undefined) {
|
|
194
|
+
if (!Array.isArray(config.import)) {
|
|
195
|
+
errors.push(`Global config: import must be array or undefined`);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
errors.push(...validateFlows(config.import, 'global.import'));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (config.description !== undefined && typeof config.description !== 'string') {
|
|
202
|
+
errors.push(`Global config: description must be string or undefined`);
|
|
203
|
+
}
|
|
204
|
+
return errors;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Validate an array of flows
|
|
208
|
+
*/
|
|
209
|
+
function validateFlows(flows, context) {
|
|
210
|
+
const errors = [];
|
|
211
|
+
for (let i = 0; i < flows.length; i++) {
|
|
212
|
+
const flow = flows[i];
|
|
213
|
+
// Required fields
|
|
214
|
+
if (!flow.from) {
|
|
215
|
+
errors.push(`${context}, flows[${i}]: Missing 'from' field`);
|
|
216
|
+
}
|
|
217
|
+
else if (typeof flow.from !== 'string' && !Array.isArray(flow.from)) {
|
|
218
|
+
errors.push(`${context}, flows[${i}]: 'from' must be string or array of strings`);
|
|
219
|
+
}
|
|
220
|
+
else if (typeof flow.from === 'string' && flow.from.trim() === '') {
|
|
221
|
+
errors.push(`${context}, flows[${i}]: 'from' cannot be empty`);
|
|
222
|
+
}
|
|
223
|
+
else if (Array.isArray(flow.from) && (flow.from.length === 0 || flow.from.some(p => typeof p !== 'string' || p.trim() === ''))) {
|
|
224
|
+
errors.push(`${context}, flows[${i}]: 'from' array must contain non-empty strings`);
|
|
225
|
+
}
|
|
226
|
+
if (!flow.to) {
|
|
227
|
+
errors.push(`${context}, flows[${i}]: Missing 'to' field`);
|
|
228
|
+
}
|
|
229
|
+
else if (typeof flow.to !== 'string' && typeof flow.to !== 'object') {
|
|
230
|
+
errors.push(`${context}, flows[${i}]: 'to' must be string or object`);
|
|
231
|
+
}
|
|
232
|
+
// Validate merge strategy
|
|
233
|
+
if (flow.merge !== undefined) {
|
|
234
|
+
const validMerges = ['replace', 'shallow', 'deep', 'composite'];
|
|
235
|
+
if (!validMerges.includes(flow.merge)) {
|
|
236
|
+
errors.push(`${context}, flows[${i}]: Invalid merge strategy '${flow.merge}'. Must be one of: ${validMerges.join(', ')}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Note: pipe transforms are now handled within map pipeline via $pipe operation
|
|
240
|
+
// Validate map pipeline (must be array)
|
|
241
|
+
if (flow.map !== undefined && !Array.isArray(flow.map)) {
|
|
242
|
+
errors.push(`${context}, flows[${i}]: 'map' must be array of operations`);
|
|
243
|
+
}
|
|
244
|
+
// Validate pick/omit
|
|
245
|
+
if (flow.pick !== undefined && !Array.isArray(flow.pick)) {
|
|
246
|
+
errors.push(`${context}, flows[${i}]: 'pick' must be array or undefined`);
|
|
247
|
+
}
|
|
248
|
+
if (flow.omit !== undefined && !Array.isArray(flow.omit)) {
|
|
249
|
+
errors.push(`${context}, flows[${i}]: 'omit' must be array or undefined`);
|
|
250
|
+
}
|
|
251
|
+
// Validate embed
|
|
252
|
+
if (flow.embed !== undefined && (typeof flow.embed !== 'string' || flow.embed.trim() === '')) {
|
|
253
|
+
errors.push(`${context}, flows[${i}]: 'embed' must be non-empty string or undefined`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return errors;
|
|
257
|
+
}
|
|
258
|
+
const GLOBAL_DIR = join(os.homedir(), ".openpackage");
|
|
259
|
+
const stateCache = new Map();
|
|
260
|
+
/**
|
|
261
|
+
* Clear the platforms cache. Useful for testing.
|
|
262
|
+
* @param cwd Optional path to clear cache for. If not provided, clears all cache.
|
|
263
|
+
*/
|
|
264
|
+
export function clearPlatformsCache(cwd) {
|
|
265
|
+
if (cwd !== undefined) {
|
|
266
|
+
stateCache.delete(cwd);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
stateCache.clear();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function getPlatformsState(cwd) {
|
|
273
|
+
const key = cwd ?? null;
|
|
274
|
+
if (stateCache.has(key)) {
|
|
275
|
+
return stateCache.get(key);
|
|
276
|
+
}
|
|
277
|
+
let config;
|
|
278
|
+
if (key === null) {
|
|
279
|
+
// Global
|
|
280
|
+
const globalFile = readJsoncOrJson(join(GLOBAL_DIR, "platforms.jsonc")) ??
|
|
281
|
+
readJsoncOrJson(join(GLOBAL_DIR, "platforms.json"));
|
|
282
|
+
config = globalFile
|
|
283
|
+
? mergePlatformsConfig(BUILT_IN_CONFIG, globalFile)
|
|
284
|
+
: BUILT_IN_CONFIG;
|
|
285
|
+
const errors = validatePlatformsConfig(config);
|
|
286
|
+
if (errors.length > 0) {
|
|
287
|
+
throw new Error(`Global platforms config validation failed:\n - ${errors.join('\n - ')}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// Local
|
|
292
|
+
const globalState = getPlatformsState(null);
|
|
293
|
+
const globalConfig = globalState.config;
|
|
294
|
+
const localDir = join(key, DIR_PATTERNS.OPENPACKAGE);
|
|
295
|
+
const localFile = readJsoncOrJson(join(localDir, "platforms.jsonc")) ??
|
|
296
|
+
readJsoncOrJson(join(localDir, "platforms.json"));
|
|
297
|
+
config = localFile
|
|
298
|
+
? mergePlatformsConfig(globalConfig, localFile)
|
|
299
|
+
: globalConfig;
|
|
300
|
+
const errors = validatePlatformsConfig(config);
|
|
301
|
+
if (errors.length > 0) {
|
|
302
|
+
throw new Error(`Local platforms config validation failed in ${key}:\n - ${errors.join('\n - ')}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Extract global flows if present
|
|
306
|
+
const globalConfig = config['global'];
|
|
307
|
+
const globalExportFlows = (globalConfig && isGlobalFlowsConfig(globalConfig))
|
|
308
|
+
? globalConfig.export
|
|
309
|
+
: undefined;
|
|
310
|
+
const globalImportFlows = (globalConfig && isGlobalFlowsConfig(globalConfig))
|
|
311
|
+
? globalConfig.import
|
|
312
|
+
: undefined;
|
|
313
|
+
// Create definitions and compute state
|
|
314
|
+
const defs = createPlatformDefinitions(config);
|
|
315
|
+
const dirLookup = {};
|
|
316
|
+
const aliasLookup = {};
|
|
317
|
+
const universalPatterns = new Set();
|
|
318
|
+
const rootFiles = [];
|
|
319
|
+
const allPlatforms = [];
|
|
320
|
+
// Collect all universal patterns from platform export flows
|
|
321
|
+
for (const def of Object.values(defs)) {
|
|
322
|
+
allPlatforms.push(def.id);
|
|
323
|
+
dirLookup[def.rootDir] = def.id;
|
|
75
324
|
for (const alias of def.aliases ?? []) {
|
|
76
|
-
|
|
325
|
+
aliasLookup[alias.toLowerCase()] = def.id;
|
|
326
|
+
}
|
|
327
|
+
// Collect all 'from' patterns from export flows
|
|
328
|
+
if (def.export && def.export.length > 0) {
|
|
329
|
+
for (const flow of def.export) {
|
|
330
|
+
// For array patterns, add all patterns
|
|
331
|
+
if (Array.isArray(flow.from)) {
|
|
332
|
+
flow.from.forEach((p) => universalPatterns.add(p));
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
universalPatterns.add(flow.from);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
77
338
|
}
|
|
339
|
+
if (def.rootFile) {
|
|
340
|
+
rootFiles.push(def.rootFile);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Add patterns from global export flows
|
|
344
|
+
if (globalExportFlows && globalExportFlows.length > 0) {
|
|
345
|
+
for (const flow of globalExportFlows) {
|
|
346
|
+
// For array patterns, add all patterns
|
|
347
|
+
if (Array.isArray(flow.from)) {
|
|
348
|
+
flow.from.forEach((p) => universalPatterns.add(p));
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
universalPatterns.add(flow.from);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Derive subdirectories from patterns (backward compatibility)
|
|
356
|
+
const universalSubdirs = extractSubdirectoriesFromPatterns(universalPatterns);
|
|
357
|
+
const enabledPlatforms = allPlatforms.filter((p) => defs[p].enabled);
|
|
358
|
+
const state = {
|
|
359
|
+
config,
|
|
360
|
+
globalExportFlows,
|
|
361
|
+
globalImportFlows,
|
|
362
|
+
defs,
|
|
363
|
+
dirLookup,
|
|
364
|
+
aliasLookup,
|
|
365
|
+
universalPatterns,
|
|
366
|
+
universalSubdirs,
|
|
367
|
+
rootFiles,
|
|
368
|
+
allPlatforms,
|
|
369
|
+
enabledPlatforms
|
|
370
|
+
};
|
|
371
|
+
stateCache.set(key, state);
|
|
372
|
+
return state;
|
|
373
|
+
}
|
|
374
|
+
export function getPlatformDefinitions(cwd) {
|
|
375
|
+
return getPlatformsState(cwd).defs;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Get global export flows that apply to all platforms (install/apply)
|
|
379
|
+
*/
|
|
380
|
+
export function getGlobalExportFlows(cwd) {
|
|
381
|
+
return getPlatformsState(cwd).globalExportFlows;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Get global import flows that apply to all platforms (save)
|
|
385
|
+
*/
|
|
386
|
+
export function getGlobalImportFlows(cwd) {
|
|
387
|
+
return getPlatformsState(cwd).globalImportFlows;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* @deprecated Use getGlobalExportFlows() for export flows or getGlobalImportFlows() for import flows
|
|
391
|
+
*/
|
|
392
|
+
export function getGlobalFlows(cwd) {
|
|
393
|
+
// Return export flows for backward compatibility
|
|
394
|
+
return getPlatformsState(cwd).globalExportFlows;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Check if a platform uses flow-based configuration (export or import)
|
|
398
|
+
*/
|
|
399
|
+
export function platformUsesFlows(platform, cwd) {
|
|
400
|
+
try {
|
|
401
|
+
const def = getPlatformDefinition(platform, cwd);
|
|
402
|
+
const hasExport = def.export !== undefined && def.export.length > 0;
|
|
403
|
+
const hasImport = def.import !== undefined && def.import.length > 0;
|
|
404
|
+
return hasExport || hasImport;
|
|
78
405
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
406
|
+
catch (error) {
|
|
407
|
+
// Platform not found - return false
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Check if a platform uses legacy subdirs configuration
|
|
413
|
+
* @deprecated Always returns false - subdirs support removed
|
|
414
|
+
*/
|
|
415
|
+
export function platformUsesSubdirs(platform, cwd) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Get all universal path patterns from flow definitions.
|
|
420
|
+
* These patterns define which files are considered universal package content.
|
|
421
|
+
* This is the source of truth for determining what belongs in a package.
|
|
422
|
+
*
|
|
423
|
+
* @param cwd - Optional cwd for local config overrides
|
|
424
|
+
* @returns Set of glob patterns from all platform flows
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* // Returns: Set(["rules/**\/*.md", "mcp.jsonc", "commands/*.md", ...])
|
|
428
|
+
* const patterns = getAllUniversalPatterns()
|
|
429
|
+
*/
|
|
430
|
+
export function getAllUniversalPatterns(cwd) {
|
|
431
|
+
return new Set(getPlatformsState(cwd).universalPatterns);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Check if a file path matches any universal pattern from flows.
|
|
435
|
+
* This is the primary method for determining if a file is universal content.
|
|
436
|
+
*
|
|
437
|
+
* @param filePath - File path to check (normalized, no leading slash)
|
|
438
|
+
* @param cwd - Optional cwd for local config overrides
|
|
439
|
+
* @returns true if path matches any universal pattern
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* matchesUniversalPattern("mcp.jsonc") // true (if defined in flows)
|
|
443
|
+
* matchesUniversalPattern("rules/typescript.md") // true
|
|
444
|
+
* matchesUniversalPattern("random-file.txt") // false
|
|
445
|
+
*/
|
|
446
|
+
export function matchesUniversalPattern(filePath, cwd) {
|
|
447
|
+
const patterns = getAllUniversalPatterns(cwd);
|
|
448
|
+
return matchesAnyPattern(filePath, patterns);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Get all unique universal subdirectory names defined across all platforms.
|
|
452
|
+
* This is derived from universal patterns for backward compatibility.
|
|
453
|
+
*
|
|
454
|
+
* @param cwd - Optional cwd for local config overrides
|
|
455
|
+
* @returns Set of all universal subdir names
|
|
456
|
+
* @deprecated Prefer using matchesUniversalPattern for file validation
|
|
457
|
+
*/
|
|
458
|
+
export function getAllUniversalSubdirs(cwd) {
|
|
459
|
+
return new Set(getPlatformsState(cwd).universalSubdirs);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Check if a string is a recognized universal subdir.
|
|
463
|
+
*
|
|
464
|
+
* @param subdirName - Name to check
|
|
465
|
+
* @param cwd - Optional cwd for local config overrides
|
|
466
|
+
* @returns true if the subdir is defined in any platform
|
|
467
|
+
* @deprecated Prefer using matchesUniversalPattern for file validation
|
|
468
|
+
*/
|
|
469
|
+
export function isKnownUniversalSubdir(subdirName, cwd) {
|
|
470
|
+
return getPlatformsState(cwd).universalSubdirs.has(subdirName);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Get lookup map from platform directory name to platform ID.
|
|
474
|
+
*/
|
|
475
|
+
export function getPlatformDirLookup(cwd) {
|
|
476
|
+
return getPlatformsState(cwd).dirLookup;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get lookup map from platform alias to platform ID.
|
|
480
|
+
*/
|
|
481
|
+
export function getPlatformAliasLookup(cwd) {
|
|
482
|
+
return getPlatformsState(cwd).aliasLookup;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Get all known platform root files.
|
|
486
|
+
*/
|
|
487
|
+
export function getPlatformRootFiles(cwd) {
|
|
488
|
+
return getPlatformsState(cwd).rootFiles;
|
|
489
|
+
}
|
|
84
490
|
/**
|
|
85
491
|
* Get platform definition by name
|
|
492
|
+
* @throws Error if platform not found
|
|
86
493
|
*/
|
|
87
|
-
export function getPlatformDefinition(name) {
|
|
88
|
-
|
|
494
|
+
export function getPlatformDefinition(name, cwd) {
|
|
495
|
+
const state = getPlatformsState(cwd);
|
|
496
|
+
const def = state.defs[name];
|
|
497
|
+
if (!def) {
|
|
498
|
+
throw new Error(`Unknown platform: ${name}`);
|
|
499
|
+
}
|
|
500
|
+
return def;
|
|
89
501
|
}
|
|
90
502
|
/**
|
|
91
503
|
* Get all platforms
|
|
92
504
|
*/
|
|
93
|
-
export function getAllPlatforms(options) {
|
|
505
|
+
export function getAllPlatforms(options, cwd) {
|
|
506
|
+
const state = getPlatformsState(cwd);
|
|
94
507
|
if (options?.includeDisabled) {
|
|
95
|
-
return
|
|
508
|
+
return state.allPlatforms;
|
|
96
509
|
}
|
|
97
|
-
return
|
|
510
|
+
return state.enabledPlatforms;
|
|
98
511
|
}
|
|
99
|
-
export function resolvePlatformName(input) {
|
|
512
|
+
export function resolvePlatformName(input, cwd) {
|
|
100
513
|
if (!input) {
|
|
101
514
|
return undefined;
|
|
102
515
|
}
|
|
516
|
+
const state = getPlatformsState(cwd);
|
|
103
517
|
const normalized = input.toLowerCase();
|
|
104
|
-
if (normalized in
|
|
518
|
+
if (normalized in state.defs) {
|
|
105
519
|
return normalized;
|
|
106
520
|
}
|
|
107
|
-
return
|
|
521
|
+
return state.aliasLookup[normalized];
|
|
108
522
|
}
|
|
109
|
-
|
|
110
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Resolve a frontmatter/platform key (id or alias, case-insensitive) to a canonical platform id.
|
|
525
|
+
* Returns null when the key is not a known platform or alias.
|
|
526
|
+
*/
|
|
527
|
+
export function resolvePlatformKey(key, cwd) {
|
|
528
|
+
if (!key)
|
|
529
|
+
return null;
|
|
530
|
+
const normalized = key.toLowerCase();
|
|
531
|
+
const state = getPlatformsState(cwd);
|
|
532
|
+
if (normalized in state.defs) {
|
|
533
|
+
return normalized;
|
|
534
|
+
}
|
|
535
|
+
return state.aliasLookup[normalized] ?? null;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Internal helper to build directory paths for a single platform definition.
|
|
539
|
+
*/
|
|
540
|
+
function buildDirectoryPaths(definition, cwd) {
|
|
541
|
+
const subdirsPaths = {};
|
|
542
|
+
// Build from export flows (the flows that define workspace structure)
|
|
543
|
+
if (definition.export && definition.export.length > 0) {
|
|
544
|
+
for (const flow of definition.export) {
|
|
545
|
+
// Extract universal subdir from 'from' pattern
|
|
546
|
+
// For array patterns, use the first pattern
|
|
547
|
+
const fromPattern = Array.isArray(flow.from) ? flow.from[0] : flow.from;
|
|
548
|
+
const firstComponent = fromPattern.split('/')[0];
|
|
549
|
+
// Skip if it's a file (contains extension) or already exists
|
|
550
|
+
if (firstComponent && !firstComponent.includes('.') && !subdirsPaths[firstComponent]) {
|
|
551
|
+
// Extract platform subdir from 'to' pattern
|
|
552
|
+
const toPattern = typeof flow.to === 'string' ? flow.to : Object.values(flow.to)[0];
|
|
553
|
+
if (typeof toPattern === 'string') {
|
|
554
|
+
// Get the directory part of the target path
|
|
555
|
+
const targetPath = toPattern.split('/').slice(0, -1).join('/');
|
|
556
|
+
if (targetPath) {
|
|
557
|
+
subdirsPaths[firstComponent] = join(cwd, targetPath);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
rootDir: join(cwd, definition.rootDir),
|
|
565
|
+
rootFile: definition.rootFile ? join(cwd, definition.rootFile) : undefined,
|
|
566
|
+
subdirs: subdirsPaths
|
|
567
|
+
};
|
|
111
568
|
}
|
|
112
569
|
/**
|
|
113
570
|
* Get platform directory paths for a given working directory
|
|
114
571
|
*/
|
|
115
572
|
export function getPlatformDirectoryPaths(cwd) {
|
|
573
|
+
const state = getPlatformsState(cwd);
|
|
116
574
|
const paths = {};
|
|
117
|
-
for (const platform of
|
|
118
|
-
|
|
119
|
-
const rulesSubdir = definition.subdirs[UNIVERSAL_SUBDIRS.RULES];
|
|
120
|
-
paths[platform] = {
|
|
121
|
-
rulesDir: join(cwd, definition.rootDir, rulesSubdir?.path || '')
|
|
122
|
-
};
|
|
123
|
-
if (definition.rootFile) {
|
|
124
|
-
paths[platform].rootFile = join(cwd, definition.rootFile);
|
|
125
|
-
}
|
|
126
|
-
const commandsSubdir = definition.subdirs[UNIVERSAL_SUBDIRS.COMMANDS];
|
|
127
|
-
if (commandsSubdir) {
|
|
128
|
-
paths[platform].commandsDir = join(cwd, definition.rootDir, commandsSubdir.path);
|
|
129
|
-
}
|
|
130
|
-
const agentsSubdir = definition.subdirs[UNIVERSAL_SUBDIRS.AGENTS];
|
|
131
|
-
if (agentsSubdir) {
|
|
132
|
-
paths[platform].agentsDir = join(cwd, definition.rootDir, agentsSubdir.path);
|
|
133
|
-
}
|
|
134
|
-
const skillsSubdir = definition.subdirs[UNIVERSAL_SUBDIRS.SKILLS];
|
|
135
|
-
if (skillsSubdir) {
|
|
136
|
-
paths[platform].skillsDir = join(cwd, definition.rootDir, skillsSubdir.path);
|
|
137
|
-
}
|
|
575
|
+
for (const platform of state.enabledPlatforms) {
|
|
576
|
+
paths[platform] = buildDirectoryPaths(state.defs[platform], cwd);
|
|
138
577
|
}
|
|
139
578
|
return paths;
|
|
140
579
|
}
|
|
141
580
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
581
|
+
* Get directory paths for a specific platform.
|
|
582
|
+
* @throws Error if platform unknown
|
|
144
583
|
*/
|
|
145
|
-
export
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const def = getPlatformDefinition(platform);
|
|
151
|
-
if (def.rootFile && def.rootFile !== FILE_PATTERNS.AGENTS_MD) {
|
|
152
|
-
rootFileToPlatform.set(def.rootFile, platform);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
// Check for existence of each root file at cwd
|
|
156
|
-
for (const [rootFile, platform] of rootFileToPlatform.entries()) {
|
|
157
|
-
const filePath = join(cwd, rootFile);
|
|
158
|
-
if (await exists(filePath)) {
|
|
159
|
-
detectedPlatforms.push(platform);
|
|
160
|
-
}
|
|
584
|
+
export function getPlatformDirectoryPathsForPlatform(platform, cwd) {
|
|
585
|
+
const state = getPlatformsState(cwd);
|
|
586
|
+
const definition = state.defs[platform];
|
|
587
|
+
if (!definition) {
|
|
588
|
+
throw new Error(`Unknown platform: ${platform}`);
|
|
161
589
|
}
|
|
162
|
-
return
|
|
590
|
+
return buildDirectoryPaths(definition, cwd);
|
|
163
591
|
}
|
|
164
592
|
/**
|
|
165
593
|
* Detect all platforms present in a directory
|
|
166
|
-
* Checks both platform directories and root files
|
|
594
|
+
* Checks both platform directories (.platform/) and unique root files (e.g., CLAUDE.md)
|
|
595
|
+
* AGENTS.md is skipped as it's universal/ambiguous.
|
|
167
596
|
*/
|
|
168
597
|
export async function detectAllPlatforms(cwd) {
|
|
169
|
-
|
|
170
|
-
const detectionPromises =
|
|
171
|
-
const definition =
|
|
598
|
+
const state = getPlatformsState(cwd);
|
|
599
|
+
const detectionPromises = state.enabledPlatforms.map(async (platform) => {
|
|
600
|
+
const definition = state.defs[platform];
|
|
172
601
|
const rootDirPath = join(cwd, definition.rootDir);
|
|
173
|
-
//
|
|
174
|
-
const
|
|
602
|
+
// Detected if root dir exists OR unique root file exists (skip AGENTS.md)
|
|
603
|
+
const dirExists = await exists(rootDirPath);
|
|
604
|
+
let fileExists = false;
|
|
605
|
+
if (definition.rootFile &&
|
|
606
|
+
definition.rootFile !== FILE_PATTERNS.AGENTS_MD) {
|
|
607
|
+
const rootFilePath = join(cwd, definition.rootFile);
|
|
608
|
+
fileExists = await exists(rootFilePath);
|
|
609
|
+
}
|
|
610
|
+
const detected = dirExists || fileExists;
|
|
175
611
|
return {
|
|
176
612
|
name: platform,
|
|
177
|
-
detected
|
|
613
|
+
detected,
|
|
178
614
|
};
|
|
179
615
|
});
|
|
180
|
-
|
|
181
|
-
// Also detect by root files
|
|
182
|
-
const rootFileDetectedPlatforms = await detectPlatformByRootFile(cwd);
|
|
183
|
-
// Merge results - mark platforms as detected if they have either directory or root file
|
|
184
|
-
for (const platform of rootFileDetectedPlatforms) {
|
|
185
|
-
const result = detectionResults.find(r => r.name === platform);
|
|
186
|
-
if (result && !result.detected) {
|
|
187
|
-
result.detected = true;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return detectionResults;
|
|
616
|
+
return await Promise.all(detectionPromises);
|
|
191
617
|
}
|
|
192
618
|
/**
|
|
193
619
|
* Get detected platforms only
|
|
194
620
|
*/
|
|
195
621
|
export async function getDetectedPlatforms(cwd) {
|
|
196
622
|
const results = await detectAllPlatforms(cwd);
|
|
197
|
-
return results
|
|
623
|
+
return results
|
|
624
|
+
.filter((result) => result.detected)
|
|
625
|
+
.map((result) => result.name);
|
|
198
626
|
}
|
|
199
627
|
/**
|
|
200
628
|
* Create platform directories
|
|
201
629
|
*/
|
|
630
|
+
/**
|
|
631
|
+
* Create platform directories
|
|
632
|
+
* @deprecated Subdirs support removed - flows create directories as needed
|
|
633
|
+
* Only creates root directory for platforms now
|
|
634
|
+
*/
|
|
202
635
|
export async function createPlatformDirectories(cwd, platforms) {
|
|
636
|
+
const state = getPlatformsState(cwd);
|
|
203
637
|
const created = [];
|
|
204
|
-
const paths = getPlatformDirectoryPaths(cwd);
|
|
205
638
|
for (const platform of platforms) {
|
|
206
|
-
const
|
|
639
|
+
const definition = state.defs[platform];
|
|
640
|
+
if (!definition) {
|
|
641
|
+
throw new Error(`Unknown platform: ${platform}`);
|
|
642
|
+
}
|
|
643
|
+
// Only create root directory
|
|
644
|
+
const rootPath = join(cwd, definition.rootDir);
|
|
207
645
|
try {
|
|
208
|
-
const dirExists = await exists(
|
|
646
|
+
const dirExists = await exists(rootPath);
|
|
209
647
|
if (!dirExists) {
|
|
210
|
-
await ensureDir(
|
|
211
|
-
created.push(relative(cwd,
|
|
212
|
-
logger.debug(`Created platform directory: ${
|
|
648
|
+
await ensureDir(rootPath);
|
|
649
|
+
created.push(relative(cwd, rootPath));
|
|
650
|
+
logger.debug(`Created platform root directory: ${rootPath}`);
|
|
213
651
|
}
|
|
214
652
|
}
|
|
215
653
|
catch (error) {
|
|
216
|
-
logger.error(`Failed to create platform directory ${
|
|
654
|
+
logger.error(`Failed to create platform root directory (${rootPath}): ${error}`);
|
|
217
655
|
}
|
|
656
|
+
// Flow-based installations will create subdirectories as needed
|
|
218
657
|
}
|
|
219
658
|
return created;
|
|
220
659
|
}
|
|
@@ -222,109 +661,125 @@ export async function createPlatformDirectories(cwd, platforms) {
|
|
|
222
661
|
* Validate platform directory structure
|
|
223
662
|
*/
|
|
224
663
|
export async function validatePlatformStructure(cwd, platform) {
|
|
225
|
-
const
|
|
226
|
-
const definition =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Check if rules directory exists
|
|
230
|
-
if (!(await exists(platformPaths.rulesDir))) {
|
|
231
|
-
issues.push(`Rules directory does not exist: ${platformPaths.rulesDir}`);
|
|
664
|
+
const state = getPlatformsState(cwd);
|
|
665
|
+
const definition = state.defs[platform];
|
|
666
|
+
if (!definition) {
|
|
667
|
+
throw new Error(`Unknown platform: ${platform}`);
|
|
232
668
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
669
|
+
const issues = [];
|
|
670
|
+
// Check root file
|
|
671
|
+
if (definition.rootFile) {
|
|
672
|
+
const rootFilePath = join(cwd, definition.rootFile);
|
|
673
|
+
if (!(await exists(rootFilePath))) {
|
|
674
|
+
issues.push(`Root file does not exist: ${rootFilePath}`);
|
|
237
675
|
}
|
|
238
676
|
}
|
|
677
|
+
// TODO: Optionally check flow-based directories exist
|
|
678
|
+
// (may not be necessary since flows handle missing directories gracefully)
|
|
239
679
|
return {
|
|
240
680
|
valid: issues.length === 0,
|
|
241
|
-
issues
|
|
681
|
+
issues,
|
|
242
682
|
};
|
|
243
683
|
}
|
|
244
684
|
/**
|
|
245
|
-
* Get
|
|
685
|
+
* Get file extensions allowed in a specific universal subdir for a platform
|
|
686
|
+
* @param universalSubdir - The universal subdirectory name (e.g., 'rules', 'commands', or custom)
|
|
687
|
+
* @returns Allowed extensions or empty array if subdir not supported
|
|
246
688
|
*/
|
|
247
|
-
export function
|
|
248
|
-
const
|
|
249
|
-
|
|
689
|
+
export function getPlatformSubdirExts(platform, universalSubdir, cwd) {
|
|
690
|
+
const state = getPlatformsState(cwd);
|
|
691
|
+
const definition = state.defs[platform];
|
|
692
|
+
if (!definition) {
|
|
693
|
+
throw new Error(`Unknown platform: ${platform}`);
|
|
694
|
+
}
|
|
695
|
+
// Check export flows
|
|
696
|
+
if (definition.export && definition.export.length > 0) {
|
|
697
|
+
const extensions = new Set();
|
|
698
|
+
for (const flow of definition.export) {
|
|
699
|
+
// Check if this flow matches the universal subdir
|
|
700
|
+
// For array patterns, use the first pattern
|
|
701
|
+
const fromPattern = Array.isArray(flow.from) ? flow.from[0] : flow.from;
|
|
702
|
+
if (fromPattern.startsWith(`${universalSubdir}/`)) {
|
|
703
|
+
// Extract extension from the 'from' pattern
|
|
704
|
+
const extMatch = fromPattern.match(/\.[^./]+$/);
|
|
705
|
+
if (extMatch) {
|
|
706
|
+
extensions.add(extMatch[0]);
|
|
707
|
+
}
|
|
708
|
+
// Also check 'to' pattern for extension changes
|
|
709
|
+
const toPattern = typeof flow.to === 'string' ? flow.to : Object.values(flow.to)[0];
|
|
710
|
+
if (typeof toPattern === 'string') {
|
|
711
|
+
const toExtMatch = toPattern.match(/\.[^./]+$/);
|
|
712
|
+
if (toExtMatch) {
|
|
713
|
+
extensions.add(toExtMatch[0]);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (extensions.size > 0) {
|
|
719
|
+
return Array.from(extensions);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
logger.warn(`Platform ${platform} does not support universal subdir '${universalSubdir}'`);
|
|
723
|
+
return [];
|
|
250
724
|
}
|
|
251
725
|
/**
|
|
252
726
|
* Get all universal subdirs that exist for a platform
|
|
253
727
|
*/
|
|
254
728
|
export function getPlatformUniversalSubdirs(cwd, platform) {
|
|
255
|
-
const paths =
|
|
256
|
-
const platformPaths = paths[platform];
|
|
729
|
+
const paths = getPlatformDirectoryPathsForPlatform(platform, cwd);
|
|
257
730
|
const subdirs = [];
|
|
258
|
-
|
|
259
|
-
subdirs.push({
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
subdirs.push({ dir: platformPaths.skillsDir, label: UNIVERSAL_SUBDIRS.SKILLS, leaf: getPathLeaf(platformPaths.skillsDir) });
|
|
731
|
+
for (const [label, dir] of Object.entries(paths.subdirs)) {
|
|
732
|
+
subdirs.push({
|
|
733
|
+
dir,
|
|
734
|
+
label,
|
|
735
|
+
leaf: getPathLeaf(dir),
|
|
736
|
+
});
|
|
737
|
+
}
|
|
266
738
|
return subdirs;
|
|
267
739
|
}
|
|
268
740
|
/**
|
|
269
741
|
* Check if a normalized path represents a universal subdir
|
|
270
742
|
*/
|
|
271
|
-
export function isUniversalSubdirPath(normalizedPath) {
|
|
272
|
-
|
|
273
|
-
|
|
743
|
+
export function isUniversalSubdirPath(normalizedPath, cwd) {
|
|
744
|
+
const state = getPlatformsState(cwd);
|
|
745
|
+
for (const subdir of state.universalSubdirs) {
|
|
746
|
+
if (normalizedPath.startsWith(`${subdir}/`) ||
|
|
274
747
|
normalizedPath === subdir ||
|
|
275
748
|
normalizedPath.startsWith(`${DIR_PATTERNS.OPENPACKAGE}/${subdir}/`) ||
|
|
276
|
-
normalizedPath === `${DIR_PATTERNS.OPENPACKAGE}/${subdir}`)
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
* Used for validating subdir keys before processing
|
|
282
|
-
*/
|
|
283
|
-
export function isValidUniversalSubdir(subKey) {
|
|
284
|
-
return Object.values(UNIVERSAL_SUBDIRS).includes(subKey);
|
|
749
|
+
normalizedPath === `${DIR_PATTERNS.OPENPACKAGE}/${subdir}`) {
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return false;
|
|
285
754
|
}
|
|
286
755
|
/**
|
|
287
756
|
* Check if a value is a valid platform ID.
|
|
288
757
|
*/
|
|
289
|
-
export function isPlatformId(value) {
|
|
290
|
-
|
|
758
|
+
export function isPlatformId(value, cwd) {
|
|
759
|
+
if (!value)
|
|
760
|
+
return false;
|
|
761
|
+
return value in getPlatformsState(cwd).defs;
|
|
291
762
|
}
|
|
292
763
|
/**
|
|
293
|
-
*
|
|
764
|
+
* @deprecated Subdirs support removed - use flows instead
|
|
765
|
+
* Always returns false for backward compatibility
|
|
294
766
|
*/
|
|
295
767
|
export function isExtAllowed(subdirDef, ext) {
|
|
296
|
-
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
if (subdirDef.exts === undefined) {
|
|
300
|
-
return true;
|
|
301
|
-
}
|
|
302
|
-
if (subdirDef.exts.length === 0) {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
return subdirDef.exts.includes(ext);
|
|
768
|
+
return false;
|
|
306
769
|
}
|
|
307
770
|
/**
|
|
308
|
-
*
|
|
309
|
-
*
|
|
771
|
+
* @deprecated Subdirs support removed - use flows instead
|
|
772
|
+
* Always returns original extension for backward compatibility
|
|
310
773
|
*/
|
|
311
774
|
export function getWorkspaceExt(subdirDef, packageExt) {
|
|
312
|
-
|
|
313
|
-
return packageExt;
|
|
314
|
-
}
|
|
315
|
-
const transformation = subdirDef.transformations.find(({ packageExt: candidate }) => candidate === packageExt);
|
|
316
|
-
return transformation?.workspaceExt ?? packageExt;
|
|
775
|
+
return packageExt;
|
|
317
776
|
}
|
|
318
777
|
/**
|
|
319
|
-
*
|
|
320
|
-
*
|
|
778
|
+
* @deprecated Subdirs support removed - use flows instead
|
|
779
|
+
* Always returns original extension for backward compatibility
|
|
321
780
|
*/
|
|
322
781
|
export function getPackageExt(subdirDef, workspaceExt) {
|
|
323
|
-
|
|
324
|
-
return workspaceExt;
|
|
325
|
-
}
|
|
326
|
-
const transformation = subdirDef.transformations.find(({ workspaceExt: candidate }) => candidate === workspaceExt);
|
|
327
|
-
return transformation?.packageExt ?? workspaceExt;
|
|
782
|
+
return workspaceExt;
|
|
328
783
|
}
|
|
329
784
|
/**
|
|
330
785
|
* Infer platform from workspace file information.
|
|
@@ -337,22 +792,23 @@ export function getPackageExt(subdirDef, workspaceExt) {
|
|
|
337
792
|
* @param fullPath - Full absolute path to the file
|
|
338
793
|
* @param sourceDir - Source directory name (e.g., '.cursor', 'ai')
|
|
339
794
|
* @param registryPath - Registry path (e.g., 'rules/file.md')
|
|
795
|
+
* @param cwd - Optional cwd for local platform config overrides
|
|
340
796
|
* @returns Platform ID, 'ai', or undefined if cannot be determined
|
|
341
797
|
*/
|
|
342
|
-
export function inferPlatformFromWorkspaceFile(fullPath, sourceDir, registryPath) {
|
|
798
|
+
export function inferPlatformFromWorkspaceFile(fullPath, sourceDir, registryPath, cwd) {
|
|
343
799
|
// First try to get platform from full path using existing mapper
|
|
344
|
-
const mapping = mapPlatformFileToUniversal(fullPath);
|
|
800
|
+
const mapping = mapPlatformFileToUniversal(fullPath, cwd);
|
|
345
801
|
if (mapping?.platform) {
|
|
346
802
|
return mapping.platform;
|
|
347
803
|
}
|
|
348
804
|
// Look up platform from source directory
|
|
349
|
-
const fromSource =
|
|
805
|
+
const fromSource = getPlatformDirLookup(cwd)[sourceDir];
|
|
350
806
|
if (fromSource) {
|
|
351
807
|
return fromSource;
|
|
352
808
|
}
|
|
353
809
|
// Fallback: check registry path for platform suffix
|
|
354
810
|
const parsed = parseUniversalPath(registryPath, { allowPlatformSuffix: true });
|
|
355
|
-
if (parsed?.platformSuffix && isPlatformId(parsed.platformSuffix)) {
|
|
811
|
+
if (parsed?.platformSuffix && isPlatformId(parsed.platformSuffix, cwd)) {
|
|
356
812
|
return parsed.platformSuffix;
|
|
357
813
|
}
|
|
358
814
|
return undefined;
|