@unbrained/pm-cli 2026.9.6 → 2026.9.8
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/.agents/skills/pm-developer/SKILL.md +12 -4
- package/.agents/skills/pm-developer/references/MULTI_AGENT_MERGE.md +19 -10
- package/.agents/skills/pm-extensions/SKILL.md +6 -0
- package/.agents/skills/pm-sdk/SKILL.md +11 -2
- package/.agents/skills/pm-user/SKILL.md +5 -0
- package/.agents/skills/pm-user/references/ECOSYSTEM_PLANNING.md +95 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +218 -182
- package/dist/cli/after-command-context-usage.js +1 -48
- package/dist/cli/argv-utils.js +1 -40
- package/dist/cli/bootstrap-args.js +1 -9
- package/dist/cli/bundle-integrity.js +2 -110
- package/dist/cli/commander-usage.js +11 -771
- package/dist/cli/commands/activity.js +1 -9
- package/dist/cli/commands/aggregate.js +1 -5
- package/dist/cli/commands/append.js +1 -5
- package/dist/cli/commands/claim.js +1 -5
- package/dist/cli/commands/close-many.js +1 -5
- package/dist/cli/commands/close.js +1 -9
- package/dist/cli/commands/comments.js +1 -5
- package/dist/cli/commands/config.js +1 -9
- package/dist/cli/commands/context.js +1 -9
- package/dist/cli/commands/contracts.js +1 -5
- package/dist/cli/commands/copy.js +1 -9
- package/dist/cli/commands/create.js +1 -9
- package/dist/cli/commands/delete.js +1 -9
- package/dist/cli/commands/deps.js +1 -9
- package/dist/cli/commands/docs.js +1 -9
- package/dist/cli/commands/duplicates.js +1 -9
- package/dist/cli/commands/eval.js +1 -9
- package/dist/cli/commands/extension/describe.js +1 -5
- package/dist/cli/commands/extension/scaffold.js +1 -5
- package/dist/cli/commands/extension/shared.js +1 -5
- package/dist/cli/commands/extension.js +1 -9
- package/dist/cli/commands/files.js +1 -9
- package/dist/cli/commands/focus.js +1 -9
- package/dist/cli/commands/gc.js +1 -9
- package/dist/cli/commands/get.js +1 -9
- package/dist/cli/commands/graph.js +1 -10
- package/dist/cli/commands/health.js +1 -9
- package/dist/cli/commands/history-compact.js +1 -10
- package/dist/cli/commands/history-redact.js +1 -11
- package/dist/cli/commands/history-repair.js +1 -10
- package/dist/cli/commands/history.js +1 -5
- package/dist/cli/commands/index.js +1 -58
- package/dist/cli/commands/init-agent-guidance.js +1 -9
- package/dist/cli/commands/init.js +1 -9
- package/dist/cli/commands/learnings.js +1 -5
- package/dist/cli/commands/lifecycle-transitions.js +1 -5
- package/dist/cli/commands/list.js +1 -9
- package/dist/cli/commands/merge.js +1 -11
- package/dist/cli/commands/next.js +1 -9
- package/dist/cli/commands/notes.js +1 -5
- package/dist/cli/commands/plan.js +1 -9
- package/dist/cli/commands/profile.js +1 -9
- package/dist/cli/commands/restore.js +1 -9
- package/dist/cli/commands/scheduling-shortcuts.js +1 -9
- package/dist/cli/commands/schema.js +1 -10
- package/dist/cli/commands/search.js +1 -9
- package/dist/cli/commands/stats.js +1 -9
- package/dist/cli/commands/telemetry.js +1 -9
- package/dist/cli/commands/test-all.js +1 -9
- package/dist/cli/commands/test-runs.js +1 -9
- package/dist/cli/commands/test.js +1 -9
- package/dist/cli/commands/update-many.js +1 -5
- package/dist/cli/commands/update.js +1 -9
- package/dist/cli/commands/upgrade.js +1 -5
- package/dist/cli/commands/validate.js +1 -15
- package/dist/cli/context-intent-invocation.js +1 -20
- package/dist/cli/description-stdin.js +1 -17
- package/dist/cli/error-guidance.js +1 -1353
- package/dist/cli/extension-command-help.js +5 -636
- package/dist/cli/extension-command-options.js +1 -427
- package/dist/cli/extension-recovery.js +1 -126
- package/dist/cli/help-content.js +2 -1001
- package/dist/cli/help-json-payload.js +4 -570
- package/dist/cli/main.d.ts +4 -0
- package/dist/cli/main.js +5 -2573
- package/dist/cli/migration-gates.js +1 -289
- package/dist/cli/public.js +1 -20
- package/dist/cli/register-annotations.js +1 -270
- package/dist/cli/register-assurance.js +1 -62
- package/dist/cli/register-files-lookup.js +1 -56
- package/dist/cli/register-history-activity.d.ts +8 -0
- package/dist/cli/register-history-activity.js +2 -0
- package/dist/cli/register-history-author.js +1 -38
- package/dist/cli/register-history-maintenance.d.ts +9 -0
- package/dist/cli/register-history-maintenance.js +2 -0
- package/dist/cli/register-list-query.js +10 -943
- package/dist/cli/register-mutation-options.js +1 -9
- package/dist/cli/register-mutation.js +19 -1893
- package/dist/cli/register-operations.js +1 -920
- package/dist/cli/register-setup.js +1 -716
- package/dist/cli/register-structured-mutation.js +1 -196
- package/dist/cli/registration-helpers.js +1 -5
- package/dist/cli/runtime-compatibility-boundary.js +4 -42
- package/dist/cli/schema-registration-helpers.js +1 -73
- package/dist/cli/search-refresh.js +1 -13
- package/dist/cli/stats-analytics-json.js +1 -68
- package/dist/cli/telemetry-flush.js +1 -10
- package/dist/cli-bundle/bundle-manifest.json +164 -156
- package/dist/cli-bundle/chunks/chunk-3BZZA6A3.js +5 -0
- package/dist/cli-bundle/chunks/chunk-AMRDVX36.js +235 -0
- package/dist/cli-bundle/chunks/chunk-DAM4CBBR.js +2202 -0
- package/dist/cli-bundle/chunks/{chunk-WYRZZ5ZE.js → chunk-DEZPZOQ7.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-FLDWYEND.js +77 -0
- package/dist/cli-bundle/chunks/{chunk-NPN5I67J.js → chunk-G5ADGQ2D.js} +3 -3
- package/dist/cli-bundle/chunks/chunk-JAAEYHUA.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-JX4DH4WC.js → chunk-KR3RP4FH.js} +3 -3
- package/dist/cli-bundle/chunks/chunk-LEZOUXG6.js +2 -0
- package/dist/cli-bundle/chunks/chunk-NSAPNEYH.js +2 -0
- package/dist/cli-bundle/chunks/chunk-QOEKOECV.js +29 -0
- package/dist/cli-bundle/chunks/chunk-RHSFOMHU.js +2 -0
- package/dist/cli-bundle/chunks/chunk-XOD2PCB6.js +20 -0
- package/dist/cli-bundle/chunks/{chunk-JT6XF6GE.js → chunk-YVEQ2AMO.js} +4 -4
- package/dist/cli-bundle/chunks/register-list-query-KSK3R63M.js +11 -0
- package/dist/cli-bundle/chunks/register-mutation-IQ5C7U6O.js +20 -0
- package/dist/cli-bundle/chunks/{register-operations-I5W3KMGB.js → register-operations-DWVHR2NC.js} +2 -2
- package/dist/cli-bundle/chunks/register-setup-BQ5SVD4B.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-3WRPWLA7.js +3 -0
- package/dist/cli-bundle/focused-chunks/chunk-7MBUII7U.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-COPBO5HW.js +8 -0
- package/dist/cli-bundle/focused-chunks/chunk-DUN4IXYN.js +20 -0
- package/dist/cli-bundle/focused-chunks/{chunk-LIJIA4TY.js → chunk-EE5QSM4Z.js} +3 -3
- package/dist/cli-bundle/focused-chunks/chunk-HJVARYM3.js +6 -0
- package/dist/cli-bundle/focused-chunks/chunk-IAUOUGVR.js +177 -0
- package/dist/cli-bundle/focused-chunks/chunk-KOOMJ54F.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-MZDXX7JY.js +56 -0
- package/dist/cli-bundle/focused-chunks/{chunk-M6PBRFSH.js → chunk-OPFHFGDD.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-QHNETP77.js +9 -0
- package/dist/cli-bundle/focused-chunks/{chunk-3BZEOXLT.js → chunk-RMWES4M6.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-RXT4P5QH.js +10 -0
- package/dist/cli-bundle/focused-chunks/chunk-SDOL6FM2.js +12 -0
- package/dist/cli-bundle/focused-chunks/chunk-SQV4AAUC.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-PEFABF7E.js → chunk-WE3IUNIW.js} +3 -3
- package/dist/cli-bundle/focused-chunks/chunk-YALOUBWJ.js +2 -0
- package/dist/cli-bundle/main.js +20 -20
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +2 -2
- package/dist/cli-bundle/sdk-core.js +29 -29
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +42 -42
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +11 -11
- package/dist/cli.js +1 -104
- package/dist/core/checkpoint/checkpoint-gc.js +1 -169
- package/dist/core/checkpoint/mutation-checkpoint.js +2 -133
- package/dist/core/config/nested-settings.js +1 -534
- package/dist/core/config/positional-value.js +1 -109
- package/dist/core/diagnostics/remediation.js +1 -506
- package/dist/core/extensions/activation-summary-markdown.js +2 -145
- package/dist/core/extensions/activation-summary.js +1 -92
- package/dist/core/extensions/activation-transaction.js +1 -46
- package/dist/core/extensions/capability-usage.js +1 -173
- package/dist/core/extensions/command-hook-context.js +1 -13
- package/dist/core/extensions/command-metadata-contract.js +1 -22
- package/dist/core/extensions/command-visibility-tier.js +1 -29
- package/dist/core/extensions/contribution-inventory.js +1 -173
- package/dist/core/extensions/exporter-output-contract.js +1 -66
- package/dist/core/extensions/extension-candidate.js +1 -18
- package/dist/core/extensions/extension-capability-aliases.js +1 -158
- package/dist/core/extensions/extension-hook-runtime.js +1 -699
- package/dist/core/extensions/extension-policy.js +1 -599
- package/dist/core/extensions/extension-registries.js +1 -62
- package/dist/core/extensions/extension-runtime-helpers.js +1 -33
- package/dist/core/extensions/extension-types.js +1 -102
- package/dist/core/extensions/flag-definition-validation.js +1 -52
- package/dist/core/extensions/flag-value-types.js +1 -64
- package/dist/core/extensions/index.js +1 -526
- package/dist/core/extensions/item-field-types.js +1 -39
- package/dist/core/extensions/item-fields.js +1 -261
- package/dist/core/extensions/loader.js +1 -2223
- package/dist/core/extensions/manifest-schema.js +1 -69
- package/dist/core/extensions/module-graph-snapshot.js +1 -33
- package/dist/core/extensions/preflight-ownership.js +1 -41
- package/dist/core/extensions/registration-validation.js +1 -82
- package/dist/core/extensions/registration-values.js +1 -209
- package/dist/core/extensions/reserved-host-flags.js +1 -36
- package/dist/core/extensions/runtime-registrations.js +1 -67
- package/dist/core/extensions/service-ownership.js +1 -19
- package/dist/core/extensions/version-compat.js +1 -216
- package/dist/core/fs/fs-utils.js +2 -158
- package/dist/core/fs/host-environment-errors.js +1 -141
- package/dist/core/fs/index.js +1 -9
- package/dist/core/fs/path-utils.js +1 -17
- package/dist/core/governance/content-fields.js +1 -160
- package/dist/core/governance/issue-codes.js +1 -151
- package/dist/core/governance/metadata-coverage.js +1 -358
- package/dist/core/history/drift-cache.js +3 -72
- package/dist/core/history/drift-scan.js +1 -342
- package/dist/core/history/event-classification.js +1 -109
- package/dist/core/history/event-index.js +8 -784
- package/dist/core/history/history-compact-bulk.js +1 -104
- package/dist/core/history/history-diff.js +1 -158
- package/dist/core/history/history-rewrite.d.ts +6 -1
- package/dist/core/history/history-rewrite.js +1 -107
- package/dist/core/history/history-storage-stats.js +1 -102
- package/dist/core/history/history-stream-policy.js +1 -62
- package/dist/core/history/history.js +2 -617
- package/dist/core/history/identity.js +1 -30
- package/dist/core/history/index.js +1 -9
- package/dist/core/history/projection.js +1 -196
- package/dist/core/history/read.d.ts +9 -0
- package/dist/core/history/read.js +1 -57
- package/dist/core/history/replay.js +2 -439
- package/dist/core/history/version-address.js +1 -32
- package/dist/core/history/workspace-history.js +2 -433
- package/dist/core/io/body-file.js +1 -62
- package/dist/core/io/bulk-ids-input.js +1 -112
- package/dist/core/item/actionability.js +1 -327
- package/dist/core/item/dependency-reference.js +1 -20
- package/dist/core/item/id.js +1 -112
- package/dist/core/item/index.js +1 -12
- package/dist/core/item/item-format-version.js +1 -78
- package/dist/core/item/item-format.js +5 -1473
- package/dist/core/item/item-record.js +1 -23
- package/dist/core/item/item-type-definition.js +1 -133
- package/dist/core/item/parent-reference-policy.js +1 -57
- package/dist/core/item/parse.js +2 -603
- package/dist/core/item/plan-lifecycle.js +1 -19
- package/dist/core/item/priority.js +1 -61
- package/dist/core/item/sprint-release-format.js +1 -38
- package/dist/core/item/status-filter.js +1 -115
- package/dist/core/item/status.js +1 -43
- package/dist/core/item/toon-decode.js +1 -29
- package/dist/core/item/type-registry.js +1 -715
- package/dist/core/item/type-synonyms.js +1 -38
- package/dist/core/lock/index.js +1 -9
- package/dist/core/lock/lock-gc.js +1 -240
- package/dist/core/lock/lock.js +3 -590
- package/dist/core/output/mutation-projection.js +1 -217
- package/dist/core/output/output-control.js +1 -25
- package/dist/core/output/output.js +11 -494
- package/dist/core/output/query-summary.js +1 -58
- package/dist/core/output/tabular.js +1 -77
- package/dist/core/packages/manifest.js +1 -244
- package/dist/core/packages/root.js +1 -84
- package/dist/core/profile/profile-describe.js +1 -83
- package/dist/core/profile/profile-lint.js +1 -415
- package/dist/core/profile/profile-plan.js +0 -0
- package/dist/core/profile/profile-presets.js +1 -370
- package/dist/core/reproducibility/context.js +1 -82
- package/dist/core/schema/fields-file.js +2 -288
- package/dist/core/schema/item-types-file.js +2 -296
- package/dist/core/schema/runtime-field-filters.js +1 -49
- package/dist/core/schema/runtime-field-values.js +1 -313
- package/dist/core/schema/runtime-schema.js +2 -826
- package/dist/core/schema/status-defs-file.js +2 -227
- package/dist/core/schema/status-token.js +1 -31
- package/dist/core/schema/type-inference.js +1 -95
- package/dist/core/schema/type-presets.js +1 -87
- package/dist/core/schema/type-workflows.js +1 -104
- package/dist/core/search/background-refresh.js +2 -286
- package/dist/core/search/bm25.js +1 -146
- package/dist/core/search/cache.js +2 -534
- package/dist/core/search/corpus.js +1 -311
- package/dist/core/search/embedding-batches.js +1 -163
- package/dist/core/search/eval.js +1 -225
- package/dist/core/search/http-client.js +1 -75
- package/dist/core/search/providers.js +1 -242
- package/dist/core/search/relevance.js +1 -244
- package/dist/core/search/semantic-defaults.js +1 -199
- package/dist/core/search/staleness.js +1 -30
- package/dist/core/search/vector-stores.js +2 -664
- package/dist/core/search/vectorization-metadata.js +1 -81
- package/dist/core/sentry/helpers.js +1 -272
- package/dist/core/sentry/instrument.js +1 -387
- package/dist/core/session/session-state.js +1 -260
- package/dist/core/shared/agent-session-context.js +1 -133
- package/dist/core/shared/author.js +1 -1172
- package/dist/core/shared/command-types.js +1 -4
- package/dist/core/shared/conflict-markers.js +1 -31
- package/dist/core/shared/constants.js +1 -405
- package/dist/core/shared/errors.js +1 -20
- package/dist/core/shared/html-entity-decode.js +1 -131
- package/dist/core/shared/index.js +1 -12
- package/dist/core/shared/item-metadata-contract.js +1 -89
- package/dist/core/shared/lazy-module.js +1 -16
- package/dist/core/shared/levenshtein.js +1 -61
- package/dist/core/shared/memo.js +1 -19
- package/dist/core/shared/numeric-parsers.js +1 -20
- package/dist/core/shared/option-alias-visibility.js +1 -67
- package/dist/core/shared/primitives.js +1 -127
- package/dist/core/shared/serial-queue.js +1 -79
- package/dist/core/shared/serialization.js +1 -210
- package/dist/core/shared/split-comma-list.js +1 -30
- package/dist/core/shared/text-normalization.js +1 -38
- package/dist/core/shared/time.js +1 -306
- package/dist/core/store/index.js +1 -12
- package/dist/core/store/item-format-migration.js +1 -125
- package/dist/core/store/item-metadata-cache.js +1 -1116
- package/dist/core/store/item-metadata-query-index.js +9 -394
- package/dist/core/store/item-metadata-read-work.js +1 -43
- package/dist/core/store/item-store.js +1 -693
- package/dist/core/store/paths.d.ts +1 -1
- package/dist/core/store/paths.js +1 -214
- package/dist/core/store/settings-read-cache.js +1 -85
- package/dist/core/store/settings-validator.js +1 -421
- package/dist/core/store/settings.js +3 -1309
- package/dist/core/store/tracker-preflight.js +1 -141
- package/dist/core/telemetry/consent.js +5 -108
- package/dist/core/telemetry/observability.js +1 -246
- package/dist/core/telemetry/runtime.js +4 -1988
- package/dist/core/test/background-runs.js +4 -945
- package/dist/core/test/item-test-run-tracking.js +1 -63
- package/dist/core/test/run-selectors.js +1 -120
- package/dist/core/validate/estimate-defaults.js +1 -89
- package/dist/core/validate/fix-planning.js +1 -189
- package/dist/core/validate/linked-artifact-reference.js +1 -36
- package/dist/core/validate/missing-by-type.js +1 -34
- package/dist/core/validate/missing-link-owners.js +1 -95
- package/dist/core/validate/stale-file-classification.js +1 -82
- package/dist/mcp/http-server.js +5 -448
- package/dist/mcp/legacy-adapter.js +1 -77
- package/dist/mcp/runtime-capabilities.js +1 -200
- package/dist/mcp/server.js +4 -1233
- package/dist/mcp/tool-definitions.js +1 -804
- package/dist/sdk/actionability.js +1 -13
- package/dist/sdk/agent/closed-domain-contracts.js +1 -161
- package/dist/sdk/agent/command-recovery.js +1 -59
- package/dist/sdk/agent/command-suggestions.js +1 -66
- package/dist/sdk/agent/create-option-policy.js +1 -31
- package/dist/sdk/agent/identity-config.js +1 -106
- package/dist/sdk/agent/item-addressing.js +1 -207
- package/dist/sdk/agent/refusal-closure-census.js +1 -151
- package/dist/sdk/agent/refusal-closure.js +1 -133
- package/dist/sdk/agent/refusal-corpus-contracts.js +1 -193
- package/dist/sdk/agent/refusal-reachability.js +1 -593
- package/dist/sdk/agent/subcommand-recovery.js +1 -83
- package/dist/sdk/agent/task-transcript-contracts.js +1 -197
- package/dist/sdk/agent/tracker-preflight-contracts.js +1 -126
- package/dist/sdk/agent-capability-contracts.js +1 -428
- package/dist/sdk/agent-session-context.js +1 -9
- package/dist/sdk/annotations.js +1 -448
- package/dist/sdk/author-attribution.js +1 -468
- package/dist/sdk/authoring.js +1 -20
- package/dist/sdk/cli-bootstrap.js +1 -838
- package/dist/sdk/cli-contracts/agent-output-contracts.d.ts +1 -1
- package/dist/sdk/cli-contracts/agent-output-contracts.js +4 -476
- package/dist/sdk/cli-contracts/bootstrap-command-scanner.js +1 -79
- package/dist/sdk/cli-contracts/command-aliases.d.ts +4 -0
- package/dist/sdk/cli-contracts/command-aliases.js +1 -107
- package/dist/sdk/cli-contracts/command-exit-contracts.js +1 -151
- package/dist/sdk/cli-contracts/commander-mutation-options.js +1 -1081
- package/dist/sdk/cli-contracts/commander-types.js +1 -159
- package/dist/sdk/cli-contracts/completeness.js +1 -393
- package/dist/sdk/cli-contracts/enum-contracts.js +1 -222
- package/dist/sdk/cli-contracts/flag-contracts.js +1 -1947
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -347
- package/dist/sdk/cli-contracts/grammar-contracts.js +1 -986
- package/dist/sdk/cli-contracts/registration-helpers.js +7 -990
- package/dist/sdk/cli-contracts/runtime-contracts.d.ts +5 -0
- package/dist/sdk/cli-contracts/runtime-contracts.js +1 -2090
- package/dist/sdk/cli-contracts/schemas/assurance-definition.js +1 -324
- package/dist/sdk/cli-contracts/string-lists.js +1 -16
- package/dist/sdk/cli-contracts/tool-option-contracts.js +1 -461
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +1 -2167
- package/dist/sdk/cli-contracts/tool-schema.d.ts +3 -1
- package/dist/sdk/cli-contracts/tool-schema.js +1 -1664
- package/dist/sdk/cli-contracts/typed-action-inputs.js +1 -4
- package/dist/sdk/cli-contracts.d.ts +1 -1
- package/dist/sdk/cli-contracts.js +1 -17
- package/dist/sdk/cli-program.js +1 -49
- package/dist/sdk/command-line.d.ts +2 -5
- package/dist/sdk/command-line.js +1 -78
- package/dist/sdk/comments.js +1 -133
- package/dist/sdk/compile-cache.js +1 -73
- package/dist/sdk/completion.js +157 -837
- package/dist/sdk/compose.js +1 -1090
- package/dist/sdk/config.js +1 -1204
- package/dist/sdk/context/recency.js +1 -33
- package/dist/sdk/context/semantic-session-attribution.js +1 -82
- package/dist/sdk/context/usage-egress.js +1 -29
- package/dist/sdk/context/usage-ledger.js +3 -185
- package/dist/sdk/context-intent-contracts.js +1 -709
- package/dist/sdk/context-intent-runtime.js +1 -115
- package/dist/sdk/context-packing.js +1 -218
- package/dist/sdk/context-relevance.js +1 -503
- package/dist/sdk/context-signal-store.js +2 -426
- package/dist/sdk/context-usage.js +1 -367
- package/dist/sdk/contracts.js +1 -23
- package/dist/sdk/core-governance.js +1 -14
- package/dist/sdk/core.js +1 -14
- package/dist/sdk/corpus-shape.js +1 -401
- package/dist/sdk/define.js +1 -220
- package/dist/sdk/dependencies.js +1 -718
- package/dist/sdk/dependency-flag-validation.js +1 -156
- package/dist/sdk/dependency-provenance.js +1 -135
- package/dist/sdk/docs.js +1 -14
- package/dist/sdk/duplicates.js +1 -40
- package/dist/sdk/environment/agent-environment.js +1 -466
- package/dist/sdk/environment/host-environment-errors.js +1 -8
- package/dist/sdk/environment/project-runtime-compatibility.js +1 -481
- package/dist/sdk/environment/source-context.js +1 -28
- package/dist/sdk/environment/tracker-preflight.js +1 -9
- package/dist/sdk/error-code-catalog.js +1 -153
- package/dist/sdk/error-runtime.js +1 -58
- package/dist/sdk/errors.js +1 -58
- package/dist/sdk/eval.js +1 -190
- package/dist/sdk/extension/author-manifest.js +1 -92
- package/dist/sdk/extension/bundled-catalog.js +1 -332
- package/dist/sdk/extension/concurrency.js +1 -25
- package/dist/sdk/extension/copy-scope.d.ts +2 -0
- package/dist/sdk/extension/copy-scope.js +2 -0
- package/dist/sdk/extension/custom-field-collisions.js +1 -30
- package/dist/sdk/extension/describe.js +3 -189
- package/dist/sdk/extension/doctor.js +1 -606
- package/dist/sdk/extension/install-plan.d.ts +69 -0
- package/dist/sdk/extension/install-plan.js +2 -0
- package/dist/sdk/extension/install-receipts.d.ts +7 -0
- package/dist/sdk/extension/install-receipts.js +2 -0
- package/dist/sdk/extension/install-runtime.js +3 -313
- package/dist/sdk/extension/install-snapshot.js +1 -66
- package/dist/sdk/extension/install-sources.js +5 -1057
- package/dist/sdk/extension/managed-state.js +2 -252
- package/dist/sdk/extension/migrations.js +2 -174
- package/dist/sdk/extension/output-ownership.js +1 -25
- package/dist/sdk/extension/runtime-summary.js +1 -18
- package/dist/sdk/extension/scaffold.js +4 -2308
- package/dist/sdk/extension/shared.js +1 -147
- package/dist/sdk/extension/source-resolution.js +1 -65
- package/dist/sdk/extension/update-check.js +1 -67
- package/dist/sdk/extension-command-context.js +1 -44
- package/dist/sdk/extension-contracts.js +1 -73
- package/dist/sdk/extension.d.ts +7 -2
- package/dist/sdk/extension.js +1 -2196
- package/dist/sdk/files.js +1 -535
- package/dist/sdk/flag-invocation-contracts.d.ts +0 -5
- package/dist/sdk/flag-invocation-contracts.js +1 -344
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -2224
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -2236
- package/dist/sdk/generated-error-code-catalog.js +1 -16
- package/dist/sdk/governance/assurance-action-contracts.js +1 -4
- package/dist/sdk/governance/assurance-action.js +1 -411
- package/dist/sdk/governance/assurance-limits.js +1 -10
- package/dist/sdk/governance/assurance-mutation-error.js +1 -81
- package/dist/sdk/governance/assurance-presets.js +1 -135
- package/dist/sdk/governance/assurance-runtime.js +1 -238
- package/dist/sdk/governance/assurance.js +4 -1182
- package/dist/sdk/governance/boundary-fixtures.js +1 -200
- package/dist/sdk/governance/coverage-assurance.js +1 -60
- package/dist/sdk/governance/defect-recurrence-coverage.js +1 -188
- package/dist/sdk/governance/defect-recurrence-graph.js +1 -120
- package/dist/sdk/governance/defect-recurrence-signals.js +1 -24
- package/dist/sdk/governance/defect-recurrence.js +1 -623
- package/dist/sdk/governance/extension-host-version.js +1 -115
- package/dist/sdk/governance/gc.js +1 -368
- package/dist/sdk/governance/health.js +1 -2367
- package/dist/sdk/governance/hierarchy-validation.js +1 -108
- package/dist/sdk/governance/linked-file-report.js +1 -71
- package/dist/sdk/governance/merge-receipt-history.js +1 -206
- package/dist/sdk/governance/provenance-health.js +1 -282
- package/dist/sdk/governance/reindex.js +4 -761
- package/dist/sdk/governance/stale-work.js +1 -82
- package/dist/sdk/governance/status-role-diagnostics.js +1 -35
- package/dist/sdk/governance/storage-integrity.js +1 -280
- package/dist/sdk/governance/tracked-runtime-cache.js +1 -106
- package/dist/sdk/governance/upgrade.js +2 -327
- package/dist/sdk/governance/validate-history-drift.js +1 -81
- package/dist/sdk/governance/validate-item-reader.js +1 -17
- package/dist/sdk/governance/validate-normalization.js +1 -36
- package/dist/sdk/governance/validate.js +1 -2261
- package/dist/sdk/governance/workspace-position.js +1 -163
- package/dist/sdk/governance.d.ts +1 -0
- package/dist/sdk/governance.js +1 -31
- package/dist/sdk/graph/adapter.js +1 -181
- package/dist/sdk/graph/analytics.js +1 -474
- package/dist/sdk/graph/assembly.js +1 -377
- package/dist/sdk/graph/cache.js +1 -160
- package/dist/sdk/graph/centrality.js +1 -258
- package/dist/sdk/graph/conformance.js +1 -130
- package/dist/sdk/graph/deadline-scheduling.d.ts +90 -0
- package/dist/sdk/graph/deadline-scheduling.js +2 -0
- package/dist/sdk/graph/durable-cache.d.ts +1 -1
- package/dist/sdk/graph/durable-cache.js +1 -361
- package/dist/sdk/graph/governance-contracts.js +1 -34
- package/dist/sdk/graph/governance.d.ts +13 -1
- package/dist/sdk/graph/governance.js +1 -735
- package/dist/sdk/graph/hierarchy-integrity.js +1 -310
- package/dist/sdk/graph/index.d.ts +3 -2
- package/dist/sdk/graph/index.js +1 -26
- package/dist/sdk/graph/lifecycle-coverage.d.ts +32 -0
- package/dist/sdk/graph/lifecycle-coverage.js +2 -0
- package/dist/sdk/graph/mutation-advisory.js +1 -176
- package/dist/sdk/graph/remediation.js +1 -197
- package/dist/sdk/graph/run.d.ts +15 -1
- package/dist/sdk/graph/run.js +1 -768
- package/dist/sdk/graph/scheduling.js +1 -124
- package/dist/sdk/graph/traversal.js +1 -284
- package/dist/sdk/graph/workspace-relationship-kind-registry.js +1 -27
- package/dist/sdk/graph.js +1 -15
- package/dist/sdk/guide-topics.js +1 -707
- package/dist/sdk/guide.js +1 -279
- package/dist/sdk/history/maintenance.d.ts +71 -0
- package/dist/sdk/history/maintenance.js +2 -0
- package/dist/sdk/history/salvage.js +2 -247
- package/dist/sdk/history/subject.d.ts +19 -0
- package/dist/sdk/history/subject.js +2 -0
- package/dist/sdk/history-analytics.js +1 -282
- package/dist/sdk/history-compact.js +1 -527
- package/dist/sdk/history-mcp.js +1 -68
- package/dist/sdk/history-provenance.js +1 -182
- package/dist/sdk/history-read.js +1 -96
- package/dist/sdk/history-redact.d.ts +1 -14
- package/dist/sdk/history-redact.js +1 -643
- package/dist/sdk/history-repair.d.ts +5 -0
- package/dist/sdk/history-repair.js +1 -657
- package/dist/sdk/improvement-ledger-validation.js +1 -62
- package/dist/sdk/improvement-ledger.js +2 -257
- package/dist/sdk/index.d.ts +4 -0
- package/dist/sdk/index.js +1 -183
- package/dist/sdk/init-agent-guidance.js +2 -496
- package/dist/sdk/init.js +2 -820
- package/dist/sdk/invocation-author.js +1 -39
- package/dist/sdk/item-children.js +1 -90
- package/dist/sdk/item-metadata-index.js +1 -15
- package/dist/sdk/item-schedule.js +1 -27
- package/dist/sdk/item-transaction.js +1 -396
- package/dist/sdk/learnings.js +1 -29
- package/dist/sdk/lifecycle/append.js +2 -53
- package/dist/sdk/lifecycle/claim.d.ts +2 -0
- package/dist/sdk/lifecycle/claim.js +1 -242
- package/dist/sdk/lifecycle/close-many.js +1 -438
- package/dist/sdk/lifecycle/close.js +1 -468
- package/dist/sdk/lifecycle/copy.js +1 -212
- package/dist/sdk/lifecycle/create.js +1 -1949
- package/dist/sdk/lifecycle/delete.js +1 -45
- package/dist/sdk/lifecycle/event-validation-messages.js +1 -91
- package/dist/sdk/lifecycle/focus.js +1 -91
- package/dist/sdk/lifecycle/hierarchy-mutation-lock.js +1 -24
- package/dist/sdk/lifecycle/legacy-none-tokens.js +1 -149
- package/dist/sdk/lifecycle/lifecycle-transitions.js +1 -40
- package/dist/sdk/lifecycle/mcp-actions.d.ts +2 -0
- package/dist/sdk/lifecycle/mcp-actions.js +1 -51
- package/dist/sdk/lifecycle/metadata-normalizers.js +1 -46
- package/dist/sdk/lifecycle/mutation-command-options.js +1 -9
- package/dist/sdk/lifecycle/plan.js +1 -1606
- package/dist/sdk/lifecycle/recurrence-parsers.js +1 -147
- package/dist/sdk/lifecycle/reopen.js +1 -68
- package/dist/sdk/lifecycle/repeatable-metadata-parsers.js +1 -143
- package/dist/sdk/lifecycle/restore.d.ts +1 -0
- package/dist/sdk/lifecycle/restore.js +1 -330
- package/dist/sdk/lifecycle/shared-unset-fields.js +1 -295
- package/dist/sdk/lifecycle/update-many.js +1 -861
- package/dist/sdk/lifecycle/update.js +1 -2136
- package/dist/sdk/lifecycle-completion.js +1 -92
- package/dist/sdk/lifecycle-policy.js +1 -120
- package/dist/sdk/linked-artifacts.js +1 -530
- package/dist/sdk/mcp/apps.js +4 -133
- package/dist/sdk/mcp/authorization.js +1 -404
- package/dist/sdk/mcp/discovery.js +1 -494
- package/dist/sdk/mcp/interactions.js +1 -336
- package/dist/sdk/mcp/protocol.d.ts +7 -0
- package/dist/sdk/mcp/protocol.js +1 -184
- package/dist/sdk/mcp/skills.js +1 -389
- package/dist/sdk/mcp/subscriptions.js +1 -211
- package/dist/sdk/mcp/tasks.js +2 -430
- package/dist/sdk/mcp/transport.js +1 -260
- package/dist/sdk/merge/abandoned-receipts.js +1 -46
- package/dist/sdk/merge/driver.js +1 -212
- package/dist/sdk/merge/index.js +1 -16
- package/dist/sdk/merge/install.js +3 -561
- package/dist/sdk/merge/receipt-file-boundary.js +1 -46
- package/dist/sdk/merge/receipt-operation.js +1 -96
- package/dist/sdk/merge/receipt-schema.js +1 -397
- package/dist/sdk/merge/receipts.js +4 -525
- package/dist/sdk/merge/reconcile.js +1 -352
- package/dist/sdk/merge/three-way.js +3 -775
- package/dist/sdk/merge.js +1 -10
- package/dist/sdk/mutation-events.js +1 -292
- package/dist/sdk/mutation-guard.js +1 -216
- package/dist/sdk/notes.js +1 -139
- package/dist/sdk/output-contracts.js +1 -172
- package/dist/sdk/output-projection.js +1 -430
- package/dist/sdk/output-token-accounting.js +1 -140
- package/dist/sdk/output.js +1 -64
- package/dist/sdk/ownership-source.js +1 -21
- package/dist/sdk/package-import-adapters.js +1 -442
- package/dist/sdk/package-migrations.js +1 -20
- package/dist/sdk/package-runtime-options.js +1 -80
- package/dist/sdk/pagination.js +1 -162
- package/dist/sdk/profile.js +1 -454
- package/dist/sdk/provenance.js +1 -258
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/query/activity.js +1 -401
- package/dist/sdk/query/aggregate.d.ts +16 -0
- package/dist/sdk/query/aggregate.js +1 -390
- package/dist/sdk/query/calendar.js +1 -964
- package/dist/sdk/query/complete-list.js +1 -215
- package/dist/sdk/query/context.js +1 -1702
- package/dist/sdk/query/duplicate-candidates.js +1 -70
- package/dist/sdk/query/get.js +1 -450
- package/dist/sdk/query/history.js +1 -186
- package/dist/sdk/query/item-filter-options.js +1 -9
- package/dist/sdk/query/light-metadata.js +1 -18
- package/dist/sdk/query/list-filter-shared.js +1 -81
- package/dist/sdk/query/list.js +1 -1304
- package/dist/sdk/query/multi-value-filters.js +1 -78
- package/dist/sdk/query/next.js +1 -513
- package/dist/sdk/query/parsers.js +1 -44
- package/dist/sdk/query/projection-contracts.js +1 -184
- package/dist/sdk/query/search-contracts.js +1 -230
- package/dist/sdk/query/search-pagination.js +1 -35
- package/dist/sdk/query/search-rendering.js +1 -105
- package/dist/sdk/query/search.js +2 -1987
- package/dist/sdk/query.js +1 -35
- package/dist/sdk/read-output/continuation.js +1 -194
- package/dist/sdk/read-output-budget.js +1 -135
- package/dist/sdk/read-output-contracts.js +1 -1225
- package/dist/sdk/read-output-rows.js +1 -186
- package/dist/sdk/read-output-session.js +1 -208
- package/dist/sdk/relationship-analytics.js +1 -410
- package/dist/sdk/relationship-context.js +1 -284
- package/dist/sdk/relationship-history.js +4 -519
- package/dist/sdk/relationship-kinds/contract.js +1 -22
- package/dist/sdk/relationship-migration.js +1 -77
- package/dist/sdk/relationships.js +1 -579
- package/dist/sdk/reproducibility/process.js +1 -87
- package/dist/sdk/runtime-action-aliases.js +1 -59
- package/dist/sdk/runtime-extended-actions.js +1 -177
- package/dist/sdk/runtime-input.js +1 -614
- package/dist/sdk/runtime-primitives.js +1 -84
- package/dist/sdk/runtime-public-contracts.js +1 -4
- package/dist/sdk/runtime-stats-options.js +1 -38
- package/dist/sdk/runtime.js +1 -1928
- package/dist/sdk/scheduling-shortcuts.js +1 -63
- package/dist/sdk/schema-migration.js +3 -856
- package/dist/sdk/schema.js +1 -1376
- package/dist/sdk/search-refresh-worker.js +1 -18
- package/dist/sdk/similarity-scoring.js +1 -53
- package/dist/sdk/similarity.js +1 -326
- package/dist/sdk/start-task-status.js +1 -14
- package/dist/sdk/stats.js +1 -270
- package/dist/sdk/structured-mutations.js +1 -580
- package/dist/sdk/telemetry-flush.js +1 -13
- package/dist/sdk/telemetry.js +1 -405
- package/dist/sdk/templates.js +2 -292
- package/dist/sdk/test/batch.js +2 -509
- package/dist/sdk/test/entry.js +1 -70
- package/dist/sdk/test/execution.js +6 -2211
- package/dist/sdk/test/linked-command-detection.js +1 -546
- package/dist/sdk/test/measurements.js +1 -91
- package/dist/sdk/test/parsers.js +1 -545
- package/dist/sdk/test/runs.js +1 -193
- package/dist/sdk/test/trust.js +2 -206
- package/dist/sdk/testing.js +4 -1391
- package/dist/sdk/traceability/runtime-files-lookup.js +1 -32
- package/dist/sdk/traceability/source-traceability.js +1 -292
- package/dist/sdk/tracker-root-discovery.js +1 -9
- package/dist/sdk/workspace-contracts-cache.js +1 -28
- package/dist/sdk/workspace-contracts.js +1 -33
- package/dist/sdk/workspace-memory.js +2 -318
- package/dist/sdk/workspace-recipe.js +1 -71
- package/dist/sdk/workspace-snapshot.js +4 -613
- package/dist/sdk/workspace-transaction-gc.js +1 -149
- package/dist/sdk/workspace-transaction.js +2 -363
- package/dist/sdk/workspace.js +3 -127
- package/dist/types/index.js +1 -9
- package/dist/types.js +1 -245
- package/docs/AGENT_GUIDE.md +1 -1
- package/docs/CONTEXT_ALGORITHM_PORTFOLIO.md +167 -0
- package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +9 -3
- package/docs/EXTENSIONS.md +1 -0
- package/docs/HISTORY_MAINTENANCE.md +125 -0
- package/docs/MCP_2026_07_28_CONFORMANCE.md +20 -10
- package/docs/PACKAGE_EVIDENCE.md +92 -0
- package/docs/PLANNING_MEASUREMENTS.md +183 -0
- package/docs/README.md +3 -0
- package/docs/RELEASING.md +1 -0
- package/docs/SCRIPTING.md +21 -0
- package/docs/TESTING.md +23 -0
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +5 -5
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +2 -2
- package/marketplace.json +2 -2
- package/package.json +1 -1
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/scripts/finalize-build.mjs +45 -3
- package/dist/cli-bundle/chunks/chunk-AZDFJGH6.js +0 -9
- package/dist/cli-bundle/chunks/chunk-BAX2BYUW.js +0 -2111
- package/dist/cli-bundle/chunks/chunk-CKOL5U7J.js +0 -77
- package/dist/cli-bundle/chunks/chunk-OF76PSDQ.js +0 -5
- package/dist/cli-bundle/chunks/chunk-QUWFABDM.js +0 -232
- package/dist/cli-bundle/chunks/chunk-QWRRFYK3.js +0 -2
- package/dist/cli-bundle/chunks/chunk-RWWBOWJ5.js +0 -2
- package/dist/cli-bundle/chunks/chunk-WGAOYSYH.js +0 -40
- package/dist/cli-bundle/chunks/chunk-WI2AYPA7.js +0 -2
- package/dist/cli-bundle/chunks/register-list-query-5JL52D33.js +0 -11
- package/dist/cli-bundle/chunks/register-mutation-ZPG7KMVP.js +0 -20
- package/dist/cli-bundle/chunks/register-setup-H4SVAVX7.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-AL5ON6K6.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-CHC2IDXA.js +0 -6
- package/dist/cli-bundle/focused-chunks/chunk-CK6QFAPZ.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-EZR3GHFE.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-H2WJWK7L.js +0 -56
- package/dist/cli-bundle/focused-chunks/chunk-NTPY5GSO.js +0 -3
- package/dist/cli-bundle/focused-chunks/chunk-P5O7MP5Y.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-PDMW2E36.js +0 -177
- package/dist/cli-bundle/focused-chunks/chunk-QTVCLMSZ.js +0 -6
- package/dist/cli-bundle/focused-chunks/chunk-QTZRBUYA.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-SGQST3PQ.js +0 -12
- package/dist/cli-bundle/focused-chunks/chunk-UKHASMMP.js +0 -10
- package/dist/cli-bundle/focused-chunks/chunk-ZPEKQHOC.js +0 -2
|
@@ -1,1058 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="05f56edb-c08d-570c-b8b5-641fc694331e")}catch(e){}}();
|
|
8
|
-
import { execFile } from "node:child_process";
|
|
9
|
-
import fs from "node:fs/promises";
|
|
10
|
-
import os from "node:os";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
-
import { promisify } from "node:util";
|
|
14
|
-
import npa from "npm-package-arg";
|
|
15
|
-
import { extract as extractTar, list as listTar } from "tar";
|
|
16
|
-
import { collectPackageExtensionDirectories } from "../../core/packages/manifest.js";
|
|
17
|
-
import { resolvePmPackageRootFromModule } from "../../core/packages/root.js";
|
|
18
|
-
import { pathExists } from "../../core/fs/fs-utils.js";
|
|
19
|
-
import { isPathWithinDirectory } from "../../core/fs/path-utils.js";
|
|
20
|
-
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
21
|
-
import { PmCliError, } from "../../core/shared/errors.js";
|
|
22
|
-
import { listBundledPackageAliases } from "./bundled-catalog.js";
|
|
23
|
-
const execFileAsync = promisify(execFile);
|
|
24
|
-
const PM_CLI_PACKAGE_NAME = "@unbrained/pm-cli";
|
|
25
|
-
const LOCAL_PACKAGE_ARCHIVE_LIMITS = {
|
|
26
|
-
maxArchiveBytes: 64 * 1024 * 1024,
|
|
27
|
-
maxEntries: 4096,
|
|
28
|
-
maxExpandedBytes: 256 * 1024 * 1024,
|
|
29
|
-
maxEntryBytes: 64 * 1024 * 1024,
|
|
30
|
-
};
|
|
31
|
-
let bundledPackageAliasesCache;
|
|
32
|
-
/** Resolve an already-installed npm package without executing package code. */
|
|
33
|
-
export async function findInstalledNpmPackageCandidate(packageName, cwd = process.cwd()) {
|
|
34
|
-
const normalized = packageName.trim();
|
|
35
|
-
if (!/^(@[^/\\\s]+\/[^/\\\s]+|[^@/\\\s][^/\\\s]*)$/u.test(normalized)) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
let current = path.resolve(cwd);
|
|
39
|
-
while (true) {
|
|
40
|
-
const directory = path.join(current, "node_modules", ...normalized.split("/"));
|
|
41
|
-
const manifestPath = path.join(directory, "package.json");
|
|
42
|
-
if (await pathExists(manifestPath)) {
|
|
43
|
-
try {
|
|
44
|
-
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
45
|
-
if (manifest.name === normalized) {
|
|
46
|
-
return {
|
|
47
|
-
package: normalized,
|
|
48
|
-
...(typeof manifest.version === "string"
|
|
49
|
-
? { version: manifest.version }
|
|
50
|
-
: {}),
|
|
51
|
-
directory,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const parent = path.dirname(current);
|
|
60
|
-
if (parent === current)
|
|
61
|
-
return null;
|
|
62
|
-
current = parent;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function parseGithubPathSpec(pathSpec, input, refOverride) {
|
|
66
|
-
const segments = pathSpec
|
|
67
|
-
.split("/")
|
|
68
|
-
.map((segment) => segment.trim())
|
|
69
|
-
.filter((segment) => segment.length > 0);
|
|
70
|
-
if (segments.length < 2) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
const owner = segments[0];
|
|
74
|
-
const repo = segments[1].replace(/\.git$/i, "");
|
|
75
|
-
if (owner.length === 0 || repo.length === 0) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
const tail = segments.slice(2);
|
|
79
|
-
let ref;
|
|
80
|
-
let subpath;
|
|
81
|
-
if (tail[0] === "tree" && tail.length >= 2) {
|
|
82
|
-
ref = tail[1];
|
|
83
|
-
subpath = tail.slice(2).join("/");
|
|
84
|
-
}
|
|
85
|
-
else if (tail.length > 0) {
|
|
86
|
-
subpath = tail.join("/");
|
|
87
|
-
}
|
|
88
|
-
if (typeof refOverride === "string" && refOverride.trim().length > 0) {
|
|
89
|
-
ref = refOverride.trim();
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
kind: "github",
|
|
93
|
-
input,
|
|
94
|
-
owner,
|
|
95
|
-
repo,
|
|
96
|
-
repository: `https://github.com/${owner}/${repo}.git`,
|
|
97
|
-
ref,
|
|
98
|
-
subpath: subpath && subpath.length > 0 ? subpath : undefined,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
function parseNpmInstallSource(normalizedInput, forceGithub, refOverride) {
|
|
102
|
-
const spec = normalizedInput.slice("npm:".length).trim();
|
|
103
|
-
if (spec.length === 0) {
|
|
104
|
-
throw new PmCliError('npm package source must include a package spec after "npm:".', EXIT_CODE.USAGE);
|
|
105
|
-
}
|
|
106
|
-
if (forceGithub) {
|
|
107
|
-
throw new PmCliError('Options "--gh/--github" cannot be combined with npm: package sources.', EXIT_CODE.USAGE);
|
|
108
|
-
}
|
|
109
|
-
if (refOverride) {
|
|
110
|
-
throw new PmCliError('Option "--ref" cannot be combined with npm: package sources.', EXIT_CODE.USAGE);
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
kind: "npm",
|
|
114
|
-
input: normalizedInput,
|
|
115
|
-
spec,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
/** Implements parse extension install source for the public runtime surface of this module. */
|
|
119
|
-
export function parseExtensionInstallSource(input, options = {}) {
|
|
120
|
-
const normalizedInput = input.trim();
|
|
121
|
-
if (normalizedInput.length === 0) {
|
|
122
|
-
throw new PmCliError("Extension source is required for --install.", EXIT_CODE.USAGE);
|
|
123
|
-
}
|
|
124
|
-
const refOverride = typeof options.ref === "string" && options.ref.trim().length > 0
|
|
125
|
-
? options.ref.trim()
|
|
126
|
-
: undefined;
|
|
127
|
-
if (normalizedInput.startsWith("npm:")) {
|
|
128
|
-
return parseNpmInstallSource(normalizedInput, options.forceGithub === true, refOverride);
|
|
129
|
-
}
|
|
130
|
-
const maybeGithubByUrl = (() => {
|
|
131
|
-
try {
|
|
132
|
-
const parsed = new URL(normalizedInput);
|
|
133
|
-
if (parsed.hostname !== "github.com") {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
const pathSpec = parsed.pathname.replace(/^\/+/, "");
|
|
137
|
-
return parseGithubPathSpec(pathSpec, normalizedInput, refOverride);
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
})();
|
|
143
|
-
if (maybeGithubByUrl) {
|
|
144
|
-
return maybeGithubByUrl;
|
|
145
|
-
}
|
|
146
|
-
const strippedDomainInput = normalizedInput.startsWith("github.com/")
|
|
147
|
-
? normalizedInput.slice("github.com/".length)
|
|
148
|
-
: null;
|
|
149
|
-
if (strippedDomainInput) {
|
|
150
|
-
const parsed = parseGithubPathSpec(strippedDomainInput, normalizedInput, refOverride);
|
|
151
|
-
if (!parsed) {
|
|
152
|
-
throw new PmCliError(`Invalid GitHub source "${normalizedInput}".`, EXIT_CODE.USAGE);
|
|
153
|
-
}
|
|
154
|
-
return parsed;
|
|
155
|
-
}
|
|
156
|
-
if (options.forceGithub) {
|
|
157
|
-
const parsed = parseGithubPathSpec(normalizedInput, normalizedInput, refOverride);
|
|
158
|
-
if (!parsed) {
|
|
159
|
-
throw new PmCliError(`Invalid GitHub shorthand "${normalizedInput}".`, EXIT_CODE.USAGE);
|
|
160
|
-
}
|
|
161
|
-
return parsed;
|
|
162
|
-
}
|
|
163
|
-
if (/^https?:\/\//i.test(normalizedInput)) {
|
|
164
|
-
throw new PmCliError(`Unsupported extension source URL "${normalizedInput}". Supported remote source host: github.com.`, EXIT_CODE.USAGE);
|
|
165
|
-
}
|
|
166
|
-
return {
|
|
167
|
-
kind: "local",
|
|
168
|
-
input: normalizedInput,
|
|
169
|
-
absolute_path: path.resolve(process.cwd(), normalizedInput),
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
/** Implements run git command for the public runtime surface of this module. */
|
|
173
|
-
export async function runGitCommand(args, execRunner = execFileAsync, timeoutMs) {
|
|
174
|
-
try {
|
|
175
|
-
const result = await execRunner("git", args, {
|
|
176
|
-
encoding: "utf8",
|
|
177
|
-
timeout: timeoutMs,
|
|
178
|
-
});
|
|
179
|
-
return (result.stdout ?? "").trim();
|
|
180
|
-
}
|
|
181
|
-
catch (error) {
|
|
182
|
-
/* c8 ignore start -- stderr-vs-error-message precedence is validated in integration command-runner paths */
|
|
183
|
-
const stderr = typeof error === "object" && error !== null && "stderr" in error
|
|
184
|
-
? String(error.stderr)
|
|
185
|
-
: "";
|
|
186
|
-
const message = stderr.trim().length > 0
|
|
187
|
-
? stderr.trim()
|
|
188
|
-
: error instanceof Error
|
|
189
|
-
? error.message
|
|
190
|
-
: String(error);
|
|
191
|
-
/* c8 ignore stop */
|
|
192
|
-
throw new PmCliError(`Git command failed: git ${args.join(" ")}\n${message}`, EXIT_CODE.GENERIC_FAILURE);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/** Implements resolve npm command name for the public runtime surface of this module. */
|
|
196
|
-
export function resolveNpmCommandName(platform = process.platform) {
|
|
197
|
-
return platform === "win32" ? "npm.cmd" : "npm";
|
|
198
|
-
}
|
|
199
|
-
/** Implements should run npm command in shell for the public runtime surface of this module. */
|
|
200
|
-
export function shouldRunNpmCommandInShell(platform = process.platform) {
|
|
201
|
-
return platform === "win32";
|
|
202
|
-
}
|
|
203
|
-
async function runNpmCommand(args, cwd, execRunner = execFileAsync, platform = process.platform, environment = process.env) {
|
|
204
|
-
const npmCommand = resolveNpmCommandName(platform);
|
|
205
|
-
const env = Object.fromEntries(Object.entries(environment).filter(([key]) => key.toLowerCase() !== "npm_config_allow_scripts"));
|
|
206
|
-
env.NPM_CONFIG_ALLOW_SCRIPTS = "";
|
|
207
|
-
try {
|
|
208
|
-
const result = await execRunner(npmCommand, args, {
|
|
209
|
-
cwd,
|
|
210
|
-
encoding: "utf8",
|
|
211
|
-
env,
|
|
212
|
-
shell: shouldRunNpmCommandInShell(platform),
|
|
213
|
-
});
|
|
214
|
-
return (result.stdout ?? "").trim();
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
const stderr = typeof error === "object" && error !== null && "stderr" in error
|
|
218
|
-
? String(error.stderr)
|
|
219
|
-
: "";
|
|
220
|
-
const message = stderr.trim().length > 0
|
|
221
|
-
? stderr.trim()
|
|
222
|
-
: error instanceof Error
|
|
223
|
-
? error.message
|
|
224
|
-
: String(error);
|
|
225
|
-
throw new PmCliError(`npm command failed: ${npmCommand} ${args.join(" ")}\n${message}`, EXIT_CODE.GENERIC_FAILURE);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
function npmPackageNameFromSpec(spec) {
|
|
229
|
-
const trimmed = spec.trim();
|
|
230
|
-
const withoutAlias = trimmed.includes("@file:")
|
|
231
|
-
? trimmed.slice(0, trimmed.lastIndexOf("@file:"))
|
|
232
|
-
: trimmed;
|
|
233
|
-
if (withoutAlias.startsWith("@")) {
|
|
234
|
-
const scoped = withoutAlias.match(/^(@[^/@\s]+\/[^/@\s]+)/);
|
|
235
|
-
return scoped?.[1] ?? withoutAlias;
|
|
236
|
-
}
|
|
237
|
-
const unscoped = withoutAlias.match(/^([^/@\s]+)/);
|
|
238
|
-
return unscoped?.[1] ?? withoutAlias;
|
|
239
|
-
}
|
|
240
|
-
/** Return whether an npm spec is a complete local-file alias, including unchanged UNC targets. */
|
|
241
|
-
function isNpmLocalFileAliasSpec(spec) {
|
|
242
|
-
const marker = "@file:";
|
|
243
|
-
const markerIndex = spec.lastIndexOf(marker);
|
|
244
|
-
return (markerIndex > 0 &&
|
|
245
|
-
spec.slice(0, markerIndex).trim().length > 0 &&
|
|
246
|
-
spec.slice(markerIndex + marker.length).trim().length > 0);
|
|
247
|
-
}
|
|
248
|
-
/** Implements check whether npm not found error for the public runtime surface of this module. */
|
|
249
|
-
export function isNpmNotFoundError(error) {
|
|
250
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
251
|
-
const normalized = message.toLowerCase();
|
|
252
|
-
return (normalized.includes("npm err! code e404") ||
|
|
253
|
-
normalized.includes("404 not found") ||
|
|
254
|
-
normalized.includes("is not in this registry"));
|
|
255
|
-
}
|
|
256
|
-
/** Implements check whether npm pack not found error for the public runtime surface of this module. */
|
|
257
|
-
export function isNpmPackNotFoundError(error) {
|
|
258
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
259
|
-
const normalized = message.toLowerCase();
|
|
260
|
-
return isNpmNotFoundError(error) || normalized.includes("not found");
|
|
261
|
-
}
|
|
262
|
-
function isFirstPartyPmPackageName(packageName) {
|
|
263
|
-
const normalized = packageName.trim().toLowerCase();
|
|
264
|
-
return (normalized.startsWith("@unbrained/pm-") || normalized.startsWith("pm-"));
|
|
265
|
-
}
|
|
266
|
-
/** Implements build npm not found recovery for the public runtime surface of this module. */
|
|
267
|
-
export function buildNpmNotFoundRecovery(spec) {
|
|
268
|
-
const packageName = npmPackageNameFromSpec(spec);
|
|
269
|
-
const isFirstPartyPackage = isFirstPartyPmPackageName(packageName);
|
|
270
|
-
const repoName = packageName.replace(/^.*\//, "");
|
|
271
|
-
const githubSource = isFirstPartyPackage
|
|
272
|
-
? `github.com/unbraind/${repoName}`
|
|
273
|
-
: undefined;
|
|
274
|
-
const nextBestCommand = githubSource
|
|
275
|
-
? `pm install --project ${githubSource}`
|
|
276
|
-
: undefined;
|
|
277
|
-
return {
|
|
278
|
-
message: isFirstPartyPackage
|
|
279
|
-
? `npm package "${spec}" was not found in the registry. If this is an unpublished first-party pm package, install its GitHub repository instead.`
|
|
280
|
-
: `npm package "${spec}" was not found in the registry.`,
|
|
281
|
-
context: {
|
|
282
|
-
code: "npm_package_not_found",
|
|
283
|
-
required: "Use an install source that exists, or publish the npm package before installing it with npm:<name>.",
|
|
284
|
-
why: "Classifying npm 404s avoids repeated registry retries and gives agents a deterministic fallback path.",
|
|
285
|
-
examples: nextBestCommand
|
|
286
|
-
? [nextBestCommand, `pm package catalog --project --json`]
|
|
287
|
-
: [`npm view ${packageName}`, `pm package catalog --project --json`],
|
|
288
|
-
nextSteps: nextBestCommand
|
|
289
|
-
? [
|
|
290
|
-
`Try ${nextBestCommand} if the repository exists.`,
|
|
291
|
-
"Use pm package catalog --project --json to inspect bundled package aliases before installing.",
|
|
292
|
-
]
|
|
293
|
-
: [
|
|
294
|
-
`Verify ${packageName} exists in the npm registry and that you have access to it.`,
|
|
295
|
-
"Use pm package catalog --project --json to inspect bundled package aliases before installing.",
|
|
296
|
-
],
|
|
297
|
-
recovery: {
|
|
298
|
-
attempted_command: `pm install --project npm:${spec}`,
|
|
299
|
-
normalized_args: ["install", "--project", `npm:${spec}`],
|
|
300
|
-
...(githubSource && nextBestCommand
|
|
301
|
-
? {
|
|
302
|
-
fallback_candidates: [
|
|
303
|
-
{
|
|
304
|
-
source: githubSource,
|
|
305
|
-
command: nextBestCommand,
|
|
306
|
-
reason: "canonical first-party GitHub repository fallback for unpublished pm packages",
|
|
307
|
-
},
|
|
308
|
-
],
|
|
309
|
-
next_best_command: nextBestCommand,
|
|
310
|
-
}
|
|
311
|
-
: {}),
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
/** Implements wrap npm pack resolution error for the public runtime surface of this module. */
|
|
317
|
-
export function wrapNpmPackResolutionError(spec, error) {
|
|
318
|
-
if (!isNpmPackNotFoundError(error)) {
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
const recovery = buildNpmNotFoundRecovery(spec);
|
|
322
|
-
return new PmCliError(recovery.message, EXIT_CODE.NOT_FOUND, recovery.context);
|
|
323
|
-
}
|
|
324
|
-
async function resolveLocalNpmPackagePath(spec) {
|
|
325
|
-
if (path.isAbsolute(spec) || spec.startsWith(".") || spec.startsWith("..")) {
|
|
326
|
-
const absolutePath = path.resolve(process.cwd(), spec);
|
|
327
|
-
return (await pathExists(absolutePath)) ? absolutePath : null;
|
|
328
|
-
}
|
|
329
|
-
try {
|
|
330
|
-
const parsed = new URL(spec);
|
|
331
|
-
if (parsed.protocol === "file:") {
|
|
332
|
-
const absolutePath = fileURLToPath(parsed);
|
|
333
|
-
return (await pathExists(absolutePath)) ? absolutePath : null;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
// Registry package specs are not URLs.
|
|
338
|
-
}
|
|
339
|
-
if (!/^[a-z][a-z0-9+.-]*:/i.test(spec)) {
|
|
340
|
-
const absolutePath = path.resolve(process.cwd(), spec);
|
|
341
|
-
if (await pathExists(absolutePath)) {
|
|
342
|
-
return absolutePath;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return null;
|
|
346
|
-
}
|
|
347
|
-
async function resolveNpmPackSpec(spec) {
|
|
348
|
-
const localPath = await resolveLocalNpmPackagePath(spec);
|
|
349
|
-
if (localPath) {
|
|
350
|
-
// Hand npm pack a NATIVE filesystem path, never a percent-encoded file URL:
|
|
351
|
-
// npm opens the spec literally, so spaces (`%20`) or a Windows 8.3 `~` short
|
|
352
|
-
// name (`%7E`) escaped by pathToFileURL make pack fail ENOENT on every
|
|
353
|
-
// platform (GH-363). resolveLocalNpmPackagePath already decoded the path.
|
|
354
|
-
return localPath;
|
|
355
|
-
}
|
|
356
|
-
const localFileAlias = normalizeNpmLocalFileAliasSpec(spec);
|
|
357
|
-
if (localFileAlias !== spec) {
|
|
358
|
-
return localFileAlias;
|
|
359
|
-
}
|
|
360
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(spec)) {
|
|
361
|
-
return spec;
|
|
362
|
-
}
|
|
363
|
-
return spec;
|
|
364
|
-
}
|
|
365
|
-
/** Implements normalize npm local file alias spec for the public runtime surface of this module. */
|
|
366
|
-
export function normalizeNpmLocalFileAliasSpec(spec, cwd = process.cwd()) {
|
|
367
|
-
const marker = "@file:";
|
|
368
|
-
const markerIndex = spec.lastIndexOf(marker);
|
|
369
|
-
if (!isNpmLocalFileAliasSpec(spec)) {
|
|
370
|
-
return spec;
|
|
371
|
-
}
|
|
372
|
-
const packageName = spec.slice(0, markerIndex);
|
|
373
|
-
const target = spec.slice(markerIndex + marker.length);
|
|
374
|
-
// `file://host/share` (exactly two leading slashes) is a UNC / network spec,
|
|
375
|
-
// not a local path — leave it for npm to resolve.
|
|
376
|
-
if (target.startsWith("//") && !target.startsWith("///")) {
|
|
377
|
-
return spec;
|
|
378
|
-
}
|
|
379
|
-
// A bare relative target resolves against cwd.
|
|
380
|
-
if (!target.startsWith("/")) {
|
|
381
|
-
return `${packageName}@${path.resolve(cwd, target)}`;
|
|
382
|
-
}
|
|
383
|
-
// Resolve an absolute `file:` URL target to a NATIVE, percent-DECODED path
|
|
384
|
-
// (never an encoded file URL): npm opens the alias target literally, so an
|
|
385
|
-
// escaped path — spaces (`%20`) or a Windows 8.3 `~` short name escaped to
|
|
386
|
-
// `%7E` — fails ENOENT on every platform (GH-363). Decode via URL +
|
|
387
|
-
// decodeURIComponent rather than fileURLToPath, which throws
|
|
388
|
-
// ERR_INVALID_FILE_URL_PATH for a driveless absolute path supplied on Windows;
|
|
389
|
-
// strip the leading slash before a Windows drive letter (`/C:/x` -> `C:/x`).
|
|
390
|
-
let decodedPath;
|
|
391
|
-
try {
|
|
392
|
-
decodedPath = decodeURIComponent(new URL(`file:${target}`).pathname);
|
|
393
|
-
}
|
|
394
|
-
catch {
|
|
395
|
-
// Malformed percent-encoding (e.g. `%ZZ`) makes decodeURIComponent throw a
|
|
396
|
-
// URIError; leave the spec untouched so npm surfaces a clear error instead
|
|
397
|
-
// of crashing the CLI on an uncaught exception.
|
|
398
|
-
return spec;
|
|
399
|
-
}
|
|
400
|
-
const nativePath = /^\/[A-Za-z]:/.test(decodedPath)
|
|
401
|
-
? decodedPath.slice(1)
|
|
402
|
-
: decodedPath;
|
|
403
|
-
return `${packageName}@${nativePath}`;
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Parse npm pack output and bind the reported artifact to its isolated target.
|
|
407
|
-
*
|
|
408
|
-
* Supports current JSON metadata and the legacy filename-only form while
|
|
409
|
-
* refusing absolute or traversing results before the filesystem is consulted.
|
|
410
|
-
*/
|
|
411
|
-
function resolvePackedTarball(packDirectory, filename) {
|
|
412
|
-
const tarball = path.resolve(packDirectory, filename);
|
|
413
|
-
const relativeTarball = path.relative(packDirectory, tarball);
|
|
414
|
-
if (relativeTarball.length === 0 ||
|
|
415
|
-
relativeTarball === ".." ||
|
|
416
|
-
relativeTarball.startsWith(`..${path.sep}`) ||
|
|
417
|
-
path.isAbsolute(relativeTarball)) {
|
|
418
|
-
throw new PmCliError("npm pack reported a package archive outside its isolated destination.", EXIT_CODE.GENERIC_FAILURE, {
|
|
419
|
-
code: "npm_package_archive_unsafe",
|
|
420
|
-
required: "Retry with a trusted npm client that writes its archive inside the requested pack destination.",
|
|
421
|
-
why: "pm will not read a package-manager artifact outside the isolated directory created for this install.",
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
return tarball;
|
|
425
|
-
}
|
|
426
|
-
function decodePackedNpmCandidate(value, key) {
|
|
427
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
428
|
-
return undefined;
|
|
429
|
-
}
|
|
430
|
-
const record = value;
|
|
431
|
-
if (typeof record.filename !== "string" ||
|
|
432
|
-
record.filename.trim().length === 0) {
|
|
433
|
-
return undefined;
|
|
434
|
-
}
|
|
435
|
-
return {
|
|
436
|
-
...(key === undefined ? {} : { key }),
|
|
437
|
-
filename: record.filename,
|
|
438
|
-
...(typeof record.name === "string" ? { name: record.name } : {}),
|
|
439
|
-
...(typeof record.version === "string" ? { version: record.version } : {}),
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
function collectPackedNpmCandidates(parsed) {
|
|
443
|
-
const entries = Array.isArray(parsed)
|
|
444
|
-
? parsed.map((value) => ({ value }))
|
|
445
|
-
: typeof parsed === "object" && parsed !== null
|
|
446
|
-
? Object.entries(parsed).map(([key, value]) => ({ key, value }))
|
|
447
|
-
: [];
|
|
448
|
-
return entries.flatMap(({ key, value }) => {
|
|
449
|
-
const candidate = decodePackedNpmCandidate(value, key);
|
|
450
|
-
return candidate ? [candidate] : [];
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
function selectPackedNpmCandidate(candidates, expectedPackage) {
|
|
454
|
-
const normalizedExpected = expectedPackage?.trim().toLowerCase();
|
|
455
|
-
const matchingCandidates = normalizedExpected
|
|
456
|
-
? candidates.filter((candidate) => {
|
|
457
|
-
const normalizedName = candidate.name?.trim().toLowerCase();
|
|
458
|
-
return normalizedName !== undefined
|
|
459
|
-
? normalizedName === normalizedExpected
|
|
460
|
-
: candidate.key?.trim().toLowerCase() === normalizedExpected;
|
|
461
|
-
})
|
|
462
|
-
: [];
|
|
463
|
-
if (matchingCandidates.length === 1)
|
|
464
|
-
return matchingCandidates[0];
|
|
465
|
-
if (candidates.length !== 1)
|
|
466
|
-
return undefined;
|
|
467
|
-
const soleCandidate = candidates[0];
|
|
468
|
-
const soleIdentity = soleCandidate.name ?? soleCandidate.key;
|
|
469
|
-
return normalizedExpected &&
|
|
470
|
-
soleIdentity !== undefined &&
|
|
471
|
-
soleIdentity.trim().toLowerCase() !== normalizedExpected
|
|
472
|
-
? undefined
|
|
473
|
-
: soleCandidate;
|
|
474
|
-
}
|
|
475
|
-
function parsePackedNpmPackage(stdout, packDirectory, expectedPackage) {
|
|
476
|
-
let parsed;
|
|
477
|
-
try {
|
|
478
|
-
parsed = JSON.parse(stdout);
|
|
479
|
-
}
|
|
480
|
-
catch {
|
|
481
|
-
const filename = stdout
|
|
482
|
-
.split(/\r?\n/)
|
|
483
|
-
.map((line) => line.trim())
|
|
484
|
-
.filter((line) => line.length > 0)
|
|
485
|
-
.at(-1);
|
|
486
|
-
if (!filename) {
|
|
487
|
-
throw new PmCliError("npm pack did not report a tarball filename.", EXIT_CODE.GENERIC_FAILURE);
|
|
488
|
-
}
|
|
489
|
-
return { tarball: resolvePackedTarball(packDirectory, filename) };
|
|
490
|
-
}
|
|
491
|
-
const candidates = collectPackedNpmCandidates(parsed);
|
|
492
|
-
const selected = selectPackedNpmCandidate(candidates, expectedPackage);
|
|
493
|
-
if (!selected) {
|
|
494
|
-
throw new PmCliError("npm pack --json returned an unsupported or ambiguous package receipt.", EXIT_CODE.GENERIC_FAILURE, {
|
|
495
|
-
code: "npm_pack_json_shape_unsupported",
|
|
496
|
-
required: "Use an npm client that returns an array of package receipts or an object keyed by package name, with exactly one requested package entry containing filename.",
|
|
497
|
-
why: `Well-formed JSON contained ${candidates.length} usable package receipt(s), but pm could not select ${expectedPackage ?? "one package"} deterministically.`,
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
return {
|
|
501
|
-
tarball: resolvePackedTarball(packDirectory, selected.filename),
|
|
502
|
-
package: selected.name ?? selected.key,
|
|
503
|
-
version: selected.version,
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
async function resolveNpmSourceDirectory(source) {
|
|
507
|
-
return resolveNpmSourceDirectoryWithRunner(source, runNpmCommand);
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Resolve an npm source with an injectable, argv-safe package-manager runner.
|
|
511
|
-
*
|
|
512
|
-
* Local package directories remain in place, while local and registry archives
|
|
513
|
-
* share the same bounded validation, dependency installation, and cleanup
|
|
514
|
-
* contract. Registry artifacts must also exist inside the requested pack root.
|
|
515
|
-
*/
|
|
516
|
-
async function resolveNpmSourceDirectoryWithRunner(source, npmRunner) {
|
|
517
|
-
const localPackageRoot = await resolveLocalNpmPackagePath(source.spec);
|
|
518
|
-
if (localPackageRoot) {
|
|
519
|
-
const localStats = await fs.stat(localPackageRoot);
|
|
520
|
-
if (localStats.isFile() && isLocalPackageArchive(localPackageRoot)) {
|
|
521
|
-
return extractLocalPackageArchive(localPackageRoot);
|
|
522
|
-
}
|
|
523
|
-
const packageJsonPath = path.join(localPackageRoot, "package.json");
|
|
524
|
-
const packageJson = (await pathExists(packageJsonPath))
|
|
525
|
-
? JSON.parse(await fs.readFile(packageJsonPath, "utf8"))
|
|
526
|
-
: {};
|
|
527
|
-
return {
|
|
528
|
-
directory: await resolvePackageExtensionDirectory(localPackageRoot, source.input),
|
|
529
|
-
package: typeof packageJson.name === "string" ? packageJson.name : undefined,
|
|
530
|
-
version: typeof packageJson.version === "string"
|
|
531
|
-
? packageJson.version
|
|
532
|
-
: undefined,
|
|
533
|
-
cleanup: async () => { },
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "pm-npm-package-source-"));
|
|
537
|
-
const packDirectory = path.join(tempRoot, "pack");
|
|
538
|
-
await fs.mkdir(packDirectory, { recursive: true });
|
|
539
|
-
try {
|
|
540
|
-
const packSpec = await resolveNpmPackSpec(source.spec);
|
|
541
|
-
const packStdout = await npmRunner([
|
|
542
|
-
"pack",
|
|
543
|
-
packSpec,
|
|
544
|
-
"--json",
|
|
545
|
-
"--pack-destination",
|
|
546
|
-
packDirectory,
|
|
547
|
-
]);
|
|
548
|
-
const packed = parsePackedNpmPackage(packStdout, packDirectory, isNpmLocalFileAliasSpec(source.spec)
|
|
549
|
-
? undefined
|
|
550
|
-
: npmPackageNameFromSpec(source.spec));
|
|
551
|
-
if (!(await pathExists(packed.tarball))) {
|
|
552
|
-
throw new PmCliError("npm pack did not create its reported package archive.", EXIT_CODE.GENERIC_FAILURE, {
|
|
553
|
-
code: "npm_package_archive_missing",
|
|
554
|
-
required: "Retry the npm package install with a healthy registry and writable temporary directory.",
|
|
555
|
-
why: "The package manager reported a tarball filename that was absent before validation, so pm cannot safely extract or install it.",
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
const extracted = await extractLocalPackageArchive(packed.tarball);
|
|
559
|
-
return {
|
|
560
|
-
directory: extracted.directory,
|
|
561
|
-
package: packed.package ?? extracted.package,
|
|
562
|
-
version: packed.version ?? extracted.version,
|
|
563
|
-
cleanup: async () => {
|
|
564
|
-
try {
|
|
565
|
-
await extracted.cleanup();
|
|
566
|
-
}
|
|
567
|
-
finally {
|
|
568
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
569
|
-
}
|
|
570
|
-
},
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
catch (error) {
|
|
574
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
575
|
-
const wrappedError = wrapNpmPackResolutionError(source.spec, error);
|
|
576
|
-
if (wrappedError) {
|
|
577
|
-
throw wrappedError;
|
|
578
|
-
}
|
|
579
|
-
throw error;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
/** Return whether a filesystem path names one supported npm package archive. */
|
|
583
|
-
function isLocalPackageArchive(candidate) {
|
|
584
|
-
const normalized = candidate.toLowerCase();
|
|
585
|
-
return normalized.endsWith(".tgz") || normalized.endsWith(".tar.gz");
|
|
586
|
-
}
|
|
587
|
-
function failLocalPackageArchive(message) {
|
|
588
|
-
throw new PmCliError(message, EXIT_CODE.USAGE, {
|
|
589
|
-
code: "local_package_archive_unsafe",
|
|
590
|
-
required: "Use a bounded npm .tgz or .tar.gz archive with one package/ root and no links or escaping paths.",
|
|
591
|
-
why: "Package archives are untrusted input and must be validated before any entry reaches the filesystem.",
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
function validateLocalPackageArchiveEntry(entry, limits, state) {
|
|
595
|
-
if (entry.meta)
|
|
596
|
-
return;
|
|
597
|
-
state.entries += 1;
|
|
598
|
-
if (state.entries > limits.maxEntries) {
|
|
599
|
-
failLocalPackageArchive(`Local package archive exceeds the ${limits.maxEntries} entry limit.`);
|
|
600
|
-
}
|
|
601
|
-
const archivePath = entry.path;
|
|
602
|
-
const segments = archivePath.split("/");
|
|
603
|
-
if (archivePath.includes("\\") ||
|
|
604
|
-
archivePath.startsWith("/") ||
|
|
605
|
-
/^[A-Za-z]:/u.test(archivePath) ||
|
|
606
|
-
segments.some((segment) => segment === "..")) {
|
|
607
|
-
failLocalPackageArchive(`Local package archive contains an escaping path: "${archivePath}".`);
|
|
608
|
-
}
|
|
609
|
-
if (segments[0] !== "package") {
|
|
610
|
-
failLocalPackageArchive(`Local package archive entry "${archivePath}" is outside the required package/ root.`);
|
|
611
|
-
}
|
|
612
|
-
if (entry.type === "SymbolicLink" || entry.type === "Link") {
|
|
613
|
-
failLocalPackageArchive(`Local package archive link "${archivePath}" is not supported.`);
|
|
614
|
-
}
|
|
615
|
-
if (entry.type !== "File" &&
|
|
616
|
-
entry.type !== "OldFile" &&
|
|
617
|
-
entry.type !== "Directory") {
|
|
618
|
-
failLocalPackageArchive(`Local package archive entry "${archivePath}" uses unsupported type "${entry.type}".`);
|
|
619
|
-
}
|
|
620
|
-
if (entry.size > limits.maxEntryBytes) {
|
|
621
|
-
failLocalPackageArchive(`Local package archive entry "${archivePath}" exceeds the ${limits.maxEntryBytes} byte entry limit.`);
|
|
622
|
-
}
|
|
623
|
-
state.expandedBytes += entry.size;
|
|
624
|
-
if (state.expandedBytes > limits.maxExpandedBytes) {
|
|
625
|
-
failLocalPackageArchive(`Local package archive exceeds the ${limits.maxExpandedBytes} expanded byte limit.`);
|
|
626
|
-
}
|
|
627
|
-
if (archivePath === "package/package.json") {
|
|
628
|
-
state.packageJsonEntries += 1;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
/** Validate and extract one local npm package archive into an isolated temporary root. */
|
|
632
|
-
async function extractLocalPackageArchive(archivePath, limits = LOCAL_PACKAGE_ARCHIVE_LIMITS) {
|
|
633
|
-
const archiveStats = await fs.stat(archivePath);
|
|
634
|
-
if (archiveStats.size > limits.maxArchiveBytes) {
|
|
635
|
-
failLocalPackageArchive(`Local package archive exceeds the ${limits.maxArchiveBytes} archive byte limit.`);
|
|
636
|
-
}
|
|
637
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "pm-local-package-archive-"));
|
|
638
|
-
const extractDirectory = path.join(tempRoot, "extract");
|
|
639
|
-
await fs.mkdir(extractDirectory, { recursive: true });
|
|
640
|
-
const state = { entries: 0, expandedBytes: 0, packageJsonEntries: 0 };
|
|
641
|
-
let validationError;
|
|
642
|
-
try {
|
|
643
|
-
await listTar({
|
|
644
|
-
file: archivePath,
|
|
645
|
-
strict: true,
|
|
646
|
-
maxDecompressionRatio: 100,
|
|
647
|
-
onReadEntry: (entry) => {
|
|
648
|
-
if (validationError !== undefined)
|
|
649
|
-
return;
|
|
650
|
-
try {
|
|
651
|
-
validateLocalPackageArchiveEntry(entry, limits, state);
|
|
652
|
-
}
|
|
653
|
-
catch (error) {
|
|
654
|
-
validationError = error;
|
|
655
|
-
}
|
|
656
|
-
},
|
|
657
|
-
});
|
|
658
|
-
if (validationError !== undefined)
|
|
659
|
-
throw validationError;
|
|
660
|
-
if (state.packageJsonEntries !== 1) {
|
|
661
|
-
failLocalPackageArchive(`Local package archive must contain exactly one package/package.json entry; found ${state.packageJsonEntries}.`);
|
|
662
|
-
}
|
|
663
|
-
await extractTar({
|
|
664
|
-
file: archivePath,
|
|
665
|
-
cwd: extractDirectory,
|
|
666
|
-
strict: true,
|
|
667
|
-
preservePaths: false,
|
|
668
|
-
preserveOwner: false,
|
|
669
|
-
noMtime: true,
|
|
670
|
-
unlink: true,
|
|
671
|
-
maxDepth: 64,
|
|
672
|
-
maxDecompressionRatio: 100,
|
|
673
|
-
});
|
|
674
|
-
const packageRoot = path.join(extractDirectory, "package");
|
|
675
|
-
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
676
|
-
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
|
|
677
|
-
await installNpmPackageRuntimeDependencies(packageRoot);
|
|
678
|
-
return {
|
|
679
|
-
directory: await resolvePackageExtensionDirectory(packageRoot, archivePath),
|
|
680
|
-
package: typeof packageJson.name === "string" ? packageJson.name : undefined,
|
|
681
|
-
version: typeof packageJson.version === "string"
|
|
682
|
-
? packageJson.version
|
|
683
|
-
: undefined,
|
|
684
|
-
cleanup: async () => {
|
|
685
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
686
|
-
},
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
catch (error) {
|
|
690
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
691
|
-
throw error;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
async function installNpmPackageRuntimeDependencies(packageRoot, npmRunner = runNpmCommand) {
|
|
695
|
-
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
696
|
-
if (!(await pathExists(packageJsonPath))) {
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
let parsed;
|
|
700
|
-
try {
|
|
701
|
-
parsed = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
|
|
702
|
-
}
|
|
703
|
-
catch {
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
const manifest = parsed;
|
|
710
|
-
const dependencySpecs = runtimeDependencyInstallSpecs(manifest);
|
|
711
|
-
const shouldLinkHostedPmCli = hasHostedPmCliDependency(manifest);
|
|
712
|
-
if (dependencySpecs.length === 0 && !shouldLinkHostedPmCli) {
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
const runtimeOnlyManifest = { ...parsed };
|
|
716
|
-
delete runtimeOnlyManifest.devDependencies;
|
|
717
|
-
const installManifest = { ...runtimeOnlyManifest };
|
|
718
|
-
removeHostedPmCliDependency(installManifest);
|
|
719
|
-
await fs.writeFile(packageJsonPath, `${JSON.stringify(installManifest, null, 2)}\n`, "utf8");
|
|
720
|
-
try {
|
|
721
|
-
await Promise.all([
|
|
722
|
-
fs.rm(path.join(packageRoot, "package-lock.json"), { force: true }),
|
|
723
|
-
fs.rm(path.join(packageRoot, "npm-shrinkwrap.json"), { force: true }),
|
|
724
|
-
]);
|
|
725
|
-
if (dependencySpecs.length > 0) {
|
|
726
|
-
await npmRunner([
|
|
727
|
-
"install",
|
|
728
|
-
"--ignore-scripts",
|
|
729
|
-
"--no-audit",
|
|
730
|
-
"--fund=false",
|
|
731
|
-
"--package-lock=false",
|
|
732
|
-
"--no-save",
|
|
733
|
-
"--omit=peer",
|
|
734
|
-
"--",
|
|
735
|
-
], packageRoot);
|
|
736
|
-
}
|
|
737
|
-
if (shouldLinkHostedPmCli) {
|
|
738
|
-
await linkHostedPmCliDependency(packageRoot);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
finally {
|
|
742
|
-
await fs.writeFile(packageJsonPath, `${JSON.stringify(runtimeOnlyManifest, null, 2)}\n`, "utf8");
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
function hasHostedPmCliDependency(manifest) {
|
|
746
|
-
for (const dependencyMap of [
|
|
747
|
-
manifest.dependencies,
|
|
748
|
-
manifest.optionalDependencies,
|
|
749
|
-
manifest.peerDependencies,
|
|
750
|
-
]) {
|
|
751
|
-
if (typeof dependencyMap === "object" &&
|
|
752
|
-
dependencyMap !== null &&
|
|
753
|
-
PM_CLI_PACKAGE_NAME in dependencyMap) {
|
|
754
|
-
return true;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
return false;
|
|
758
|
-
}
|
|
759
|
-
function removeHostedPmCliDependency(manifest) {
|
|
760
|
-
for (const field of [
|
|
761
|
-
"dependencies",
|
|
762
|
-
"optionalDependencies",
|
|
763
|
-
"peerDependencies",
|
|
764
|
-
]) {
|
|
765
|
-
const dependencyMap = manifest[field];
|
|
766
|
-
if (typeof dependencyMap !== "object" || dependencyMap === null) {
|
|
767
|
-
continue;
|
|
768
|
-
}
|
|
769
|
-
const nextMap = { ...dependencyMap };
|
|
770
|
-
delete nextMap[PM_CLI_PACKAGE_NAME];
|
|
771
|
-
if (Object.keys(nextMap).length === 0) {
|
|
772
|
-
delete manifest[field];
|
|
773
|
-
}
|
|
774
|
-
else {
|
|
775
|
-
manifest[field] = nextMap;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
async function linkHostedPmCliDependency(packageRoot) {
|
|
780
|
-
const hostPackageRoot = resolvePmPackageRootFromModule(import.meta.url, [
|
|
781
|
-
"../../..",
|
|
782
|
-
]);
|
|
783
|
-
const [scope, packageName] = PM_CLI_PACKAGE_NAME.split("/");
|
|
784
|
-
const scopedDirectory = path.join(packageRoot, "node_modules", scope);
|
|
785
|
-
const linkPath = path.join(scopedDirectory, packageName);
|
|
786
|
-
await fs.mkdir(scopedDirectory, { recursive: true });
|
|
787
|
-
await fs.rm(linkPath, { recursive: true, force: true });
|
|
788
|
-
await fs.symlink(hostPackageRoot, linkPath, resolveDirectorySymlinkType(process.platform));
|
|
789
|
-
}
|
|
790
|
-
function resolveDirectorySymlinkType(platform) {
|
|
791
|
-
return platform === "win32" ? "junction" : "dir";
|
|
792
|
-
}
|
|
793
|
-
function validateRuntimeDependencySpec(name, version) {
|
|
794
|
-
if (name.startsWith("-")) {
|
|
795
|
-
throw new PmCliError(`Extension runtime dependency name "${name}" is unsafe because npm could interpret it as a command-line option.`, EXIT_CODE.USAGE, {
|
|
796
|
-
code: "extension_dependency_name_unsafe",
|
|
797
|
-
required: "Use a valid npm package name that does not begin with a dash.",
|
|
798
|
-
why: "Extension manifests are untrusted input and dependency names must never become npm options.",
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
const hasUnsafeControlOrShellCharacter = [...version].some((character) => {
|
|
802
|
-
const codePoint = character.charCodeAt(0);
|
|
803
|
-
return (codePoint <= 31 || codePoint === 127 || "&|;()`\"'".includes(character));
|
|
804
|
-
});
|
|
805
|
-
if (hasUnsafeControlOrShellCharacter) {
|
|
806
|
-
throw new PmCliError(`Extension runtime dependency "${name}" has an unsafe version specifier.`, EXIT_CODE.USAGE, {
|
|
807
|
-
code: "extension_dependency_version_unsafe",
|
|
808
|
-
required: "Use an npm version, range, dist-tag, URL, or file specifier without shell metacharacters.",
|
|
809
|
-
why: "npm.cmd requires a command shell on Windows, so untrusted shell metacharacters cannot be forwarded.",
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
try {
|
|
813
|
-
npa.resolve(name, version);
|
|
814
|
-
}
|
|
815
|
-
catch {
|
|
816
|
-
throw new PmCliError(`Extension runtime dependency "${name}@${version}" is not a valid npm dependency specifier.`, EXIT_CODE.USAGE, {
|
|
817
|
-
code: "extension_dependency_spec_invalid",
|
|
818
|
-
required: "Use a valid npm package name and npm-supported version, range, dist-tag, URL, or file specifier.",
|
|
819
|
-
why: "Only grammar-validated dependency specs may be written to the isolated runtime install manifest.",
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
return `${name}@${version}`;
|
|
823
|
-
}
|
|
824
|
-
function runtimeDependencyInstallSpecs(manifest) {
|
|
825
|
-
const specs = new Map();
|
|
826
|
-
for (const dependencyMap of [
|
|
827
|
-
manifest.dependencies,
|
|
828
|
-
manifest.optionalDependencies,
|
|
829
|
-
manifest.peerDependencies,
|
|
830
|
-
]) {
|
|
831
|
-
if (typeof dependencyMap !== "object" || dependencyMap === null) {
|
|
832
|
-
continue;
|
|
833
|
-
}
|
|
834
|
-
for (const [name, version] of Object.entries(dependencyMap)) {
|
|
835
|
-
if (name === PM_CLI_PACKAGE_NAME ||
|
|
836
|
-
typeof version !== "string" ||
|
|
837
|
-
version.trim().length === 0 ||
|
|
838
|
-
specs.has(name)) {
|
|
839
|
-
continue;
|
|
840
|
-
}
|
|
841
|
-
const normalizedVersion = version.trim();
|
|
842
|
-
specs.set(name, validateRuntimeDependencySpec(name, normalizedVersion));
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
return [...specs.values()];
|
|
846
|
-
}
|
|
847
|
-
async function resolvePackageExtensionDirectory(packageRoot, sourceLabel) {
|
|
848
|
-
const discovered = await collectPackageExtensionDirectories(packageRoot);
|
|
849
|
-
if (discovered.length === 1) {
|
|
850
|
-
return discovered[0];
|
|
851
|
-
}
|
|
852
|
-
if (discovered.length > 1) {
|
|
853
|
-
const choices = discovered
|
|
854
|
-
.map((entry) => path.relative(packageRoot, entry).replaceAll(path.sep, "/"))
|
|
855
|
-
.sort((left, right) => left.localeCompare(right));
|
|
856
|
-
throw new PmCliError(`Package source "${sourceLabel}" contains multiple extension manifests. Provide an explicit extension path. Candidates: ${choices.join(", ")}`, EXIT_CODE.USAGE);
|
|
857
|
-
}
|
|
858
|
-
throw new PmCliError(`Unable to locate a pm extension manifest in package source "${sourceLabel}". Package installs currently activate only extension resources, so add package.json pm.extensions or an extensions/ directory. Metadata-only resources like pm.docs/pm.examples are catalog metadata and do not activate commands.`, EXIT_CODE.USAGE);
|
|
859
|
-
}
|
|
860
|
-
async function resolveGithubSourceDirectory(cloneDirectory, source) {
|
|
861
|
-
const candidatePaths = [];
|
|
862
|
-
/* c8 ignore start -- subpath candidate expansion permutations are covered by source-resolution integration suites */
|
|
863
|
-
if (source.subpath) {
|
|
864
|
-
candidatePaths.push(source.subpath);
|
|
865
|
-
candidatePaths.push(path.posix.join(".agents/pm/extensions", source.subpath));
|
|
866
|
-
candidatePaths.push(path.posix.join(".custom/pm-extensions", source.subpath));
|
|
867
|
-
candidatePaths.push(path.posix.join(".custom/pm-extension", source.subpath));
|
|
868
|
-
}
|
|
869
|
-
/* c8 ignore stop */
|
|
870
|
-
for (const candidate of candidatePaths) {
|
|
871
|
-
const absolute = path.resolve(cloneDirectory, candidate);
|
|
872
|
-
if (!isPathWithinDirectory(cloneDirectory, absolute)) {
|
|
873
|
-
// source.subpath is user-controlled; never resolve a manifest outside the
|
|
874
|
-
// cloned repository (path-traversal guard).
|
|
875
|
-
continue;
|
|
876
|
-
}
|
|
877
|
-
if (await pathExists(path.join(absolute, "manifest.json"))) {
|
|
878
|
-
return { directory: absolute, resolved_subpath: candidate };
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
if (await pathExists(path.join(cloneDirectory, "manifest.json"))) {
|
|
882
|
-
return { directory: cloneDirectory, resolved_subpath: "." };
|
|
883
|
-
}
|
|
884
|
-
const discoveredDirectory = await resolvePackageExtensionDirectory(cloneDirectory, source.input);
|
|
885
|
-
return {
|
|
886
|
-
directory: discoveredDirectory,
|
|
887
|
-
resolved_subpath: path
|
|
888
|
-
.relative(cloneDirectory, discoveredDirectory)
|
|
889
|
-
.replaceAll(path.sep, "/"),
|
|
890
|
-
};
|
|
891
|
-
}
|
|
892
|
-
/** Bare package-ish names (no path separators, or a single-scope npm name) that miss local resolution are almost always intended as npm or bundled installs, so the not-found error must route agents to those sources instead of dead-ending. */
|
|
893
|
-
function looksLikeBarePackageName(input) {
|
|
894
|
-
const trimmed = input.trim();
|
|
895
|
-
if (trimmed.length === 0 ||
|
|
896
|
-
trimmed.startsWith(".") ||
|
|
897
|
-
trimmed.startsWith("~")) {
|
|
898
|
-
return false;
|
|
899
|
-
}
|
|
900
|
-
if (trimmed.startsWith("@")) {
|
|
901
|
-
return trimmed.split("/").length === 2;
|
|
902
|
-
}
|
|
903
|
-
return !trimmed.includes("/") && !trimmed.includes("\\");
|
|
904
|
-
}
|
|
905
|
-
async function buildLocalSourceNotFoundError(source) {
|
|
906
|
-
const baseMessage = `Local extension source does not exist: "${source.absolute_path}".`;
|
|
907
|
-
const input = source.input.trim();
|
|
908
|
-
if (!looksLikeBarePackageName(input)) {
|
|
909
|
-
return new PmCliError(baseMessage, EXIT_CODE.NOT_FOUND);
|
|
910
|
-
}
|
|
911
|
-
let bundledAliases = [];
|
|
912
|
-
try {
|
|
913
|
-
const envPackageRoot = process.env.PM_CLI_PACKAGE_ROOT;
|
|
914
|
-
const cacheKey = typeof envPackageRoot === "string" && envPackageRoot.trim().length > 0
|
|
915
|
-
? path.resolve(envPackageRoot.trim())
|
|
916
|
-
: "";
|
|
917
|
-
if (bundledPackageAliasesCache?.key !== cacheKey) {
|
|
918
|
-
bundledPackageAliasesCache = {
|
|
919
|
-
key: cacheKey,
|
|
920
|
-
aliases: await listBundledPackageAliases(),
|
|
921
|
-
};
|
|
922
|
-
}
|
|
923
|
-
bundledAliases = bundledPackageAliasesCache.aliases;
|
|
924
|
-
}
|
|
925
|
-
catch {
|
|
926
|
-
// Alias discovery is best-effort; the npm: hint alone still unblocks agents.
|
|
927
|
-
}
|
|
928
|
-
const nextSteps = [
|
|
929
|
-
`Retry with pm install npm:${input} if the package is published to npm.`,
|
|
930
|
-
`Or inspect bundled packages with pm package catalog --project --json before installing by alias. Known aliases (blank means none found): ${bundledAliases.join(", ")}`,
|
|
931
|
-
];
|
|
932
|
-
return new PmCliError(`${baseMessage} "${input}" did not match a local directory, bundled package alias, or bundled package name; if you meant an npm package, install it as "npm:${input}".`, EXIT_CODE.NOT_FOUND, {
|
|
933
|
-
code: "local_source_not_found_bare_name",
|
|
934
|
-
required: `Use "npm:${input}" for npm registry packages, a bundled catalog alias, or an existing local directory path.`,
|
|
935
|
-
why: "Bare names resolve as local paths only after bundled aliases, so unmatched names need an explicit npm: source.",
|
|
936
|
-
examples: [
|
|
937
|
-
`pm install npm:${input}`,
|
|
938
|
-
"pm package catalog --project --json",
|
|
939
|
-
],
|
|
940
|
-
nextSteps,
|
|
941
|
-
recovery: {
|
|
942
|
-
attempted_command: `pm install ${input}`,
|
|
943
|
-
normalized_args: ["install", input],
|
|
944
|
-
next_best_command: `pm install npm:${input}`,
|
|
945
|
-
},
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
/** Implements resolve install source for the public runtime surface of this module. */
|
|
949
|
-
export async function resolveInstallSource(source) {
|
|
950
|
-
if (source.kind === "local") {
|
|
951
|
-
let localStats;
|
|
952
|
-
try {
|
|
953
|
-
localStats = await fs.stat(source.absolute_path);
|
|
954
|
-
}
|
|
955
|
-
catch {
|
|
956
|
-
throw await buildLocalSourceNotFoundError(source);
|
|
957
|
-
}
|
|
958
|
-
if (localStats.isFile() && isLocalPackageArchive(source.absolute_path)) {
|
|
959
|
-
const resolved = await extractLocalPackageArchive(source.absolute_path);
|
|
960
|
-
return {
|
|
961
|
-
source,
|
|
962
|
-
directory: resolved.directory,
|
|
963
|
-
source_root: path.dirname(resolved.directory),
|
|
964
|
-
resolved_subpath: path
|
|
965
|
-
.relative(path.dirname(resolved.directory), resolved.directory)
|
|
966
|
-
.replaceAll(path.sep, "/"),
|
|
967
|
-
npm_package: resolved.package,
|
|
968
|
-
npm_version: resolved.version,
|
|
969
|
-
cleanup: resolved.cleanup,
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
if (!localStats.isDirectory()) {
|
|
973
|
-
throw new PmCliError(`Local extension source must be a directory or npm .tgz/.tar.gz archive: "${source.absolute_path}".`, EXIT_CODE.USAGE);
|
|
974
|
-
}
|
|
975
|
-
const directory = await resolvePackageExtensionDirectory(source.absolute_path, source.input);
|
|
976
|
-
return {
|
|
977
|
-
source,
|
|
978
|
-
directory,
|
|
979
|
-
source_root: source.absolute_path,
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
if (source.kind === "npm") {
|
|
983
|
-
const resolved = await resolveNpmSourceDirectory(source);
|
|
984
|
-
return {
|
|
985
|
-
source,
|
|
986
|
-
directory: resolved.directory,
|
|
987
|
-
source_root: path.dirname(resolved.directory),
|
|
988
|
-
cleanup: resolved.cleanup,
|
|
989
|
-
resolved_subpath: path
|
|
990
|
-
.relative(path.dirname(resolved.directory), resolved.directory)
|
|
991
|
-
.replaceAll(path.sep, "/"),
|
|
992
|
-
npm_package: resolved.package,
|
|
993
|
-
npm_version: resolved.version,
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
const cloneDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "pm-extension-source-"));
|
|
997
|
-
const cloneArgs = ["clone", "--depth", "1"];
|
|
998
|
-
if (source.ref) {
|
|
999
|
-
cloneArgs.push("--branch", source.ref);
|
|
1000
|
-
}
|
|
1001
|
-
cloneArgs.push(source.repository, cloneDirectory);
|
|
1002
|
-
try {
|
|
1003
|
-
await runGitCommand(cloneArgs);
|
|
1004
|
-
const commit = await runGitCommand([
|
|
1005
|
-
"-C",
|
|
1006
|
-
cloneDirectory,
|
|
1007
|
-
"rev-parse",
|
|
1008
|
-
"HEAD",
|
|
1009
|
-
]);
|
|
1010
|
-
const resolved = await resolveGithubSourceDirectory(cloneDirectory, source);
|
|
1011
|
-
return {
|
|
1012
|
-
source,
|
|
1013
|
-
directory: resolved.directory,
|
|
1014
|
-
source_root: cloneDirectory,
|
|
1015
|
-
resolved_subpath: resolved.resolved_subpath,
|
|
1016
|
-
commit,
|
|
1017
|
-
cleanup: async () => {
|
|
1018
|
-
await fs.rm(cloneDirectory, { recursive: true, force: true });
|
|
1019
|
-
},
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
catch (error) {
|
|
1023
|
-
await fs.rm(cloneDirectory, { recursive: true, force: true });
|
|
1024
|
-
throw error;
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
/** Implements are directories equivalent for the public runtime surface of this module. */
|
|
1028
|
-
export async function areDirectoriesEquivalent(left, right) {
|
|
1029
|
-
if (!(await pathExists(left)) || !(await pathExists(right))) {
|
|
1030
|
-
return false;
|
|
1031
|
-
}
|
|
1032
|
-
const [leftRealPath, rightRealPath] = await Promise.all([
|
|
1033
|
-
fs.realpath(left),
|
|
1034
|
-
fs.realpath(right),
|
|
1035
|
-
]);
|
|
1036
|
-
return leftRealPath === rightRealPath;
|
|
1037
|
-
}
|
|
1038
|
-
/** Public contract for test only install sources, shared by SDK and presentation-layer consumers. */
|
|
1039
|
-
export const _testOnlyInstallSources = {
|
|
1040
|
-
extractLocalPackageArchive,
|
|
1041
|
-
validateLocalPackageArchiveEntry,
|
|
1042
|
-
installNpmPackageRuntimeDependencies,
|
|
1043
|
-
npmPackageNameFromSpec,
|
|
1044
|
-
parsePackedNpmPackage,
|
|
1045
|
-
resolveNpmSourceDirectoryWithRunner,
|
|
1046
|
-
resolveNpmSourceDirectory,
|
|
1047
|
-
resolveNpmPackSpec,
|
|
1048
|
-
findInstalledNpmPackageCandidate,
|
|
1049
|
-
resolvePackageExtensionDirectory,
|
|
1050
|
-
runtimeDependencyInstallSpecs,
|
|
1051
|
-
hasHostedPmCliDependency,
|
|
1052
|
-
removeHostedPmCliDependency,
|
|
1053
|
-
linkHostedPmCliDependency,
|
|
1054
|
-
resolveDirectorySymlinkType,
|
|
1055
|
-
runNpmCommand,
|
|
1056
|
-
};
|
|
1
|
+
!(function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="68a37ea1-3571-59ab-b72c-0195efa84e3c")}catch(e2){}})();import{execFile}from"node:child_process";import fs from"node:fs/promises";import os from"node:os";import path from"node:path";import{fileURLToPath}from"node:url";import{promisify}from"node:util";import npa from"npm-package-arg";import{extract as extractTar,list as listTar}from"tar";import{collectPackageExtensionDirectories}from"../../core/packages/manifest.js";import{resolvePmPackageRootFromModule}from"../../core/packages/root.js";import{pathExists}from"../../core/fs/fs-utils.js";import{isPathWithinDirectory}from"../../core/fs/path-utils.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{listBundledPackageAliases}from"./bundled-catalog.js";const execFileAsync=promisify(execFile);const PM_CLI_PACKAGE_NAME="@unbrained/pm-cli";const LOCAL_PACKAGE_ARCHIVE_LIMITS={maxArchiveBytes:64*1024*1024,maxEntries:4096,maxExpandedBytes:256*1024*1024,maxEntryBytes:64*1024*1024};let bundledPackageAliasesCache;async function findInstalledNpmPackageCandidate(packageName,cwd=process.cwd()){const normalized=packageName.trim();if(!/^(@[^/\\\s]+\/[^/\\\s]+|[^@/\\\s][^/\\\s]*)$/u.test(normalized)){return null}let current=path.resolve(cwd);while(true){const directory=path.join(current,"node_modules",...normalized.split("/"));const manifestPath=path.join(directory,"package.json");if(await pathExists(manifestPath)){try{const manifest=JSON.parse(await fs.readFile(manifestPath,"utf8"));if(manifest.name===normalized){return{package:normalized,...typeof manifest.version==="string"?{version:manifest.version}:{},directory}}}catch{return null}}const parent=path.dirname(current);if(parent===current)return null;current=parent}}function parseGithubPathSpec(pathSpec,input,refOverride){const segments=pathSpec.split("/").map(segment=>segment.trim()).filter(segment=>segment.length>0);if(segments.length<2){return null}const owner=segments[0];const repo=segments[1].replace(/\.git$/i,"");if(owner.length===0||repo.length===0){return null}const tail=segments.slice(2);let ref;let subpath;if(tail[0]==="tree"&&tail.length>=2){ref=tail[1];subpath=tail.slice(2).join("/")}else if(tail.length>0){subpath=tail.join("/")}if(typeof refOverride==="string"&&refOverride.trim().length>0){ref=refOverride.trim()}return{kind:"github",input,owner,repo,repository:`https://github.com/${owner}/${repo}.git`,ref,subpath:subpath&&subpath.length>0?subpath:void 0}}function parseNpmInstallSource(normalizedInput,forceGithub,refOverride){const spec=normalizedInput.slice("npm:".length).trim();if(spec.length===0){throw new PmCliError('npm package source must include a package spec after "npm:".',EXIT_CODE.USAGE)}if(forceGithub){throw new PmCliError('Options "--gh/--github" cannot be combined with npm: package sources.',EXIT_CODE.USAGE)}if(refOverride){throw new PmCliError('Option "--ref" cannot be combined with npm: package sources.',EXIT_CODE.USAGE)}return{kind:"npm",input:normalizedInput,spec}}function parseExtensionInstallSource(input,options={}){const normalizedInput=input.trim();if(normalizedInput.length===0){throw new PmCliError("Extension source is required for --install.",EXIT_CODE.USAGE)}const refOverride=typeof options.ref==="string"&&options.ref.trim().length>0?options.ref.trim():void 0;if(normalizedInput.startsWith("npm:")){return parseNpmInstallSource(normalizedInput,options.forceGithub===true,refOverride)}const maybeGithubByUrl=(()=>{try{const parsed=new URL(normalizedInput);if(parsed.hostname!=="github.com"){return null}const pathSpec=parsed.pathname.replace(/^\/+/,"");return parseGithubPathSpec(pathSpec,normalizedInput,refOverride)}catch{return null}})();if(maybeGithubByUrl){return maybeGithubByUrl}const strippedDomainInput=normalizedInput.startsWith("github.com/")?normalizedInput.slice("github.com/".length):null;if(strippedDomainInput){const parsed=parseGithubPathSpec(strippedDomainInput,normalizedInput,refOverride);if(!parsed){throw new PmCliError(`Invalid GitHub source "${normalizedInput}".`,EXIT_CODE.USAGE)}return parsed}if(options.forceGithub){const parsed=parseGithubPathSpec(normalizedInput,normalizedInput,refOverride);if(!parsed){throw new PmCliError(`Invalid GitHub shorthand "${normalizedInput}".`,EXIT_CODE.USAGE)}return parsed}if(/^https?:\/\//i.test(normalizedInput)){throw new PmCliError(`Unsupported extension source URL "${normalizedInput}". Supported remote source host: github.com.`,EXIT_CODE.USAGE)}return{kind:"local",input:normalizedInput,absolute_path:path.resolve(process.cwd(),normalizedInput)}}async function runGitCommand(args,execRunner=execFileAsync,timeoutMs){try{const result=await execRunner("git",args,{encoding:"utf8",timeout:timeoutMs});return(result.stdout??"").trim()}catch(error){const stderr=typeof error==="object"&&error!==null&&"stderr"in error?String(error.stderr):"";const message=stderr.trim().length>0?stderr.trim():error instanceof Error?error.message:String(error);throw new PmCliError(`Git command failed: git ${args.join(" ")}
|
|
2
|
+
${message}`,EXIT_CODE.GENERIC_FAILURE)}}function resolveNpmCommandName(platform=process.platform){return platform==="win32"?"npm.cmd":"npm"}function shouldRunNpmCommandInShell(platform=process.platform){return platform==="win32"}async function runNpmCommand(args,cwd,execRunner=execFileAsync,platform=process.platform,environment=process.env){const npmCommand=resolveNpmCommandName(platform);const env=Object.fromEntries(Object.entries(environment).filter(([key])=>key.toLowerCase()!=="npm_config_allow_scripts"));env.NPM_CONFIG_ALLOW_SCRIPTS="";try{const result=await execRunner(npmCommand,args,{cwd,encoding:"utf8",env,shell:shouldRunNpmCommandInShell(platform)});return(result.stdout??"").trim()}catch(error){const stderr=typeof error==="object"&&error!==null&&"stderr"in error?String(error.stderr):"";const message=stderr.trim().length>0?stderr.trim():error instanceof Error?error.message:String(error);throw new PmCliError(`npm command failed: ${npmCommand} ${args.join(" ")}
|
|
3
|
+
${message}`,EXIT_CODE.GENERIC_FAILURE)}}function npmPackageNameFromSpec(spec){const trimmed=spec.trim();const withoutAlias=trimmed.includes("@file:")?trimmed.slice(0,trimmed.lastIndexOf("@file:")):trimmed;if(withoutAlias.startsWith("@")){const scoped=withoutAlias.match(/^(@[^/@\s]+\/[^/@\s]+)/);return scoped?.[1]??withoutAlias}const unscoped=withoutAlias.match(/^([^/@\s]+)/);return unscoped?.[1]??withoutAlias}function isNpmLocalFileAliasSpec(spec){const marker="@file:";const markerIndex=spec.lastIndexOf(marker);return markerIndex>0&&spec.slice(0,markerIndex).trim().length>0&&spec.slice(markerIndex+marker.length).trim().length>0}function isNpmNotFoundError(error){const message=error instanceof Error?error.message:String(error);const normalized=message.toLowerCase();return normalized.includes("npm err! code e404")||normalized.includes("404 not found")||normalized.includes("is not in this registry")}function isNpmPackNotFoundError(error){const message=error instanceof Error?error.message:String(error);const normalized=message.toLowerCase();return isNpmNotFoundError(error)||normalized.includes("not found")}function isFirstPartyPmPackageName(packageName){const normalized=packageName.trim().toLowerCase();return normalized.startsWith("@unbrained/pm-")||normalized.startsWith("pm-")}function buildNpmNotFoundRecovery(spec){const packageName=npmPackageNameFromSpec(spec);const isFirstPartyPackage=isFirstPartyPmPackageName(packageName);const repoName=packageName.replace(/^.*\//,"");const githubSource=isFirstPartyPackage?`github.com/unbraind/${repoName}`:void 0;const nextBestCommand=githubSource?`pm install --project ${githubSource}`:void 0;return{message:isFirstPartyPackage?`npm package "${spec}" was not found in the registry. If this is an unpublished first-party pm package, install its GitHub repository instead.`:`npm package "${spec}" was not found in the registry.`,context:{code:"npm_package_not_found",required:"Use an install source that exists, or publish the npm package before installing it with npm:<name>.",why:"Classifying npm 404s avoids repeated registry retries and gives agents a deterministic fallback path.",examples:nextBestCommand?[nextBestCommand,`pm package catalog --project --json`]:[`npm view ${packageName}`,`pm package catalog --project --json`],nextSteps:nextBestCommand?[`Try ${nextBestCommand} if the repository exists.`,"Use pm package catalog --project --json to inspect bundled package aliases before installing."]:[`Verify ${packageName} exists in the npm registry and that you have access to it.`,"Use pm package catalog --project --json to inspect bundled package aliases before installing."],recovery:{attempted_command:`pm install --project npm:${spec}`,normalized_args:["install","--project",`npm:${spec}`],...githubSource&&nextBestCommand?{fallback_candidates:[{source:githubSource,command:nextBestCommand,reason:"canonical first-party GitHub repository fallback for unpublished pm packages"}],next_best_command:nextBestCommand}:{}}}}}function wrapNpmPackResolutionError(spec,error){if(!isNpmPackNotFoundError(error)){return null}const recovery=buildNpmNotFoundRecovery(spec);return new PmCliError(recovery.message,EXIT_CODE.NOT_FOUND,recovery.context)}async function resolveLocalNpmPackagePath(spec){if(path.isAbsolute(spec)||spec.startsWith(".")||spec.startsWith("..")){const absolutePath=path.resolve(process.cwd(),spec);return await pathExists(absolutePath)?absolutePath:null}try{const parsed=new URL(spec);if(parsed.protocol==="file:"){const absolutePath=fileURLToPath(parsed);return await pathExists(absolutePath)?absolutePath:null}}catch{}if(!/^[a-z][a-z0-9+.-]*:/i.test(spec)){const absolutePath=path.resolve(process.cwd(),spec);if(await pathExists(absolutePath)){return absolutePath}}return null}async function resolveNpmPackSpec(spec){const localPath=await resolveLocalNpmPackagePath(spec);if(localPath){return localPath}const localFileAlias=normalizeNpmLocalFileAliasSpec(spec);if(localFileAlias!==spec){return localFileAlias}if(/^[a-z][a-z0-9+.-]*:/i.test(spec)){return spec}return spec}function normalizeNpmLocalFileAliasSpec(spec,cwd=process.cwd()){const marker="@file:";const markerIndex=spec.lastIndexOf(marker);if(!isNpmLocalFileAliasSpec(spec)){return spec}const packageName=spec.slice(0,markerIndex);const target=spec.slice(markerIndex+marker.length);if(target.startsWith("//")&&!target.startsWith("///")){return spec}if(!target.startsWith("/")){return`${packageName}@${path.resolve(cwd,target)}`}let decodedPath;try{decodedPath=decodeURIComponent(new URL(`file:${target}`).pathname)}catch{return spec}const nativePath=/^\/[A-Za-z]:/.test(decodedPath)?decodedPath.slice(1):decodedPath;return`${packageName}@${nativePath}`}function resolvePackedTarball(packDirectory,filename){const tarball=path.resolve(packDirectory,filename);const relativeTarball=path.relative(packDirectory,tarball);if(relativeTarball.length===0||relativeTarball===".."||relativeTarball.startsWith(`..${path.sep}`)||path.isAbsolute(relativeTarball)){throw new PmCliError("npm pack reported a package archive outside its isolated destination.",EXIT_CODE.GENERIC_FAILURE,{code:"npm_package_archive_unsafe",required:"Retry with a trusted npm client that writes its archive inside the requested pack destination.",why:"pm will not read a package-manager artifact outside the isolated directory created for this install."})}return tarball}function decodePackedNpmCandidate(value,key){if(typeof value!=="object"||value===null||Array.isArray(value)){return void 0}const record=value;if(typeof record.filename!=="string"||record.filename.trim().length===0){return void 0}return{...key===void 0?{}:{key},filename:record.filename,...typeof record.name==="string"?{name:record.name}:{},...typeof record.version==="string"?{version:record.version}:{}}}function collectPackedNpmCandidates(parsed){const entries=Array.isArray(parsed)?parsed.map(value=>({value})):typeof parsed==="object"&&parsed!==null?Object.entries(parsed).map(([key,value])=>({key,value})):[];return entries.flatMap(({key,value})=>{const candidate=decodePackedNpmCandidate(value,key);return candidate?[candidate]:[]})}function selectPackedNpmCandidate(candidates,expectedPackage){const normalizedExpected=expectedPackage?.trim().toLowerCase();const matchingCandidates=normalizedExpected?candidates.filter(candidate=>{const normalizedName=candidate.name?.trim().toLowerCase();return normalizedName!==void 0?normalizedName===normalizedExpected:candidate.key?.trim().toLowerCase()===normalizedExpected}):[];if(matchingCandidates.length===1)return matchingCandidates[0];if(candidates.length!==1)return void 0;const soleCandidate=candidates[0];const soleIdentity=soleCandidate.name??soleCandidate.key;return normalizedExpected&&soleIdentity!==void 0&&soleIdentity.trim().toLowerCase()!==normalizedExpected?void 0:soleCandidate}function parsePackedNpmPackage(stdout,packDirectory,expectedPackage){let parsed;try{parsed=JSON.parse(stdout)}catch{const filename=stdout.split(/\r?\n/).map(line=>line.trim()).filter(line=>line.length>0).at(-1);if(!filename){throw new PmCliError("npm pack did not report a tarball filename.",EXIT_CODE.GENERIC_FAILURE)}return{tarball:resolvePackedTarball(packDirectory,filename)}}const candidates=collectPackedNpmCandidates(parsed);const selected=selectPackedNpmCandidate(candidates,expectedPackage);if(!selected){throw new PmCliError("npm pack --json returned an unsupported or ambiguous package receipt.",EXIT_CODE.GENERIC_FAILURE,{code:"npm_pack_json_shape_unsupported",required:"Use an npm client that returns an array of package receipts or an object keyed by package name, with exactly one requested package entry containing filename.",why:`Well-formed JSON contained ${candidates.length} usable package receipt(s), but pm could not select ${expectedPackage??"one package"} deterministically.`})}return{tarball:resolvePackedTarball(packDirectory,selected.filename),package:selected.name??selected.key,version:selected.version}}async function resolveNpmSourceDirectory(source){return resolveNpmSourceDirectoryWithRunner(source,runNpmCommand)}async function resolveNpmSourceDirectoryWithRunner(source,npmRunner){const localPackageRoot=await resolveLocalNpmPackagePath(source.spec);if(localPackageRoot){const localStats=await fs.stat(localPackageRoot);if(localStats.isFile()&&isLocalPackageArchive(localPackageRoot)){return extractLocalPackageArchive(localPackageRoot)}const packageJsonPath=path.join(localPackageRoot,"package.json");const packageJson=await pathExists(packageJsonPath)?JSON.parse(await fs.readFile(packageJsonPath,"utf8")):{};return{directory:await resolvePackageExtensionDirectory(localPackageRoot,source.input),package:typeof packageJson.name==="string"?packageJson.name:void 0,version:typeof packageJson.version==="string"?packageJson.version:void 0,cleanup:async()=>{}}}const tempRoot=await fs.mkdtemp(path.join(os.tmpdir(),"pm-npm-package-source-"));const packDirectory=path.join(tempRoot,"pack");await fs.mkdir(packDirectory,{recursive:true});try{const packSpec=await resolveNpmPackSpec(source.spec);const packStdout=await npmRunner(["pack",packSpec,"--json","--pack-destination",packDirectory]);const packed=parsePackedNpmPackage(packStdout,packDirectory,isNpmLocalFileAliasSpec(source.spec)?void 0:npmPackageNameFromSpec(source.spec));if(!await pathExists(packed.tarball)){throw new PmCliError("npm pack did not create its reported package archive.",EXIT_CODE.GENERIC_FAILURE,{code:"npm_package_archive_missing",required:"Retry the npm package install with a healthy registry and writable temporary directory.",why:"The package manager reported a tarball filename that was absent before validation, so pm cannot safely extract or install it."})}const extracted=await extractLocalPackageArchive(packed.tarball);return{directory:extracted.directory,package:packed.package??extracted.package,version:packed.version??extracted.version,cleanup:async()=>{try{await extracted.cleanup()}finally{await fs.rm(tempRoot,{recursive:true,force:true})}}}}catch(error){await fs.rm(tempRoot,{recursive:true,force:true});const wrappedError=wrapNpmPackResolutionError(source.spec,error);if(wrappedError){throw wrappedError}throw error}}function isLocalPackageArchive(candidate){const normalized=candidate.toLowerCase();return normalized.endsWith(".tgz")||normalized.endsWith(".tar.gz")}function failLocalPackageArchive(message){throw new PmCliError(message,EXIT_CODE.USAGE,{code:"local_package_archive_unsafe",required:"Use a bounded npm .tgz or .tar.gz archive with one package/ root and no links or escaping paths.",why:"Package archives are untrusted input and must be validated before any entry reaches the filesystem."})}function validateLocalPackageArchiveEntry(entry,limits,state){if(entry.meta)return;state.entries+=1;if(state.entries>limits.maxEntries){failLocalPackageArchive(`Local package archive exceeds the ${limits.maxEntries} entry limit.`)}const archivePath=entry.path;const segments=archivePath.split("/");if(archivePath.includes("\\")||archivePath.startsWith("/")||/^[A-Za-z]:/u.test(archivePath)||segments.some(segment=>segment==="..")){failLocalPackageArchive(`Local package archive contains an escaping path: "${archivePath}".`)}if(segments[0]!=="package"){failLocalPackageArchive(`Local package archive entry "${archivePath}" is outside the required package/ root.`)}if(entry.type==="SymbolicLink"||entry.type==="Link"){failLocalPackageArchive(`Local package archive link "${archivePath}" is not supported.`)}if(entry.type!=="File"&&entry.type!=="OldFile"&&entry.type!=="Directory"){failLocalPackageArchive(`Local package archive entry "${archivePath}" uses unsupported type "${entry.type}".`)}if(entry.size>limits.maxEntryBytes){failLocalPackageArchive(`Local package archive entry "${archivePath}" exceeds the ${limits.maxEntryBytes} byte entry limit.`)}state.expandedBytes+=entry.size;if(state.expandedBytes>limits.maxExpandedBytes){failLocalPackageArchive(`Local package archive exceeds the ${limits.maxExpandedBytes} expanded byte limit.`)}if(archivePath==="package/package.json"){state.packageJsonEntries+=1}}async function extractLocalPackageArchive(archivePath,limits=LOCAL_PACKAGE_ARCHIVE_LIMITS){const archiveStats=await fs.stat(archivePath);if(archiveStats.size>limits.maxArchiveBytes){failLocalPackageArchive(`Local package archive exceeds the ${limits.maxArchiveBytes} archive byte limit.`)}const tempRoot=await fs.mkdtemp(path.join(os.tmpdir(),"pm-local-package-archive-"));const extractDirectory=path.join(tempRoot,"extract");await fs.mkdir(extractDirectory,{recursive:true});const state={entries:0,expandedBytes:0,packageJsonEntries:0};let validationError;try{await listTar({file:archivePath,strict:true,maxDecompressionRatio:100,onReadEntry:entry=>{if(validationError!==void 0)return;try{validateLocalPackageArchiveEntry(entry,limits,state)}catch(error){validationError=error}}});if(validationError!==void 0)throw validationError;if(state.packageJsonEntries!==1){failLocalPackageArchive(`Local package archive must contain exactly one package/package.json entry; found ${state.packageJsonEntries}.`)}await extractTar({file:archivePath,cwd:extractDirectory,strict:true,preservePaths:false,preserveOwner:false,noMtime:true,unlink:true,maxDepth:64,maxDecompressionRatio:100});const packageRoot=path.join(extractDirectory,"package");const packageJsonPath=path.join(packageRoot,"package.json");const packageJson=JSON.parse(await fs.readFile(packageJsonPath,"utf8"));await installNpmPackageRuntimeDependencies(packageRoot);return{directory:await resolvePackageExtensionDirectory(packageRoot,archivePath),package:typeof packageJson.name==="string"?packageJson.name:void 0,version:typeof packageJson.version==="string"?packageJson.version:void 0,cleanup:async()=>{await fs.rm(tempRoot,{recursive:true,force:true})}}}catch(error){await fs.rm(tempRoot,{recursive:true,force:true});throw error}}async function installNpmPackageRuntimeDependencies(packageRoot,npmRunner=runNpmCommand){const packageJsonPath=path.join(packageRoot,"package.json");if(!await pathExists(packageJsonPath)){return}let parsed;try{parsed=JSON.parse(await fs.readFile(packageJsonPath,"utf8"))}catch{return}if(typeof parsed!=="object"||parsed===null){return}const manifest=parsed;const dependencySpecs=runtimeDependencyInstallSpecs(manifest);const shouldLinkHostedPmCli=hasHostedPmCliDependency(manifest);if(dependencySpecs.length===0&&!shouldLinkHostedPmCli){return}const runtimeOnlyManifest={...parsed};delete runtimeOnlyManifest.devDependencies;const installManifest={...runtimeOnlyManifest};removeHostedPmCliDependency(installManifest);await fs.writeFile(packageJsonPath,`${JSON.stringify(installManifest,null,2)}
|
|
4
|
+
`,"utf8");try{await Promise.all([fs.rm(path.join(packageRoot,"package-lock.json"),{force:true}),fs.rm(path.join(packageRoot,"npm-shrinkwrap.json"),{force:true})]);if(dependencySpecs.length>0){await npmRunner(["install","--ignore-scripts","--no-audit","--fund=false","--package-lock=false","--no-save","--omit=peer","--"],packageRoot)}if(shouldLinkHostedPmCli){await linkHostedPmCliDependency(packageRoot)}}finally{await fs.writeFile(packageJsonPath,`${JSON.stringify(runtimeOnlyManifest,null,2)}
|
|
5
|
+
`,"utf8")}}function hasHostedPmCliDependency(manifest){for(const dependencyMap of[manifest.dependencies,manifest.optionalDependencies,manifest.peerDependencies]){if(typeof dependencyMap==="object"&&dependencyMap!==null&&PM_CLI_PACKAGE_NAME in dependencyMap){return true}}return false}function removeHostedPmCliDependency(manifest){for(const field of["dependencies","optionalDependencies","peerDependencies"]){const dependencyMap=manifest[field];if(typeof dependencyMap!=="object"||dependencyMap===null){continue}const nextMap={...dependencyMap};delete nextMap[PM_CLI_PACKAGE_NAME];if(Object.keys(nextMap).length===0){delete manifest[field]}else{manifest[field]=nextMap}}}async function linkHostedPmCliDependency(packageRoot){const hostPackageRoot=resolvePmPackageRootFromModule(import.meta.url,["../../.."]);const[scope,packageName]=PM_CLI_PACKAGE_NAME.split("/");const scopedDirectory=path.join(packageRoot,"node_modules",scope);const linkPath=path.join(scopedDirectory,packageName);await fs.mkdir(scopedDirectory,{recursive:true});await fs.rm(linkPath,{recursive:true,force:true});await fs.symlink(hostPackageRoot,linkPath,resolveDirectorySymlinkType(process.platform))}function resolveDirectorySymlinkType(platform){return platform==="win32"?"junction":"dir"}function validateRuntimeDependencySpec(name,version){if(name.startsWith("-")){throw new PmCliError(`Extension runtime dependency name "${name}" is unsafe because npm could interpret it as a command-line option.`,EXIT_CODE.USAGE,{code:"extension_dependency_name_unsafe",required:"Use a valid npm package name that does not begin with a dash.",why:"Extension manifests are untrusted input and dependency names must never become npm options."})}const hasUnsafeControlOrShellCharacter=[...version].some(character=>{const codePoint=character.charCodeAt(0);return codePoint<=31||codePoint===127||"&|;()`\"'".includes(character)});if(hasUnsafeControlOrShellCharacter){throw new PmCliError(`Extension runtime dependency "${name}" has an unsafe version specifier.`,EXIT_CODE.USAGE,{code:"extension_dependency_version_unsafe",required:"Use an npm version, range, dist-tag, URL, or file specifier without shell metacharacters.",why:"npm.cmd requires a command shell on Windows, so untrusted shell metacharacters cannot be forwarded."})}try{npa.resolve(name,version)}catch{throw new PmCliError(`Extension runtime dependency "${name}@${version}" is not a valid npm dependency specifier.`,EXIT_CODE.USAGE,{code:"extension_dependency_spec_invalid",required:"Use a valid npm package name and npm-supported version, range, dist-tag, URL, or file specifier.",why:"Only grammar-validated dependency specs may be written to the isolated runtime install manifest."})}return`${name}@${version}`}function runtimeDependencyInstallSpecs(manifest){const specs=new Map;for(const dependencyMap of[manifest.dependencies,manifest.optionalDependencies,manifest.peerDependencies]){if(typeof dependencyMap!=="object"||dependencyMap===null){continue}for(const[name,version]of Object.entries(dependencyMap)){if(name===PM_CLI_PACKAGE_NAME||typeof version!=="string"||version.trim().length===0||specs.has(name)){continue}const normalizedVersion=version.trim();specs.set(name,validateRuntimeDependencySpec(name,normalizedVersion))}}return[...specs.values()]}async function resolvePackageExtensionDirectory(packageRoot,sourceLabel){const discovered=await collectPackageExtensionDirectories(packageRoot);if(discovered.length===1){return discovered[0]}if(discovered.length>1){const choices=discovered.map(entry=>path.relative(packageRoot,entry).replaceAll(path.sep,"/")).sort((left,right)=>left.localeCompare(right));throw new PmCliError(`Package source "${sourceLabel}" contains multiple extension manifests. Provide an explicit extension path. Candidates: ${choices.join(", ")}`,EXIT_CODE.USAGE)}throw new PmCliError(`Unable to locate a pm extension manifest in package source "${sourceLabel}". Package installs currently activate only extension resources, so add package.json pm.extensions or an extensions/ directory. Metadata-only resources like pm.docs/pm.examples are catalog metadata and do not activate commands.`,EXIT_CODE.USAGE)}async function resolveGithubSourceDirectory(cloneDirectory,source){const candidatePaths=[];if(source.subpath){candidatePaths.push(source.subpath);candidatePaths.push(path.posix.join(".agents/pm/extensions",source.subpath));candidatePaths.push(path.posix.join(".custom/pm-extensions",source.subpath));candidatePaths.push(path.posix.join(".custom/pm-extension",source.subpath))}for(const candidate of candidatePaths){const absolute=path.resolve(cloneDirectory,candidate);if(!isPathWithinDirectory(cloneDirectory,absolute)){continue}if(await pathExists(path.join(absolute,"manifest.json"))){return{directory:absolute,resolved_subpath:candidate}}}if(await pathExists(path.join(cloneDirectory,"manifest.json"))){return{directory:cloneDirectory,resolved_subpath:"."}}const discoveredDirectory=await resolvePackageExtensionDirectory(cloneDirectory,source.input);return{directory:discoveredDirectory,resolved_subpath:path.relative(cloneDirectory,discoveredDirectory).replaceAll(path.sep,"/")}}function looksLikeBarePackageName(input){const trimmed=input.trim();if(trimmed.length===0||trimmed.startsWith(".")||trimmed.startsWith("~")){return false}if(trimmed.startsWith("@")){return trimmed.split("/").length===2}return!trimmed.includes("/")&&!trimmed.includes("\\")}async function buildLocalSourceNotFoundError(source){const baseMessage=`Local extension source does not exist: "${source.absolute_path}".`;const input=source.input.trim();if(!looksLikeBarePackageName(input)){return new PmCliError(baseMessage,EXIT_CODE.NOT_FOUND)}let bundledAliases=[];try{const envPackageRoot=process.env.PM_CLI_PACKAGE_ROOT;const cacheKey=typeof envPackageRoot==="string"&&envPackageRoot.trim().length>0?path.resolve(envPackageRoot.trim()):"";if(bundledPackageAliasesCache?.key!==cacheKey){bundledPackageAliasesCache={key:cacheKey,aliases:await listBundledPackageAliases()}}bundledAliases=bundledPackageAliasesCache.aliases}catch{}const nextSteps=[`Retry with pm install npm:${input} if the package is published to npm.`,`Or inspect bundled packages with pm package catalog --project --json before installing by alias. Known aliases (blank means none found): ${bundledAliases.join(", ")}`];return new PmCliError(`${baseMessage} "${input}" did not match a local directory, bundled package alias, or bundled package name; if you meant an npm package, install it as "npm:${input}".`,EXIT_CODE.NOT_FOUND,{code:"local_source_not_found_bare_name",required:`Use "npm:${input}" for npm registry packages, a bundled catalog alias, or an existing local directory path.`,why:"Bare names resolve as local paths only after bundled aliases, so unmatched names need an explicit npm: source.",examples:[`pm install npm:${input}`,"pm package catalog --project --json"],nextSteps,recovery:{attempted_command:`pm install ${input}`,normalized_args:["install",input],next_best_command:`pm install npm:${input}`}})}async function resolveInstallSource(source){if(source.kind==="local"){let localStats;try{localStats=await fs.stat(source.absolute_path)}catch{throw await buildLocalSourceNotFoundError(source)}if(localStats.isFile()&&isLocalPackageArchive(source.absolute_path)){const resolved=await extractLocalPackageArchive(source.absolute_path);return{source,directory:resolved.directory,source_root:path.dirname(resolved.directory),resolved_subpath:path.relative(path.dirname(resolved.directory),resolved.directory).replaceAll(path.sep,"/"),npm_package:resolved.package,npm_version:resolved.version,cleanup:resolved.cleanup}}if(!localStats.isDirectory()){throw new PmCliError(`Local extension source must be a directory or npm .tgz/.tar.gz archive: "${source.absolute_path}".`,EXIT_CODE.USAGE)}const directory=await resolvePackageExtensionDirectory(source.absolute_path,source.input);return{source,directory,source_root:source.absolute_path}}if(source.kind==="npm"){const resolved=await resolveNpmSourceDirectory(source);return{source,directory:resolved.directory,source_root:path.dirname(resolved.directory),cleanup:resolved.cleanup,resolved_subpath:path.relative(path.dirname(resolved.directory),resolved.directory).replaceAll(path.sep,"/"),npm_package:resolved.package,npm_version:resolved.version}}const cloneDirectory=await fs.mkdtemp(path.join(os.tmpdir(),"pm-extension-source-"));const cloneArgs=["clone","--depth","1"];if(source.ref){cloneArgs.push("--branch",source.ref)}cloneArgs.push(source.repository,cloneDirectory);try{await runGitCommand(cloneArgs);const commit=await runGitCommand(["-C",cloneDirectory,"rev-parse","HEAD"]);const resolved=await resolveGithubSourceDirectory(cloneDirectory,source);return{source,directory:resolved.directory,source_root:cloneDirectory,resolved_subpath:resolved.resolved_subpath,commit,cleanup:async()=>{await fs.rm(cloneDirectory,{recursive:true,force:true})}}}catch(error){await fs.rm(cloneDirectory,{recursive:true,force:true});throw error}}async function areDirectoriesEquivalent(left,right){if(!await pathExists(left)||!await pathExists(right)){return false}const[leftRealPath,rightRealPath]=await Promise.all([fs.realpath(left),fs.realpath(right)]);return leftRealPath===rightRealPath}const _testOnlyInstallSources={extractLocalPackageArchive,validateLocalPackageArchiveEntry,installNpmPackageRuntimeDependencies,npmPackageNameFromSpec,parsePackedNpmPackage,resolveNpmSourceDirectoryWithRunner,resolveNpmSourceDirectory,resolveNpmPackSpec,findInstalledNpmPackageCandidate,resolvePackageExtensionDirectory,runtimeDependencyInstallSpecs,hasHostedPmCliDependency,removeHostedPmCliDependency,linkHostedPmCliDependency,resolveDirectorySymlinkType,runNpmCommand};export{_testOnlyInstallSources,areDirectoriesEquivalent,buildNpmNotFoundRecovery,findInstalledNpmPackageCandidate,isNpmNotFoundError,isNpmPackNotFoundError,normalizeNpmLocalFileAliasSpec,parseExtensionInstallSource,resolveInstallSource,resolveNpmCommandName,runGitCommand,shouldRunNpmCommandInShell,wrapNpmPackResolutionError};
|
|
1057
6
|
//# sourceMappingURL=install-sources.js.map
|
|
1058
|
-
//# debugId=05f56edb-c08d-570c-b8b5-641fc694331e
|