@unbrained/pm-cli 2026.3.12 → 2026.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/pm/extensions/.managed-extensions.json +42 -0
- package/.agents/pm/extensions/beads/index.js +109 -0
- package/.agents/pm/extensions/beads/manifest.json +7 -0
- package/{dist/cli/commands/beads.js → .agents/pm/extensions/beads/runtime.js} +31 -21
- package/.agents/pm/extensions/beads/runtime.ts +702 -0
- package/.agents/pm/extensions/todos/index.js +126 -0
- package/.agents/pm/extensions/todos/manifest.json +7 -0
- package/{dist/extensions/builtins/todos/import-export.js → .agents/pm/extensions/todos/runtime.js} +39 -29
- package/.agents/pm/extensions/todos/runtime.ts +568 -0
- package/AGENTS.md +196 -92
- package/CHANGELOG.md +399 -0
- package/CODE_OF_CONDUCT.md +42 -0
- package/CONTRIBUTING.md +144 -0
- package/PRD.md +512 -164
- package/README.md +1053 -2
- package/SECURITY.md +51 -0
- package/dist/cli/commands/activity.d.ts +5 -0
- package/dist/cli/commands/activity.js +66 -3
- package/dist/cli/commands/activity.js.map +1 -1
- package/dist/cli/commands/aggregate.d.ts +54 -0
- package/dist/cli/commands/aggregate.js +181 -0
- package/dist/cli/commands/aggregate.js.map +1 -0
- package/dist/cli/commands/append.js +4 -1
- package/dist/cli/commands/append.js.map +1 -1
- package/dist/cli/commands/calendar.d.ts +109 -0
- package/dist/cli/commands/calendar.js +797 -0
- package/dist/cli/commands/calendar.js.map +1 -0
- package/dist/cli/commands/claim.d.ts +5 -1
- package/dist/cli/commands/claim.js +42 -21
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/close.d.ts +1 -0
- package/dist/cli/commands/close.js +54 -5
- package/dist/cli/commands/close.js.map +1 -1
- package/dist/cli/commands/comments-audit.d.ts +91 -0
- package/dist/cli/commands/comments-audit.js +195 -0
- package/dist/cli/commands/comments-audit.js.map +1 -0
- package/dist/cli/commands/comments.d.ts +1 -0
- package/dist/cli/commands/comments.js +70 -21
- package/dist/cli/commands/comments.js.map +1 -1
- package/dist/cli/commands/completion.d.ts +10 -4
- package/dist/cli/commands/completion.js +1184 -137
- package/dist/cli/commands/completion.js.map +1 -1
- package/dist/cli/commands/config.d.ts +35 -3
- package/dist/cli/commands/config.js +968 -13
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/context.d.ts +86 -0
- package/dist/cli/commands/context.js +299 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/contracts.d.ts +78 -0
- package/dist/cli/commands/contracts.js +920 -0
- package/dist/cli/commands/contracts.js.map +1 -0
- package/dist/cli/commands/create.d.ts +48 -14
- package/dist/cli/commands/create.js +1331 -160
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/dedupe-audit.d.ts +81 -0
- package/dist/cli/commands/dedupe-audit.js +330 -0
- package/dist/cli/commands/dedupe-audit.js.map +1 -0
- package/dist/cli/commands/deps.d.ts +52 -0
- package/dist/cli/commands/deps.js +204 -0
- package/dist/cli/commands/deps.js.map +1 -0
- package/dist/cli/commands/docs.d.ts +19 -0
- package/dist/cli/commands/docs.js +212 -13
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/extension.d.ts +122 -0
- package/dist/cli/commands/extension.js +1850 -0
- package/dist/cli/commands/extension.js.map +1 -0
- package/dist/cli/commands/files.d.ts +52 -1
- package/dist/cli/commands/files.js +443 -13
- package/dist/cli/commands/files.js.map +1 -1
- package/dist/cli/commands/gc.d.ts +11 -1
- package/dist/cli/commands/gc.js +89 -11
- package/dist/cli/commands/gc.js.map +1 -1
- package/dist/cli/commands/get.d.ts +13 -0
- package/dist/cli/commands/get.js +35 -3
- package/dist/cli/commands/get.js.map +1 -1
- package/dist/cli/commands/health.d.ts +10 -2
- package/dist/cli/commands/health.js +774 -23
- package/dist/cli/commands/health.js.map +1 -1
- package/dist/cli/commands/history.d.ts +20 -0
- package/dist/cli/commands/history.js +152 -6
- package/dist/cli/commands/history.js.map +1 -1
- package/dist/cli/commands/index.d.ts +16 -3
- package/dist/cli/commands/index.js +16 -3
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +7 -2
- package/dist/cli/commands/init.js +137 -5
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/learnings.d.ts +17 -0
- package/dist/cli/commands/learnings.js +129 -0
- package/dist/cli/commands/learnings.js.map +1 -0
- package/dist/cli/commands/list.d.ts +29 -1
- package/dist/cli/commands/list.js +289 -53
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/normalize.d.ts +51 -0
- package/dist/cli/commands/normalize.js +298 -0
- package/dist/cli/commands/normalize.js.map +1 -0
- package/dist/cli/commands/notes.d.ts +17 -0
- package/dist/cli/commands/notes.js +129 -0
- package/dist/cli/commands/notes.js.map +1 -0
- package/dist/cli/commands/reindex.d.ts +1 -0
- package/dist/cli/commands/reindex.js +208 -32
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/restore.js +164 -30
- package/dist/cli/commands/restore.js.map +1 -1
- package/dist/cli/commands/search.d.ts +14 -1
- package/dist/cli/commands/search.js +475 -81
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/stats.js +26 -10
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/cli/commands/templates.d.ts +26 -0
- package/dist/cli/commands/templates.js +179 -0
- package/dist/cli/commands/templates.js.map +1 -0
- package/dist/cli/commands/test-all.d.ts +19 -1
- package/dist/cli/commands/test-all.js +161 -13
- package/dist/cli/commands/test-all.js.map +1 -1
- package/dist/cli/commands/test-runs.d.ts +63 -0
- package/dist/cli/commands/test-runs.js +179 -0
- package/dist/cli/commands/test-runs.js.map +1 -0
- package/dist/cli/commands/test.d.ts +75 -1
- package/dist/cli/commands/test.js +1360 -41
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/update-many.d.ts +57 -0
- package/dist/cli/commands/update-many.js +631 -0
- package/dist/cli/commands/update-many.js.map +1 -0
- package/dist/cli/commands/update.d.ts +30 -0
- package/dist/cli/commands/update.js +1393 -84
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/validate.d.ts +30 -0
- package/dist/cli/commands/validate.js +1140 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/error-guidance.d.ts +33 -0
- package/dist/cli/error-guidance.js +337 -0
- package/dist/cli/error-guidance.js.map +1 -0
- package/dist/cli/extension-command-options.d.ts +1 -0
- package/dist/cli/extension-command-options.js +92 -0
- package/dist/cli/extension-command-options.js.map +1 -1
- package/dist/cli/help-content.d.ts +20 -0
- package/dist/cli/help-content.js +543 -0
- package/dist/cli/help-content.js.map +1 -0
- package/dist/cli/main.js +3625 -445
- package/dist/cli/main.js.map +1 -1
- package/dist/core/extensions/index.d.ts +13 -1
- package/dist/core/extensions/index.js +108 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/item-fields.d.ts +2 -0
- package/dist/core/extensions/item-fields.js +79 -0
- package/dist/core/extensions/item-fields.js.map +1 -0
- package/dist/core/extensions/loader.d.ts +322 -9
- package/dist/core/extensions/loader.js +911 -20
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runtime-registrations.d.ts +5 -0
- package/dist/core/extensions/runtime-registrations.js +51 -0
- package/dist/core/extensions/runtime-registrations.js.map +1 -0
- package/dist/core/history/history-stream-policy.d.ts +20 -0
- package/dist/core/history/history-stream-policy.js +53 -0
- package/dist/core/history/history-stream-policy.js.map +1 -0
- package/dist/core/history/history.js +90 -1
- package/dist/core/history/history.js.map +1 -1
- package/dist/core/item/id.js +4 -1
- package/dist/core/item/id.js.map +1 -1
- package/dist/core/item/index.d.ts +1 -0
- package/dist/core/item/index.js +1 -0
- package/dist/core/item/index.js.map +1 -1
- package/dist/core/item/item-format.d.ts +11 -5
- package/dist/core/item/item-format.js +507 -24
- package/dist/core/item/item-format.js.map +1 -1
- package/dist/core/item/parent-reference-policy.d.ts +6 -0
- package/dist/core/item/parent-reference-policy.js +32 -0
- package/dist/core/item/parent-reference-policy.js.map +1 -0
- package/dist/core/item/parse.d.ts +5 -0
- package/dist/core/item/parse.js +216 -19
- package/dist/core/item/parse.js.map +1 -1
- package/dist/core/item/sprint-release-format.d.ts +6 -0
- package/dist/core/item/sprint-release-format.js +33 -0
- package/dist/core/item/sprint-release-format.js.map +1 -0
- package/dist/core/item/status.d.ts +3 -0
- package/dist/core/item/status.js +24 -0
- package/dist/core/item/status.js.map +1 -0
- package/dist/core/item/type-registry.d.ts +37 -0
- package/dist/core/item/type-registry.js +706 -0
- package/dist/core/item/type-registry.js.map +1 -0
- package/dist/core/lock/lock.d.ts +1 -1
- package/dist/core/lock/lock.js +101 -12
- package/dist/core/lock/lock.js.map +1 -1
- package/dist/core/output/command-aware.d.ts +1 -0
- package/dist/core/output/command-aware.js +394 -0
- package/dist/core/output/command-aware.js.map +1 -0
- package/dist/core/output/output.d.ts +3 -0
- package/dist/core/output/output.js +124 -6
- package/dist/core/output/output.js.map +1 -1
- package/dist/core/schema/runtime-field-filters.d.ts +3 -0
- package/dist/core/schema/runtime-field-filters.js +39 -0
- package/dist/core/schema/runtime-field-filters.js.map +1 -0
- package/dist/core/schema/runtime-field-values.d.ts +8 -0
- package/dist/core/schema/runtime-field-values.js +154 -0
- package/dist/core/schema/runtime-field-values.js.map +1 -0
- package/dist/core/schema/runtime-schema.d.ts +68 -0
- package/dist/core/schema/runtime-schema.js +554 -0
- package/dist/core/schema/runtime-schema.js.map +1 -0
- package/dist/core/search/cache.d.ts +13 -1
- package/dist/core/search/cache.js +123 -14
- package/dist/core/search/cache.js.map +1 -1
- package/dist/core/search/semantic-defaults.d.ts +6 -0
- package/dist/core/search/semantic-defaults.js +120 -0
- package/dist/core/search/semantic-defaults.js.map +1 -0
- package/dist/core/search/vector-stores.js +3 -1
- package/dist/core/search/vector-stores.js.map +1 -1
- package/dist/core/shared/command-types.d.ts +2 -0
- package/dist/core/shared/conflict-markers.d.ts +7 -0
- package/dist/core/shared/conflict-markers.js +27 -0
- package/dist/core/shared/conflict-markers.js.map +1 -0
- package/dist/core/shared/constants.d.ts +15 -4
- package/dist/core/shared/constants.js +141 -1
- package/dist/core/shared/constants.js.map +1 -1
- package/dist/core/shared/errors.d.ts +10 -1
- package/dist/core/shared/errors.js +3 -1
- package/dist/core/shared/errors.js.map +1 -1
- package/dist/core/shared/text-normalization.d.ts +4 -0
- package/dist/core/shared/text-normalization.js +33 -0
- package/dist/core/shared/text-normalization.js.map +1 -0
- package/dist/core/shared/time.d.ts +1 -2
- package/dist/core/shared/time.js +98 -11
- package/dist/core/shared/time.js.map +1 -1
- package/dist/core/store/index.d.ts +1 -0
- package/dist/core/store/index.js +1 -0
- package/dist/core/store/index.js.map +1 -1
- package/dist/core/store/item-format-migration.d.ts +9 -0
- package/dist/core/store/item-format-migration.js +87 -0
- package/dist/core/store/item-format-migration.js.map +1 -0
- package/dist/core/store/item-store.d.ts +13 -4
- package/dist/core/store/item-store.js +238 -51
- package/dist/core/store/item-store.js.map +1 -1
- package/dist/core/store/paths.d.ts +21 -3
- package/dist/core/store/paths.js +59 -4
- package/dist/core/store/paths.js.map +1 -1
- package/dist/core/store/settings.d.ts +14 -1
- package/dist/core/store/settings.js +463 -7
- package/dist/core/store/settings.js.map +1 -1
- package/dist/core/telemetry/consent.d.ts +2 -0
- package/dist/core/telemetry/consent.js +79 -0
- package/dist/core/telemetry/consent.js.map +1 -0
- package/dist/core/telemetry/runtime.d.ts +38 -0
- package/dist/core/telemetry/runtime.js +733 -0
- package/dist/core/telemetry/runtime.js.map +1 -0
- package/dist/core/test/background-runs.d.ts +117 -0
- package/dist/core/test/background-runs.js +760 -0
- package/dist/core/test/background-runs.js.map +1 -0
- package/dist/core/test/item-test-run-tracking.d.ts +9 -0
- package/dist/core/test/item-test-run-tracking.js +50 -0
- package/dist/core/test/item-test-run-tracking.js.map +1 -0
- package/dist/sdk/cli-contracts.d.ts +92 -0
- package/dist/sdk/cli-contracts.js +2357 -0
- package/dist/sdk/cli-contracts.js.map +1 -0
- package/dist/sdk/index.d.ts +34 -0
- package/dist/sdk/index.js +23 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/types.d.ts +197 -3
- package/dist/types.js +48 -1
- package/dist/types.js.map +1 -1
- package/docs/ARCHITECTURE.md +368 -39
- package/docs/EXTENSIONS.md +454 -49
- package/docs/RELEASING.md +68 -19
- package/docs/SDK.md +123 -0
- package/docs/examples/starter-extension/README.md +48 -0
- package/docs/examples/starter-extension/index.js +191 -0
- package/docs/examples/starter-extension/manifest.json +17 -0
- package/docs/examples/starter-extension/package.json +10 -0
- package/package.json +33 -6
- package/.pi/extensions/pm-cli/index.ts +0 -778
- package/dist/cli/commands/beads.d.ts +0 -16
- package/dist/cli/commands/beads.js.map +0 -1
- package/dist/cli/commands/install.d.ts +0 -18
- package/dist/cli/commands/install.js +0 -87
- package/dist/cli/commands/install.js.map +0 -1
- package/dist/core/extensions/builtins.d.ts +0 -3
- package/dist/core/extensions/builtins.js +0 -47
- package/dist/core/extensions/builtins.js.map +0 -1
- package/dist/extensions/builtins/beads/index.d.ts +0 -8
- package/dist/extensions/builtins/beads/index.js +0 -33
- package/dist/extensions/builtins/beads/index.js.map +0 -1
- package/dist/extensions/builtins/todos/import-export.d.ts +0 -26
- package/dist/extensions/builtins/todos/import-export.js.map +0 -1
- package/dist/extensions/builtins/todos/index.d.ts +0 -8
- package/dist/extensions/builtins/todos/index.js +0 -38
- package/dist/extensions/builtins/todos/index.js.map +0 -1
|
@@ -4,7 +4,27 @@ import { pathToFileURL } from "node:url";
|
|
|
4
4
|
import { pathExists } from "../fs/fs-utils.js";
|
|
5
5
|
import { resolveGlobalPmRoot } from "../store/paths.js";
|
|
6
6
|
const DEFAULT_EXTENSION_PRIORITY = 100;
|
|
7
|
-
const KNOWN_EXTENSION_CAPABILITIES = [
|
|
7
|
+
export const KNOWN_EXTENSION_CAPABILITIES = [
|
|
8
|
+
"commands",
|
|
9
|
+
"renderers",
|
|
10
|
+
"hooks",
|
|
11
|
+
"schema",
|
|
12
|
+
"importers",
|
|
13
|
+
"search",
|
|
14
|
+
"parser",
|
|
15
|
+
"preflight",
|
|
16
|
+
"services",
|
|
17
|
+
];
|
|
18
|
+
export const EXTENSION_CAPABILITY_CONTRACT_VERSION = 2;
|
|
19
|
+
export const EXTENSION_CAPABILITY_LEGACY_ALIASES = Object.freeze({
|
|
20
|
+
migration: "schema",
|
|
21
|
+
validation: "schema",
|
|
22
|
+
});
|
|
23
|
+
export const EXTENSION_CAPABILITY_CONTRACT = Object.freeze({
|
|
24
|
+
version: EXTENSION_CAPABILITY_CONTRACT_VERSION,
|
|
25
|
+
capabilities: [...KNOWN_EXTENSION_CAPABILITIES],
|
|
26
|
+
legacy_aliases: { ...EXTENSION_CAPABILITY_LEGACY_ALIASES },
|
|
27
|
+
});
|
|
8
28
|
function normalizeNames(values) {
|
|
9
29
|
return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))].sort((a, b) => a.localeCompare(b));
|
|
10
30
|
}
|
|
@@ -14,6 +34,151 @@ function isKnownExtensionCapability(value) {
|
|
|
14
34
|
function collectUnknownExtensionCapabilities(capabilities) {
|
|
15
35
|
return capabilities.filter((capability) => !isKnownExtensionCapability(capability));
|
|
16
36
|
}
|
|
37
|
+
function resolveLegacyExtensionCapabilityAlias(capability) {
|
|
38
|
+
const normalized = capability.trim().toLowerCase();
|
|
39
|
+
if (normalized.length === 0) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return EXTENSION_CAPABILITY_LEGACY_ALIASES[normalized] ?? null;
|
|
43
|
+
}
|
|
44
|
+
function normalizeManifestCapabilities(rawCapabilities) {
|
|
45
|
+
const normalizedCapabilities = normalizeNames([...rawCapabilities].map((value) => value.toLowerCase()));
|
|
46
|
+
const remappedCapabilities = [];
|
|
47
|
+
const legacyAliases = [];
|
|
48
|
+
for (const capability of normalizedCapabilities) {
|
|
49
|
+
const legacyAliasTarget = resolveLegacyExtensionCapabilityAlias(capability);
|
|
50
|
+
if (legacyAliasTarget) {
|
|
51
|
+
remappedCapabilities.push(legacyAliasTarget);
|
|
52
|
+
legacyAliases.push({
|
|
53
|
+
alias: capability,
|
|
54
|
+
target: legacyAliasTarget,
|
|
55
|
+
});
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
remappedCapabilities.push(capability);
|
|
59
|
+
}
|
|
60
|
+
const dedupedLegacyAliases = [...new Map(legacyAliases.map((entry) => [`${entry.alias}>${entry.target}`, entry])).values()].sort((left, right) => left.alias.localeCompare(right.alias));
|
|
61
|
+
return {
|
|
62
|
+
capabilities: normalizeNames(remappedCapabilities),
|
|
63
|
+
legacy_aliases: dedupedLegacyAliases,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function levenshteinDistance(left, right) {
|
|
67
|
+
if (left === right) {
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
if (left.length === 0) {
|
|
71
|
+
return right.length;
|
|
72
|
+
}
|
|
73
|
+
if (right.length === 0) {
|
|
74
|
+
return left.length;
|
|
75
|
+
}
|
|
76
|
+
const previous = new Array(right.length + 1);
|
|
77
|
+
const current = new Array(right.length + 1);
|
|
78
|
+
for (let j = 0; j <= right.length; j += 1) {
|
|
79
|
+
previous[j] = j;
|
|
80
|
+
}
|
|
81
|
+
for (let i = 1; i <= left.length; i += 1) {
|
|
82
|
+
current[0] = i;
|
|
83
|
+
for (let j = 1; j <= right.length; j += 1) {
|
|
84
|
+
const substitutionCost = left[i - 1] === right[j - 1] ? 0 : 1;
|
|
85
|
+
current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + substitutionCost);
|
|
86
|
+
}
|
|
87
|
+
for (let j = 0; j <= right.length; j += 1) {
|
|
88
|
+
previous[j] = current[j];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return previous[right.length] ?? left.length;
|
|
92
|
+
}
|
|
93
|
+
function suggestKnownExtensionCapability(capability) {
|
|
94
|
+
const normalized = capability.trim().toLowerCase();
|
|
95
|
+
if (normalized.length === 0) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const legacyAlias = resolveLegacyExtensionCapabilityAlias(normalized);
|
|
99
|
+
if (legacyAlias) {
|
|
100
|
+
return legacyAlias;
|
|
101
|
+
}
|
|
102
|
+
let bestMatch = null;
|
|
103
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
104
|
+
for (const candidate of KNOWN_EXTENSION_CAPABILITIES) {
|
|
105
|
+
const distance = levenshteinDistance(normalized, candidate);
|
|
106
|
+
if (distance < bestDistance) {
|
|
107
|
+
bestDistance = distance;
|
|
108
|
+
bestMatch = candidate;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const maxDistance = Math.max(1, Math.floor(normalized.length * 0.34));
|
|
112
|
+
return bestMatch !== null && bestDistance <= maxDistance ? bestMatch : null;
|
|
113
|
+
}
|
|
114
|
+
function formatUnknownExtensionCapabilityWarning(layer, name, capability) {
|
|
115
|
+
const allowed = KNOWN_EXTENSION_CAPABILITIES.join(",");
|
|
116
|
+
const suggested = suggestKnownExtensionCapability(capability) ?? "none";
|
|
117
|
+
return `extension_capability_unknown:${layer}:${name}:${capability}:allowed=${allowed}:suggested=${suggested}`;
|
|
118
|
+
}
|
|
119
|
+
function formatLegacyExtensionCapabilityAliasWarning(layer, name, aliases) {
|
|
120
|
+
const aliasesToken = aliases.map((entry) => `${entry.alias}>${entry.target}`).join(",");
|
|
121
|
+
return `extension_capability_legacy_alias:${layer}:${name}:aliases=${aliasesToken}`;
|
|
122
|
+
}
|
|
123
|
+
export function parseUnknownExtensionCapabilityWarning(warning) {
|
|
124
|
+
const match = /^extension_capability_unknown:(global|project):([^:]+):([^:]+):allowed=([^:]+):suggested=([^:]+)$/.exec(warning.trim());
|
|
125
|
+
if (!match) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const [, layerRaw, name, capability, allowedRaw, suggestedRaw] = match;
|
|
129
|
+
const layer = layerRaw;
|
|
130
|
+
const allowed_capabilities = allowedRaw
|
|
131
|
+
.split(",")
|
|
132
|
+
.map((value) => value.trim())
|
|
133
|
+
.filter((value) => value.length > 0);
|
|
134
|
+
const legacyAlias = resolveLegacyExtensionCapabilityAlias(capability);
|
|
135
|
+
const suggestedFromWarning = suggestedRaw === "none" ? undefined : suggestedRaw;
|
|
136
|
+
const suggested_capability = suggestedFromWarning ?? legacyAlias ?? undefined;
|
|
137
|
+
const suggestion_source = suggested_capability
|
|
138
|
+
? legacyAlias === suggested_capability
|
|
139
|
+
? "legacy_alias"
|
|
140
|
+
: "nearest_match"
|
|
141
|
+
: undefined;
|
|
142
|
+
return {
|
|
143
|
+
layer,
|
|
144
|
+
name,
|
|
145
|
+
capability,
|
|
146
|
+
allowed_capabilities,
|
|
147
|
+
capability_contract_version: EXTENSION_CAPABILITY_CONTRACT_VERSION,
|
|
148
|
+
suggested_capability,
|
|
149
|
+
suggestion_source,
|
|
150
|
+
legacy_alias_target: legacyAlias ?? undefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export function parseLegacyExtensionCapabilityAliasWarning(warning) {
|
|
154
|
+
const match = /^extension_capability_legacy_alias:(global|project):([^:]+):aliases=(.+)$/.exec(warning.trim());
|
|
155
|
+
if (!match) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
const [, layerRaw, name, aliasesRaw] = match;
|
|
159
|
+
const layer = layerRaw;
|
|
160
|
+
const allowedCapabilities = [...KNOWN_EXTENSION_CAPABILITIES];
|
|
161
|
+
const parsed = [];
|
|
162
|
+
for (const token of aliasesRaw.split(",")) {
|
|
163
|
+
const [rawAlias, rawTarget] = token.split(">");
|
|
164
|
+
const alias = rawAlias?.trim();
|
|
165
|
+
const target = rawTarget?.trim().toLowerCase();
|
|
166
|
+
if (!alias || !target || !isKnownExtensionCapability(target)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
parsed.push({
|
|
170
|
+
layer,
|
|
171
|
+
name,
|
|
172
|
+
capability: alias,
|
|
173
|
+
allowed_capabilities: allowedCapabilities,
|
|
174
|
+
capability_contract_version: EXTENSION_CAPABILITY_CONTRACT_VERSION,
|
|
175
|
+
suggested_capability: target,
|
|
176
|
+
suggestion_source: "legacy_alias",
|
|
177
|
+
legacy_alias_target: target,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return parsed;
|
|
181
|
+
}
|
|
17
182
|
function parseManifest(raw) {
|
|
18
183
|
if (typeof raw !== "object" || raw === null) {
|
|
19
184
|
return null;
|
|
@@ -36,11 +201,14 @@ function parseManifest(raw) {
|
|
|
36
201
|
priority = candidate.priority;
|
|
37
202
|
}
|
|
38
203
|
let capabilities = [];
|
|
204
|
+
let legacyCapabilityAliases = [];
|
|
39
205
|
if ("capabilities" in candidate && candidate.capabilities !== undefined && candidate.capabilities !== null) {
|
|
40
206
|
if (!Array.isArray(candidate.capabilities) || candidate.capabilities.some((value) => typeof value !== "string")) {
|
|
41
207
|
return null;
|
|
42
208
|
}
|
|
43
|
-
|
|
209
|
+
const normalizedCapabilities = normalizeManifestCapabilities(candidate.capabilities);
|
|
210
|
+
capabilities = normalizedCapabilities.capabilities;
|
|
211
|
+
legacyCapabilityAliases = normalizedCapabilities.legacy_aliases;
|
|
44
212
|
}
|
|
45
213
|
return {
|
|
46
214
|
name: candidate.name.trim(),
|
|
@@ -48,6 +216,7 @@ function parseManifest(raw) {
|
|
|
48
216
|
entry: candidate.entry.trim(),
|
|
49
217
|
priority,
|
|
50
218
|
capabilities,
|
|
219
|
+
legacy_capability_aliases: legacyCapabilityAliases.length > 0 ? legacyCapabilityAliases : undefined,
|
|
51
220
|
};
|
|
52
221
|
}
|
|
53
222
|
function asRecord(value) {
|
|
@@ -97,6 +266,21 @@ export function createEmptyExtensionCommandRegistry() {
|
|
|
97
266
|
handlers: [],
|
|
98
267
|
};
|
|
99
268
|
}
|
|
269
|
+
export function createEmptyExtensionParserRegistry() {
|
|
270
|
+
return {
|
|
271
|
+
overrides: [],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
export function createEmptyExtensionPreflightRegistry() {
|
|
275
|
+
return {
|
|
276
|
+
overrides: [],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
export function createEmptyExtensionServiceRegistry() {
|
|
280
|
+
return {
|
|
281
|
+
overrides: [],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
100
284
|
export function createEmptyExtensionRendererRegistry() {
|
|
101
285
|
return {
|
|
102
286
|
overrides: [],
|
|
@@ -104,8 +288,10 @@ export function createEmptyExtensionRendererRegistry() {
|
|
|
104
288
|
}
|
|
105
289
|
export function createEmptyExtensionRegistrationRegistry() {
|
|
106
290
|
return {
|
|
291
|
+
commands: [],
|
|
107
292
|
flags: [],
|
|
108
293
|
item_fields: [],
|
|
294
|
+
item_types: [],
|
|
109
295
|
migrations: [],
|
|
110
296
|
importers: [],
|
|
111
297
|
exporters: [],
|
|
@@ -232,8 +418,11 @@ async function scanExtensionDirectory(layer, extensionsRoot, directory, enabled,
|
|
|
232
418
|
: entryWithinDirectoryByPath;
|
|
233
419
|
const enabledForLoad = shouldEnable(manifest.name, enabled, disabled);
|
|
234
420
|
const extensionWarnings = [];
|
|
421
|
+
if (Array.isArray(manifest.legacy_capability_aliases) && manifest.legacy_capability_aliases.length > 0) {
|
|
422
|
+
extensionWarnings.push(formatLegacyExtensionCapabilityAliasWarning(layer, manifest.name, manifest.legacy_capability_aliases));
|
|
423
|
+
}
|
|
235
424
|
for (const capability of collectUnknownExtensionCapabilities(manifest.capabilities)) {
|
|
236
|
-
extensionWarnings.push(
|
|
425
|
+
extensionWarnings.push(formatUnknownExtensionCapabilityWarning(layer, manifest.name, capability));
|
|
237
426
|
}
|
|
238
427
|
if (!entryWithinDirectory) {
|
|
239
428
|
extensionWarnings.push(`extension_entry_outside_extension:${layer}:${manifest.name}`);
|
|
@@ -386,6 +575,19 @@ function cloneContextSnapshot(value) {
|
|
|
386
575
|
function isOutputRendererFormat(value) {
|
|
387
576
|
return value === "toon" || value === "json";
|
|
388
577
|
}
|
|
578
|
+
const EXTENSION_SERVICE_NAMES = [
|
|
579
|
+
"output_format",
|
|
580
|
+
"error_format",
|
|
581
|
+
"help_format",
|
|
582
|
+
"lock_acquire",
|
|
583
|
+
"lock_release",
|
|
584
|
+
"history_append",
|
|
585
|
+
"item_store_write",
|
|
586
|
+
"item_store_delete",
|
|
587
|
+
];
|
|
588
|
+
function isExtensionServiceName(value) {
|
|
589
|
+
return EXTENSION_SERVICE_NAMES.includes(value);
|
|
590
|
+
}
|
|
389
591
|
function assertHookHandler(hookName, hook) {
|
|
390
592
|
if (typeof hook !== "function") {
|
|
391
593
|
throw new TypeError(`api.hooks.${hookName} requires a function handler`);
|
|
@@ -440,18 +642,269 @@ function sanitizeRegistrationValue(value) {
|
|
|
440
642
|
}
|
|
441
643
|
return value;
|
|
442
644
|
}
|
|
645
|
+
function cloneRuntimeRegistrationValue(value) {
|
|
646
|
+
if (Array.isArray(value)) {
|
|
647
|
+
return value.map((entry) => cloneRuntimeRegistrationValue(entry));
|
|
648
|
+
}
|
|
649
|
+
if (typeof value === "object" && value !== null) {
|
|
650
|
+
const record = value;
|
|
651
|
+
const cloned = {};
|
|
652
|
+
for (const key of Object.keys(record).sort((left, right) => left.localeCompare(right))) {
|
|
653
|
+
cloned[key] = cloneRuntimeRegistrationValue(record[key]);
|
|
654
|
+
}
|
|
655
|
+
return cloned;
|
|
656
|
+
}
|
|
657
|
+
return value;
|
|
658
|
+
}
|
|
659
|
+
const EXTENSION_REGISTRATION_TRACE_SYMBOL = Symbol("extension_registration_trace");
|
|
660
|
+
function createRegistrationValidationError(message, trace) {
|
|
661
|
+
const error = new TypeError(message);
|
|
662
|
+
Object.defineProperty(error, EXTENSION_REGISTRATION_TRACE_SYMBOL, {
|
|
663
|
+
value: trace,
|
|
664
|
+
enumerable: false,
|
|
665
|
+
configurable: false,
|
|
666
|
+
writable: false,
|
|
667
|
+
});
|
|
668
|
+
return error;
|
|
669
|
+
}
|
|
670
|
+
function extractRegistrationValidationTrace(error) {
|
|
671
|
+
if (!(error instanceof Error)) {
|
|
672
|
+
return undefined;
|
|
673
|
+
}
|
|
674
|
+
return error[EXTENSION_REGISTRATION_TRACE_SYMBOL];
|
|
675
|
+
}
|
|
443
676
|
function normalizeRegistrationRecord(name, value) {
|
|
444
677
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
445
678
|
throw new TypeError(`${name} requires an object definition`);
|
|
446
679
|
}
|
|
447
680
|
return sanitizeRegistrationValue(value);
|
|
448
681
|
}
|
|
682
|
+
function normalizeRuntimeRegistrationRecord(name, value) {
|
|
683
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
684
|
+
throw new TypeError(`${name} requires an object definition`);
|
|
685
|
+
}
|
|
686
|
+
return cloneRuntimeRegistrationValue(value);
|
|
687
|
+
}
|
|
449
688
|
function normalizeRegistrationRecordList(name, value) {
|
|
450
689
|
if (!Array.isArray(value)) {
|
|
451
690
|
throw new TypeError(`${name} requires an array of object definitions`);
|
|
452
691
|
}
|
|
453
692
|
return value.map((entry) => normalizeRegistrationRecord(name, entry));
|
|
454
693
|
}
|
|
694
|
+
function asRegistrationRecord(name, value) {
|
|
695
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
696
|
+
throw new TypeError(`${name} requires an object definition`);
|
|
697
|
+
}
|
|
698
|
+
return value;
|
|
699
|
+
}
|
|
700
|
+
function assertOptionalBooleanField(name, value) {
|
|
701
|
+
if (value !== undefined && typeof value !== "boolean") {
|
|
702
|
+
throw new TypeError(`${name} must be a boolean when provided`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function assertOptionalStringField(name, value) {
|
|
706
|
+
if (value === undefined) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
710
|
+
throw new TypeError(`${name} must be a non-empty string when provided`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function assertOptionalStringArrayField(name, value) {
|
|
714
|
+
if (value === undefined) {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (!Array.isArray(value)) {
|
|
718
|
+
throw new TypeError(`${name} must be an array of non-empty strings when provided`);
|
|
719
|
+
}
|
|
720
|
+
for (const [index, entry] of value.entries()) {
|
|
721
|
+
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
722
|
+
throw new TypeError(`${name}[${index}] must be a non-empty string`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function normalizeOptionalStringArrayField(name, value) {
|
|
727
|
+
assertOptionalStringArrayField(name, value);
|
|
728
|
+
if (!Array.isArray(value)) {
|
|
729
|
+
return [];
|
|
730
|
+
}
|
|
731
|
+
const normalized = [];
|
|
732
|
+
const seen = new Set();
|
|
733
|
+
for (const entry of value) {
|
|
734
|
+
const trimmed = entry.trim();
|
|
735
|
+
if (trimmed.length === 0 || seen.has(trimmed)) {
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
seen.add(trimmed);
|
|
739
|
+
normalized.push(trimmed);
|
|
740
|
+
}
|
|
741
|
+
return normalized;
|
|
742
|
+
}
|
|
743
|
+
function normalizeCommandActionName(value) {
|
|
744
|
+
return value
|
|
745
|
+
.trim()
|
|
746
|
+
.toLowerCase()
|
|
747
|
+
.replace(/[\s_]+/g, "-")
|
|
748
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
749
|
+
.replace(/-+/g, "-")
|
|
750
|
+
.replace(/^-+|-+$/g, "");
|
|
751
|
+
}
|
|
752
|
+
function resolveCommandDefinitionAction(commandPath, action) {
|
|
753
|
+
if (action === undefined) {
|
|
754
|
+
return commandPath.replace(/\s+/g, "-");
|
|
755
|
+
}
|
|
756
|
+
if (typeof action !== "string" || action.trim().length === 0) {
|
|
757
|
+
throw new TypeError("registerCommand definition.action must be a non-empty string when provided");
|
|
758
|
+
}
|
|
759
|
+
const normalized = normalizeCommandActionName(action);
|
|
760
|
+
if (normalized.length === 0) {
|
|
761
|
+
throw new TypeError("registerCommand definition.action must contain alphanumeric characters");
|
|
762
|
+
}
|
|
763
|
+
return normalized;
|
|
764
|
+
}
|
|
765
|
+
function normalizeCommandDefinitionArguments(value) {
|
|
766
|
+
if (value === undefined) {
|
|
767
|
+
return [];
|
|
768
|
+
}
|
|
769
|
+
if (!Array.isArray(value)) {
|
|
770
|
+
throw new TypeError("registerCommand definition.arguments must be an array when provided");
|
|
771
|
+
}
|
|
772
|
+
const normalized = [];
|
|
773
|
+
for (const [index, entry] of value.entries()) {
|
|
774
|
+
const record = asRegistrationRecord(`registerCommand definition.arguments[${index}]`, entry);
|
|
775
|
+
const name = assertNonEmptyString(`registerCommand definition.arguments[${index}].name`, record.name);
|
|
776
|
+
assertOptionalBooleanField(`registerCommand definition.arguments[${index}].required`, record.required);
|
|
777
|
+
assertOptionalBooleanField(`registerCommand definition.arguments[${index}].variadic`, record.variadic);
|
|
778
|
+
assertOptionalStringField(`registerCommand definition.arguments[${index}].description`, record.description);
|
|
779
|
+
if (name.includes(" ")) {
|
|
780
|
+
throw new TypeError(`registerCommand definition.arguments[${index}].name must not contain spaces`);
|
|
781
|
+
}
|
|
782
|
+
const definition = {
|
|
783
|
+
name,
|
|
784
|
+
};
|
|
785
|
+
if (record.required === true) {
|
|
786
|
+
definition.required = true;
|
|
787
|
+
}
|
|
788
|
+
if (record.variadic === true) {
|
|
789
|
+
definition.variadic = true;
|
|
790
|
+
}
|
|
791
|
+
if (typeof record.description === "string") {
|
|
792
|
+
const trimmedDescription = record.description.trim();
|
|
793
|
+
if (trimmedDescription.length > 0) {
|
|
794
|
+
definition.description = trimmedDescription;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
normalized.push(definition);
|
|
798
|
+
}
|
|
799
|
+
let variadicCount = 0;
|
|
800
|
+
for (const [index, argument] of normalized.entries()) {
|
|
801
|
+
if (!argument.variadic) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
variadicCount += 1;
|
|
805
|
+
if (variadicCount > 1) {
|
|
806
|
+
throw new TypeError("registerCommand definition.arguments supports at most one variadic argument");
|
|
807
|
+
}
|
|
808
|
+
if (index !== normalized.length - 1) {
|
|
809
|
+
throw new TypeError("registerCommand definition.arguments variadic argument must be the final argument");
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return normalized;
|
|
813
|
+
}
|
|
814
|
+
function validateFlagDefinitions(flags) {
|
|
815
|
+
if (!Array.isArray(flags)) {
|
|
816
|
+
throw new TypeError("registerFlags flags requires an array of object definitions");
|
|
817
|
+
}
|
|
818
|
+
for (const [index, raw] of flags.entries()) {
|
|
819
|
+
const record = asRegistrationRecord(`registerFlags flags[${index}]`, raw);
|
|
820
|
+
const long = record.long;
|
|
821
|
+
const short = record.short;
|
|
822
|
+
if (long === undefined && short === undefined) {
|
|
823
|
+
throw new TypeError(`registerFlags flags[${index}] requires at least one of long or short`);
|
|
824
|
+
}
|
|
825
|
+
assertOptionalStringField(`registerFlags flags[${index}].long`, long);
|
|
826
|
+
assertOptionalStringField(`registerFlags flags[${index}].short`, short);
|
|
827
|
+
assertOptionalStringField(`registerFlags flags[${index}].value_name`, record.value_name);
|
|
828
|
+
assertOptionalStringField(`registerFlags flags[${index}].description`, record.description);
|
|
829
|
+
assertOptionalBooleanField(`registerFlags flags[${index}].required`, record.required);
|
|
830
|
+
assertOptionalBooleanField(`registerFlags flags[${index}].enabled`, record.enabled);
|
|
831
|
+
assertOptionalBooleanField(`registerFlags flags[${index}].visible`, record.visible);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
function validateItemFieldDefinitions(fields) {
|
|
835
|
+
if (!Array.isArray(fields)) {
|
|
836
|
+
throw new TypeError("registerItemFields fields requires an array of object definitions");
|
|
837
|
+
}
|
|
838
|
+
for (const [index, raw] of fields.entries()) {
|
|
839
|
+
const record = asRegistrationRecord(`registerItemFields fields[${index}]`, raw);
|
|
840
|
+
assertNonEmptyString(`registerItemFields fields[${index}].name`, record.name);
|
|
841
|
+
assertNonEmptyString(`registerItemFields fields[${index}].type`, record.type);
|
|
842
|
+
assertOptionalBooleanField(`registerItemFields fields[${index}].optional`, record.optional);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function validateItemTypeDefinitions(types) {
|
|
846
|
+
if (!Array.isArray(types)) {
|
|
847
|
+
throw new TypeError("registerItemTypes types requires an array of object definitions");
|
|
848
|
+
}
|
|
849
|
+
for (const [typeIndex, raw] of types.entries()) {
|
|
850
|
+
const record = asRegistrationRecord(`registerItemTypes types[${typeIndex}]`, raw);
|
|
851
|
+
assertNonEmptyString(`registerItemTypes types[${typeIndex}].name`, record.name);
|
|
852
|
+
assertOptionalStringField(`registerItemTypes types[${typeIndex}].folder`, record.folder);
|
|
853
|
+
assertOptionalStringArrayField(`registerItemTypes types[${typeIndex}].aliases`, record.aliases);
|
|
854
|
+
assertOptionalStringArrayField(`registerItemTypes types[${typeIndex}].required_create_fields`, record.required_create_fields);
|
|
855
|
+
assertOptionalStringArrayField(`registerItemTypes types[${typeIndex}].required_create_repeatables`, record.required_create_repeatables);
|
|
856
|
+
if (record.command_option_policies !== undefined) {
|
|
857
|
+
if (!Array.isArray(record.command_option_policies)) {
|
|
858
|
+
throw new TypeError(`registerItemTypes types[${typeIndex}].command_option_policies must be an array when provided`);
|
|
859
|
+
}
|
|
860
|
+
for (const [policyIndex, rawPolicy] of record.command_option_policies.entries()) {
|
|
861
|
+
const policy = asRegistrationRecord(`registerItemTypes types[${typeIndex}].command_option_policies[${policyIndex}]`, rawPolicy);
|
|
862
|
+
assertNonEmptyString(`registerItemTypes types[${typeIndex}].command_option_policies[${policyIndex}].command`, policy.command);
|
|
863
|
+
assertNonEmptyString(`registerItemTypes types[${typeIndex}].command_option_policies[${policyIndex}].option`, policy.option);
|
|
864
|
+
assertOptionalBooleanField(`registerItemTypes types[${typeIndex}].command_option_policies[${policyIndex}].enabled`, policy.enabled);
|
|
865
|
+
assertOptionalBooleanField(`registerItemTypes types[${typeIndex}].command_option_policies[${policyIndex}].required`, policy.required);
|
|
866
|
+
assertOptionalBooleanField(`registerItemTypes types[${typeIndex}].command_option_policies[${policyIndex}].visible`, policy.visible);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (record.options !== undefined) {
|
|
870
|
+
if (!Array.isArray(record.options)) {
|
|
871
|
+
throw new TypeError(`registerItemTypes types[${typeIndex}].options must be an array when provided`);
|
|
872
|
+
}
|
|
873
|
+
for (const [optionIndex, rawOption] of record.options.entries()) {
|
|
874
|
+
const option = asRegistrationRecord(`registerItemTypes types[${typeIndex}].options[${optionIndex}]`, rawOption);
|
|
875
|
+
assertNonEmptyString(`registerItemTypes types[${typeIndex}].options[${optionIndex}].key`, option.key);
|
|
876
|
+
assertOptionalStringArrayField(`registerItemTypes types[${typeIndex}].options[${optionIndex}].values`, option.values);
|
|
877
|
+
assertOptionalBooleanField(`registerItemTypes types[${typeIndex}].options[${optionIndex}].required`, option.required);
|
|
878
|
+
assertOptionalStringArrayField(`registerItemTypes types[${typeIndex}].options[${optionIndex}].aliases`, option.aliases);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function validateMigrationDefinition(definition) {
|
|
884
|
+
const record = asRegistrationRecord("registerMigration definition", definition);
|
|
885
|
+
if (record.id !== undefined && typeof record.id !== "string") {
|
|
886
|
+
throw new TypeError("registerMigration definition.id must be a string when provided");
|
|
887
|
+
}
|
|
888
|
+
if (record.description !== undefined && typeof record.description !== "string") {
|
|
889
|
+
throw new TypeError("registerMigration definition.description must be a string when provided");
|
|
890
|
+
}
|
|
891
|
+
if (record.status !== undefined && typeof record.status !== "string") {
|
|
892
|
+
throw new TypeError("registerMigration definition.status must be a string when provided");
|
|
893
|
+
}
|
|
894
|
+
assertOptionalBooleanField("registerMigration definition.mandatory", record.mandatory);
|
|
895
|
+
if (record.run !== undefined && typeof record.run !== "function") {
|
|
896
|
+
throw new TypeError("registerMigration definition.run must be a function when provided");
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
function attachRuntimeDefinition(entry, runtimeDefinition) {
|
|
900
|
+
Object.defineProperty(entry, "runtime_definition", {
|
|
901
|
+
value: runtimeDefinition,
|
|
902
|
+
enumerable: false,
|
|
903
|
+
writable: false,
|
|
904
|
+
configurable: false,
|
|
905
|
+
});
|
|
906
|
+
return entry;
|
|
907
|
+
}
|
|
455
908
|
function getDeclaredExtensionCapabilities(extension) {
|
|
456
909
|
if (!Array.isArray(extension.capabilities)) {
|
|
457
910
|
return null;
|
|
@@ -477,16 +930,25 @@ function assertExtensionCapability(extension, capability, method) {
|
|
|
477
930
|
throw new TypeError(`${method} requires capability '${capability}' in extension manifest capabilities`);
|
|
478
931
|
}
|
|
479
932
|
}
|
|
480
|
-
function createExtensionApi(extension, hooks, commands, renderers, registrations) {
|
|
933
|
+
function createExtensionApi(extension, hooks, commands, parsers, preflight, services, renderers, registrations, activationWarnings) {
|
|
934
|
+
const registerCommandTrace = (mode, command, expectedSchema, received, hint) => ({
|
|
935
|
+
method: "registerCommand",
|
|
936
|
+
registration_index: mode === "override" ? commands.overrides.length : commands.handlers.length,
|
|
937
|
+
command,
|
|
938
|
+
expected_schema: expectedSchema,
|
|
939
|
+
received: sanitizeRegistrationValue(received),
|
|
940
|
+
hint,
|
|
941
|
+
});
|
|
481
942
|
const registerCommand = (commandOrDefinition, override) => {
|
|
482
943
|
assertExtensionCapability(extension, "commands", "registerCommand");
|
|
483
944
|
if (typeof commandOrDefinition === "string") {
|
|
484
945
|
const normalizedCommand = normalizeCommandName(commandOrDefinition);
|
|
485
946
|
if (normalizedCommand.length === 0) {
|
|
486
|
-
throw
|
|
947
|
+
throw createRegistrationValidationError("registerCommand requires a non-empty command name", registerCommandTrace("override", commandOrDefinition, 'registerCommand("<command>", (context) => unknown)', commandOrDefinition, "Provide a non-empty command path as the first argument."));
|
|
487
948
|
}
|
|
488
949
|
if (typeof override !== "function") {
|
|
489
|
-
|
|
950
|
+
const trace = registerCommandTrace("override", normalizedCommand, 'registerCommand("<command>", (context) => unknown)', { command: commandOrDefinition, override }, "Provide a function as the second registerCommand argument.");
|
|
951
|
+
throw createRegistrationValidationError(`registerCommand requires an override function when command name is provided (command="${normalizedCommand}", registration_index=${trace.registration_index})`, trace);
|
|
490
952
|
}
|
|
491
953
|
commands.overrides.push({
|
|
492
954
|
layer: extension.layer,
|
|
@@ -497,23 +959,106 @@ function createExtensionApi(extension, hooks, commands, renderers, registrations
|
|
|
497
959
|
return;
|
|
498
960
|
}
|
|
499
961
|
if (typeof commandOrDefinition !== "object" || commandOrDefinition === null) {
|
|
500
|
-
throw
|
|
962
|
+
throw createRegistrationValidationError("registerCommand requires a command definition object", registerCommandTrace("definition", undefined, "{ name: string; run: (context) => unknown; }", commandOrDefinition, "Use registerCommand({ name: \"command path\", run: (context) => ... })."));
|
|
501
963
|
}
|
|
502
964
|
if (typeof commandOrDefinition.name !== "string") {
|
|
503
|
-
throw
|
|
965
|
+
throw createRegistrationValidationError("registerCommand requires a command definition name", registerCommandTrace("definition", undefined, "{ name: string; run: (context) => unknown; }", commandOrDefinition, "Set command definition.name to a non-empty string command path."));
|
|
504
966
|
}
|
|
505
967
|
const normalizedCommand = normalizeCommandName(commandOrDefinition.name);
|
|
506
968
|
if (normalizedCommand.length === 0) {
|
|
507
|
-
throw
|
|
969
|
+
throw createRegistrationValidationError("registerCommand requires a non-empty command definition name", registerCommandTrace("definition", commandOrDefinition.name, "{ name: string; run: (context) => unknown; }", commandOrDefinition, "Ensure command definition.name contains a non-empty command path."));
|
|
508
970
|
}
|
|
509
|
-
|
|
510
|
-
|
|
971
|
+
const runHandler = typeof commandOrDefinition.run === "function" ? commandOrDefinition.run : undefined;
|
|
972
|
+
const legacyHandler = typeof commandOrDefinition.handler === "function" ? commandOrDefinition.handler : undefined;
|
|
973
|
+
if (!runHandler && legacyHandler) {
|
|
974
|
+
activationWarnings.push(`extension_command_definition_legacy_handler_alias:${extension.layer}:${extension.name}:${normalizedCommand}`);
|
|
975
|
+
}
|
|
976
|
+
const resolvedHandler = runHandler ?? legacyHandler;
|
|
977
|
+
if (typeof resolvedHandler !== "function") {
|
|
978
|
+
const trace = registerCommandTrace("definition", normalizedCommand, "{ name: string; run: (context) => unknown; }", commandOrDefinition, "Define command definition.run as a function.");
|
|
979
|
+
throw createRegistrationValidationError(`registerCommand requires a command definition run handler (command="${normalizedCommand}", registration_index=${trace.registration_index})`, trace);
|
|
980
|
+
}
|
|
981
|
+
try {
|
|
982
|
+
assertOptionalStringField("registerCommand definition.action", commandOrDefinition.action);
|
|
983
|
+
assertOptionalStringField("registerCommand definition.description", commandOrDefinition.description);
|
|
984
|
+
assertOptionalStringField("registerCommand definition.intent", commandOrDefinition.intent);
|
|
985
|
+
const action = resolveCommandDefinitionAction(normalizedCommand, commandOrDefinition.action);
|
|
986
|
+
const description = commandOrDefinition.description?.trim();
|
|
987
|
+
const intent = commandOrDefinition.intent?.trim();
|
|
988
|
+
const examples = normalizeOptionalStringArrayField("registerCommand definition.examples", commandOrDefinition.examples);
|
|
989
|
+
const failureHints = normalizeOptionalStringArrayField("registerCommand definition.failure_hints", commandOrDefinition.failure_hints);
|
|
990
|
+
const argumentsDefinition = normalizeCommandDefinitionArguments(commandOrDefinition.arguments);
|
|
991
|
+
if (commandOrDefinition.flags !== undefined) {
|
|
992
|
+
assertExtensionCapability(extension, "schema", "registerCommand flags");
|
|
993
|
+
validateFlagDefinitions(commandOrDefinition.flags);
|
|
994
|
+
registrations.flags.push({
|
|
995
|
+
layer: extension.layer,
|
|
996
|
+
name: extension.name,
|
|
997
|
+
target_command: normalizedCommand,
|
|
998
|
+
flags: normalizeRegistrationRecordList("registerCommand definition.flags", commandOrDefinition.flags),
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
const registration = {
|
|
1002
|
+
layer: extension.layer,
|
|
1003
|
+
name: extension.name,
|
|
1004
|
+
command: normalizedCommand,
|
|
1005
|
+
action,
|
|
1006
|
+
examples,
|
|
1007
|
+
failure_hints: failureHints,
|
|
1008
|
+
arguments: argumentsDefinition,
|
|
1009
|
+
};
|
|
1010
|
+
if (description) {
|
|
1011
|
+
registration.description = description;
|
|
1012
|
+
}
|
|
1013
|
+
if (intent) {
|
|
1014
|
+
registration.intent = intent;
|
|
1015
|
+
}
|
|
1016
|
+
registrations.commands.push(registration);
|
|
1017
|
+
}
|
|
1018
|
+
catch (error) {
|
|
1019
|
+
const reason = error instanceof Error ? error.message : "registerCommand definition validation failed";
|
|
1020
|
+
const trace = registerCommandTrace("definition", normalizedCommand, "{ name: string; run: (context) => unknown; action?: string; arguments?: object[]; flags?: object[]; }", commandOrDefinition, "Use schema-style metadata (action/arguments/flags/examples/intent) with valid values.");
|
|
1021
|
+
throw createRegistrationValidationError(`registerCommand definition metadata invalid (command="${normalizedCommand}", registration_index=${trace.registration_index}): ${reason}`, trace);
|
|
511
1022
|
}
|
|
512
1023
|
commands.handlers.push({
|
|
513
1024
|
layer: extension.layer,
|
|
514
1025
|
name: extension.name,
|
|
515
1026
|
command: normalizedCommand,
|
|
516
|
-
run:
|
|
1027
|
+
run: resolvedHandler,
|
|
1028
|
+
});
|
|
1029
|
+
};
|
|
1030
|
+
const registerParser = (command, override) => {
|
|
1031
|
+
assertExtensionCapability(extension, "parser", "registerParser");
|
|
1032
|
+
const normalizedCommand = normalizeCommandName(assertNonEmptyString("registerParser command", command));
|
|
1033
|
+
assertFunctionHandler("registerParser override", override);
|
|
1034
|
+
parsers.overrides.push({
|
|
1035
|
+
layer: extension.layer,
|
|
1036
|
+
name: extension.name,
|
|
1037
|
+
command: normalizedCommand,
|
|
1038
|
+
run: override,
|
|
1039
|
+
});
|
|
1040
|
+
};
|
|
1041
|
+
const registerPreflight = (override) => {
|
|
1042
|
+
assertExtensionCapability(extension, "preflight", "registerPreflight");
|
|
1043
|
+
assertFunctionHandler("registerPreflight override", override);
|
|
1044
|
+
preflight.overrides.push({
|
|
1045
|
+
layer: extension.layer,
|
|
1046
|
+
name: extension.name,
|
|
1047
|
+
run: override,
|
|
1048
|
+
});
|
|
1049
|
+
};
|
|
1050
|
+
const registerService = (service, override) => {
|
|
1051
|
+
assertExtensionCapability(extension, "services", "registerService");
|
|
1052
|
+
const normalizedService = String(service).trim().toLowerCase();
|
|
1053
|
+
if (!isExtensionServiceName(normalizedService)) {
|
|
1054
|
+
throw new TypeError(`registerService service must be one of: ${EXTENSION_SERVICE_NAMES.join(", ")}`);
|
|
1055
|
+
}
|
|
1056
|
+
assertFunctionHandler("registerService override", override);
|
|
1057
|
+
services.overrides.push({
|
|
1058
|
+
layer: extension.layer,
|
|
1059
|
+
name: extension.name,
|
|
1060
|
+
service: normalizedService,
|
|
1061
|
+
run: override,
|
|
517
1062
|
});
|
|
518
1063
|
};
|
|
519
1064
|
const registerRenderer = (format, renderer) => {
|
|
@@ -535,6 +1080,7 @@ function createExtensionApi(extension, hooks, commands, renderers, registrations
|
|
|
535
1080
|
const registerFlags = (targetCommand, flags) => {
|
|
536
1081
|
assertExtensionCapability(extension, "schema", "registerFlags");
|
|
537
1082
|
const normalizedTargetCommand = normalizeCommandName(assertNonEmptyString("registerFlags targetCommand", targetCommand));
|
|
1083
|
+
validateFlagDefinitions(flags);
|
|
538
1084
|
const normalizedFlags = normalizeRegistrationRecordList("registerFlags flags", flags);
|
|
539
1085
|
if (normalizedFlags.length === 0) {
|
|
540
1086
|
throw new TypeError("registerFlags requires at least one flag definition");
|
|
@@ -548,6 +1094,7 @@ function createExtensionApi(extension, hooks, commands, renderers, registrations
|
|
|
548
1094
|
};
|
|
549
1095
|
const registerItemFields = (fields) => {
|
|
550
1096
|
assertExtensionCapability(extension, "schema", "registerItemFields");
|
|
1097
|
+
validateItemFieldDefinitions(fields);
|
|
551
1098
|
const normalizedFields = normalizeRegistrationRecordList("registerItemFields fields", fields);
|
|
552
1099
|
if (normalizedFields.length === 0) {
|
|
553
1100
|
throw new TypeError("registerItemFields requires at least one field definition");
|
|
@@ -558,13 +1105,28 @@ function createExtensionApi(extension, hooks, commands, renderers, registrations
|
|
|
558
1105
|
fields: normalizedFields,
|
|
559
1106
|
});
|
|
560
1107
|
};
|
|
1108
|
+
const registerItemTypes = (types) => {
|
|
1109
|
+
assertExtensionCapability(extension, "schema", "registerItemTypes");
|
|
1110
|
+
validateItemTypeDefinitions(types);
|
|
1111
|
+
const normalizedTypes = normalizeRegistrationRecordList("registerItemTypes types", types);
|
|
1112
|
+
if (normalizedTypes.length === 0) {
|
|
1113
|
+
throw new TypeError("registerItemTypes requires at least one type definition");
|
|
1114
|
+
}
|
|
1115
|
+
registrations.item_types.push({
|
|
1116
|
+
layer: extension.layer,
|
|
1117
|
+
name: extension.name,
|
|
1118
|
+
types: normalizedTypes,
|
|
1119
|
+
});
|
|
1120
|
+
};
|
|
561
1121
|
const registerMigration = (definition) => {
|
|
562
1122
|
assertExtensionCapability(extension, "schema", "registerMigration");
|
|
563
|
-
|
|
1123
|
+
validateMigrationDefinition(definition);
|
|
1124
|
+
const runtimeDefinition = normalizeRuntimeRegistrationRecord("registerMigration definition", definition);
|
|
1125
|
+
registrations.migrations.push(attachRuntimeDefinition({
|
|
564
1126
|
layer: extension.layer,
|
|
565
1127
|
name: extension.name,
|
|
566
1128
|
definition: normalizeRegistrationRecord("registerMigration definition", definition),
|
|
567
|
-
});
|
|
1129
|
+
}, runtimeDefinition));
|
|
568
1130
|
};
|
|
569
1131
|
const registerImporter = (name, importer) => {
|
|
570
1132
|
assertExtensionCapability(extension, "importers", "registerImporter");
|
|
@@ -618,19 +1180,21 @@ function createExtensionApi(extension, hooks, commands, renderers, registrations
|
|
|
618
1180
|
};
|
|
619
1181
|
const registerSearchProvider = (provider) => {
|
|
620
1182
|
assertExtensionCapability(extension, "search", "registerSearchProvider");
|
|
621
|
-
|
|
1183
|
+
const runtimeDefinition = normalizeRuntimeRegistrationRecord("registerSearchProvider provider", provider);
|
|
1184
|
+
registrations.search_providers.push(attachRuntimeDefinition({
|
|
622
1185
|
layer: extension.layer,
|
|
623
1186
|
name: extension.name,
|
|
624
1187
|
definition: normalizeRegistrationRecord("registerSearchProvider provider", provider),
|
|
625
|
-
});
|
|
1188
|
+
}, runtimeDefinition));
|
|
626
1189
|
};
|
|
627
1190
|
const registerVectorStoreAdapter = (adapter) => {
|
|
628
1191
|
assertExtensionCapability(extension, "search", "registerVectorStoreAdapter");
|
|
629
|
-
|
|
1192
|
+
const runtimeDefinition = normalizeRuntimeRegistrationRecord("registerVectorStoreAdapter adapter", adapter);
|
|
1193
|
+
registrations.vector_store_adapters.push(attachRuntimeDefinition({
|
|
630
1194
|
layer: extension.layer,
|
|
631
1195
|
name: extension.name,
|
|
632
1196
|
definition: normalizeRegistrationRecord("registerVectorStoreAdapter adapter", adapter),
|
|
633
|
-
});
|
|
1197
|
+
}, runtimeDefinition));
|
|
634
1198
|
};
|
|
635
1199
|
const registerBeforeCommand = (hook) => {
|
|
636
1200
|
assertExtensionCapability(extension, "hooks", "api.hooks.beforeCommand");
|
|
@@ -679,8 +1243,12 @@ function createExtensionApi(extension, hooks, commands, renderers, registrations
|
|
|
679
1243
|
};
|
|
680
1244
|
return {
|
|
681
1245
|
registerCommand,
|
|
1246
|
+
registerParser,
|
|
1247
|
+
registerPreflight,
|
|
1248
|
+
registerService,
|
|
682
1249
|
registerFlags,
|
|
683
1250
|
registerItemFields,
|
|
1251
|
+
registerItemTypes,
|
|
684
1252
|
registerMigration,
|
|
685
1253
|
registerRenderer,
|
|
686
1254
|
registerImporter,
|
|
@@ -709,11 +1277,15 @@ async function executeRegisteredHooks(entries, hookName, context) {
|
|
|
709
1277
|
return warnings;
|
|
710
1278
|
}
|
|
711
1279
|
function getRegistrationCounts(registrations) {
|
|
1280
|
+
const commandCount = registrations.commands.length;
|
|
712
1281
|
const flagCount = registrations.flags.reduce((total, entry) => total + entry.flags.length, 0);
|
|
713
1282
|
const itemFieldCount = registrations.item_fields.reduce((total, entry) => total + entry.fields.length, 0);
|
|
1283
|
+
const itemTypeCount = registrations.item_types.reduce((total, entry) => total + entry.types.length, 0);
|
|
714
1284
|
return {
|
|
1285
|
+
commands: commandCount,
|
|
715
1286
|
flags: flagCount,
|
|
716
1287
|
item_fields: itemFieldCount,
|
|
1288
|
+
item_types: itemTypeCount,
|
|
717
1289
|
migrations: registrations.migrations.length,
|
|
718
1290
|
importers: registrations.importers.length,
|
|
719
1291
|
exporters: registrations.exporters.length,
|
|
@@ -721,9 +1293,110 @@ function getRegistrationCounts(registrations) {
|
|
|
721
1293
|
vector_store_adapters: registrations.vector_store_adapters.length,
|
|
722
1294
|
};
|
|
723
1295
|
}
|
|
1296
|
+
function collectCommandCollisionWarnings(commands) {
|
|
1297
|
+
const warnings = [];
|
|
1298
|
+
const collectByCommand = (entries, codePrefix) => {
|
|
1299
|
+
const grouped = new Map();
|
|
1300
|
+
for (const entry of entries) {
|
|
1301
|
+
const bucket = grouped.get(entry.command) ?? [];
|
|
1302
|
+
bucket.push(entry);
|
|
1303
|
+
grouped.set(entry.command, bucket);
|
|
1304
|
+
}
|
|
1305
|
+
for (const command of [...grouped.keys()].sort((left, right) => left.localeCompare(right))) {
|
|
1306
|
+
const bucket = grouped.get(command) ?? [];
|
|
1307
|
+
if (bucket.length <= 1) {
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
const winner = bucket[bucket.length - 1];
|
|
1311
|
+
for (const displaced of bucket.slice(0, -1)) {
|
|
1312
|
+
warnings.push(`${codePrefix}:${command}:${winner.layer}:${winner.name}:${displaced.layer}:${displaced.name}`);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
collectByCommand(commands.handlers, "extension_command_handler_collision");
|
|
1317
|
+
collectByCommand(commands.overrides, "extension_command_override_collision");
|
|
1318
|
+
const handlerCommands = new Set(commands.handlers.map((entry) => entry.command));
|
|
1319
|
+
const overlapCommands = [...new Set(commands.overrides.map((entry) => entry.command))]
|
|
1320
|
+
.filter((command) => handlerCommands.has(command))
|
|
1321
|
+
.sort((left, right) => left.localeCompare(right));
|
|
1322
|
+
for (const command of overlapCommands) {
|
|
1323
|
+
warnings.push(`extension_command_override_handler_overlap:${command}`);
|
|
1324
|
+
}
|
|
1325
|
+
return warnings;
|
|
1326
|
+
}
|
|
1327
|
+
function collectRendererCollisionWarnings(renderers) {
|
|
1328
|
+
const grouped = new Map();
|
|
1329
|
+
for (const entry of renderers.overrides) {
|
|
1330
|
+
const bucket = grouped.get(entry.format) ?? [];
|
|
1331
|
+
bucket.push(entry);
|
|
1332
|
+
grouped.set(entry.format, bucket);
|
|
1333
|
+
}
|
|
1334
|
+
const warnings = [];
|
|
1335
|
+
for (const format of [...grouped.keys()].sort((left, right) => left.localeCompare(right))) {
|
|
1336
|
+
const bucket = grouped.get(format) ?? [];
|
|
1337
|
+
if (bucket.length <= 1) {
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
const winner = bucket[bucket.length - 1];
|
|
1341
|
+
for (const displaced of bucket.slice(0, -1)) {
|
|
1342
|
+
warnings.push(`extension_renderer_collision:${format}:${winner.layer}:${winner.name}:${displaced.layer}:${displaced.name}`);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
return warnings;
|
|
1346
|
+
}
|
|
1347
|
+
function collectParserCollisionWarnings(parsers) {
|
|
1348
|
+
const warnings = [];
|
|
1349
|
+
const grouped = new Map();
|
|
1350
|
+
for (const entry of parsers.overrides) {
|
|
1351
|
+
const bucket = grouped.get(entry.command) ?? [];
|
|
1352
|
+
bucket.push(entry);
|
|
1353
|
+
grouped.set(entry.command, bucket);
|
|
1354
|
+
}
|
|
1355
|
+
for (const command of [...grouped.keys()].sort((left, right) => left.localeCompare(right))) {
|
|
1356
|
+
const bucket = grouped.get(command) ?? [];
|
|
1357
|
+
if (bucket.length <= 1) {
|
|
1358
|
+
continue;
|
|
1359
|
+
}
|
|
1360
|
+
const winner = bucket[bucket.length - 1];
|
|
1361
|
+
for (const displaced of bucket.slice(0, -1)) {
|
|
1362
|
+
warnings.push(`extension_parser_override_collision:${command}:${winner.layer}:${winner.name}:${displaced.layer}:${displaced.name}`);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return warnings;
|
|
1366
|
+
}
|
|
1367
|
+
function collectPreflightCollisionWarnings(preflight) {
|
|
1368
|
+
if (preflight.overrides.length <= 1) {
|
|
1369
|
+
return [];
|
|
1370
|
+
}
|
|
1371
|
+
const winner = preflight.overrides[preflight.overrides.length - 1];
|
|
1372
|
+
return preflight.overrides.slice(0, -1).map((displaced) => `extension_preflight_override_collision:${winner.layer}:${winner.name}:${displaced.layer}:${displaced.name}`);
|
|
1373
|
+
}
|
|
1374
|
+
function collectServiceCollisionWarnings(services) {
|
|
1375
|
+
const warnings = [];
|
|
1376
|
+
const grouped = new Map();
|
|
1377
|
+
for (const entry of services.overrides) {
|
|
1378
|
+
const bucket = grouped.get(entry.service) ?? [];
|
|
1379
|
+
bucket.push(entry);
|
|
1380
|
+
grouped.set(entry.service, bucket);
|
|
1381
|
+
}
|
|
1382
|
+
for (const service of [...grouped.keys()].sort((left, right) => left.localeCompare(right))) {
|
|
1383
|
+
const bucket = grouped.get(service) ?? [];
|
|
1384
|
+
if (bucket.length <= 1) {
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
const winner = bucket[bucket.length - 1];
|
|
1388
|
+
for (const displaced of bucket.slice(0, -1)) {
|
|
1389
|
+
warnings.push(`extension_service_override_collision:${service}:${winner.layer}:${winner.name}:${displaced.layer}:${displaced.name}`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return warnings;
|
|
1393
|
+
}
|
|
724
1394
|
export async function activateExtensions(loadResult) {
|
|
725
1395
|
const hooks = createEmptyExtensionHookRegistry();
|
|
726
1396
|
const commands = createEmptyExtensionCommandRegistry();
|
|
1397
|
+
const parsers = createEmptyExtensionParserRegistry();
|
|
1398
|
+
const preflight = createEmptyExtensionPreflightRegistry();
|
|
1399
|
+
const services = createEmptyExtensionServiceRegistry();
|
|
727
1400
|
const renderers = createEmptyExtensionRendererRegistry();
|
|
728
1401
|
const registrations = createEmptyExtensionRegistrationRegistry();
|
|
729
1402
|
const failed = [];
|
|
@@ -734,25 +1407,38 @@ export async function activateExtensions(loadResult) {
|
|
|
734
1407
|
continue;
|
|
735
1408
|
}
|
|
736
1409
|
try {
|
|
737
|
-
await activatable.activate(createExtensionApi(extension, hooks, commands, renderers, registrations));
|
|
1410
|
+
await activatable.activate(createExtensionApi(extension, hooks, commands, parsers, preflight, services, renderers, registrations, warnings));
|
|
738
1411
|
}
|
|
739
1412
|
catch (error) {
|
|
740
1413
|
warnings.push(`extension_activate_failed:${extension.layer}:${extension.name}`);
|
|
1414
|
+
const trace = extractRegistrationValidationTrace(error);
|
|
741
1415
|
failed.push({
|
|
742
1416
|
layer: extension.layer,
|
|
743
1417
|
name: extension.name,
|
|
744
1418
|
entry_path: extension.entry_path,
|
|
745
1419
|
error: formatUnknownError(error),
|
|
1420
|
+
trace,
|
|
746
1421
|
});
|
|
747
1422
|
}
|
|
748
1423
|
}
|
|
1424
|
+
const collisionWarnings = [
|
|
1425
|
+
...collectCommandCollisionWarnings(commands),
|
|
1426
|
+
...collectParserCollisionWarnings(parsers),
|
|
1427
|
+
...collectPreflightCollisionWarnings(preflight),
|
|
1428
|
+
...collectServiceCollisionWarnings(services),
|
|
1429
|
+
...collectRendererCollisionWarnings(renderers),
|
|
1430
|
+
];
|
|
1431
|
+
const mergedWarnings = [...new Set([...warnings, ...collisionWarnings])];
|
|
749
1432
|
return {
|
|
750
1433
|
hooks,
|
|
751
1434
|
commands,
|
|
1435
|
+
parsers,
|
|
1436
|
+
preflight,
|
|
1437
|
+
services,
|
|
752
1438
|
renderers,
|
|
753
1439
|
registrations,
|
|
754
1440
|
failed,
|
|
755
|
-
warnings,
|
|
1441
|
+
warnings: mergedWarnings,
|
|
756
1442
|
hook_counts: {
|
|
757
1443
|
before_command: hooks.beforeCommand.length,
|
|
758
1444
|
after_command: hooks.afterCommand.length,
|
|
@@ -762,6 +1448,9 @@ export async function activateExtensions(loadResult) {
|
|
|
762
1448
|
},
|
|
763
1449
|
command_override_count: commands.overrides.length,
|
|
764
1450
|
command_handler_count: commands.handlers.length,
|
|
1451
|
+
parser_override_count: parsers.overrides.length,
|
|
1452
|
+
preflight_override_count: preflight.overrides.length,
|
|
1453
|
+
service_override_count: services.overrides.length,
|
|
765
1454
|
renderer_override_count: renderers.overrides.length,
|
|
766
1455
|
registration_counts: getRegistrationCounts(registrations),
|
|
767
1456
|
};
|
|
@@ -820,6 +1509,208 @@ export async function runCommandHandler(commands, context) {
|
|
|
820
1509
|
};
|
|
821
1510
|
}
|
|
822
1511
|
}
|
|
1512
|
+
export async function runParserOverride(parsers, context) {
|
|
1513
|
+
const command = normalizeCommandName(context.command);
|
|
1514
|
+
if (command.length === 0) {
|
|
1515
|
+
return {
|
|
1516
|
+
overridden: false,
|
|
1517
|
+
context: {
|
|
1518
|
+
command,
|
|
1519
|
+
args: cloneContextSnapshot(context.args),
|
|
1520
|
+
options: cloneCommandOptionsSnapshot(context.options),
|
|
1521
|
+
global: cloneGlobalOptionsSnapshot(context.global),
|
|
1522
|
+
pm_root: context.pm_root,
|
|
1523
|
+
},
|
|
1524
|
+
warnings: [],
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
const matched = [...parsers.overrides].reverse().find((entry) => entry.command === command);
|
|
1528
|
+
if (!matched) {
|
|
1529
|
+
return {
|
|
1530
|
+
overridden: false,
|
|
1531
|
+
context: {
|
|
1532
|
+
command,
|
|
1533
|
+
args: cloneContextSnapshot(context.args),
|
|
1534
|
+
options: cloneCommandOptionsSnapshot(context.options),
|
|
1535
|
+
global: cloneGlobalOptionsSnapshot(context.global),
|
|
1536
|
+
pm_root: context.pm_root,
|
|
1537
|
+
},
|
|
1538
|
+
warnings: [],
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
try {
|
|
1542
|
+
const delta = (await Promise.resolve(matched.run({
|
|
1543
|
+
command,
|
|
1544
|
+
args: cloneContextSnapshot(context.args),
|
|
1545
|
+
options: cloneCommandOptionsSnapshot(context.options),
|
|
1546
|
+
global: cloneGlobalOptionsSnapshot(context.global),
|
|
1547
|
+
pm_root: context.pm_root,
|
|
1548
|
+
}))) ?? {};
|
|
1549
|
+
const nextArgs = Array.isArray(delta.args) ? cloneContextSnapshot(delta.args) : cloneContextSnapshot(context.args);
|
|
1550
|
+
const nextOptions = delta.options ? cloneCommandOptionsSnapshot(delta.options) : cloneCommandOptionsSnapshot(context.options);
|
|
1551
|
+
const nextGlobal = delta.global ? cloneGlobalOptionsSnapshot(delta.global) : cloneGlobalOptionsSnapshot(context.global);
|
|
1552
|
+
return {
|
|
1553
|
+
overridden: true,
|
|
1554
|
+
context: {
|
|
1555
|
+
command,
|
|
1556
|
+
args: nextArgs,
|
|
1557
|
+
options: nextOptions,
|
|
1558
|
+
global: nextGlobal,
|
|
1559
|
+
pm_root: context.pm_root,
|
|
1560
|
+
},
|
|
1561
|
+
warnings: [],
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
catch {
|
|
1565
|
+
return {
|
|
1566
|
+
overridden: false,
|
|
1567
|
+
context: {
|
|
1568
|
+
command,
|
|
1569
|
+
args: cloneContextSnapshot(context.args),
|
|
1570
|
+
options: cloneCommandOptionsSnapshot(context.options),
|
|
1571
|
+
global: cloneGlobalOptionsSnapshot(context.global),
|
|
1572
|
+
pm_root: context.pm_root,
|
|
1573
|
+
},
|
|
1574
|
+
warnings: [`extension_parser_override_failed:${matched.layer}:${matched.name}:${matched.command}`],
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
export async function runPreflightOverride(preflight, context) {
|
|
1579
|
+
const matched = [...preflight.overrides].reverse()[0];
|
|
1580
|
+
const baseContext = {
|
|
1581
|
+
command: normalizeCommandName(context.command),
|
|
1582
|
+
args: cloneContextSnapshot(context.args),
|
|
1583
|
+
options: cloneCommandOptionsSnapshot(context.options),
|
|
1584
|
+
global: cloneGlobalOptionsSnapshot(context.global),
|
|
1585
|
+
pm_root: context.pm_root,
|
|
1586
|
+
};
|
|
1587
|
+
const baseDecision = cloneContextSnapshot(context.decision);
|
|
1588
|
+
if (!matched) {
|
|
1589
|
+
return {
|
|
1590
|
+
overridden: false,
|
|
1591
|
+
context: baseContext,
|
|
1592
|
+
decision: baseDecision,
|
|
1593
|
+
warnings: [],
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
try {
|
|
1597
|
+
const delta = (await Promise.resolve(matched.run({
|
|
1598
|
+
command: baseContext.command,
|
|
1599
|
+
args: cloneContextSnapshot(baseContext.args),
|
|
1600
|
+
options: cloneCommandOptionsSnapshot(baseContext.options),
|
|
1601
|
+
global: cloneGlobalOptionsSnapshot(baseContext.global),
|
|
1602
|
+
pm_root: baseContext.pm_root,
|
|
1603
|
+
decision: cloneContextSnapshot(baseDecision),
|
|
1604
|
+
}))) ?? {};
|
|
1605
|
+
const nextContext = {
|
|
1606
|
+
command: baseContext.command,
|
|
1607
|
+
args: Array.isArray(delta.args) ? cloneContextSnapshot(delta.args) : baseContext.args,
|
|
1608
|
+
options: delta.options ? cloneCommandOptionsSnapshot(delta.options) : baseContext.options,
|
|
1609
|
+
global: delta.global ? cloneGlobalOptionsSnapshot(delta.global) : baseContext.global,
|
|
1610
|
+
pm_root: baseContext.pm_root,
|
|
1611
|
+
};
|
|
1612
|
+
const nextDecision = {
|
|
1613
|
+
enforce_item_format_gate: typeof delta.enforce_item_format_gate === "boolean"
|
|
1614
|
+
? delta.enforce_item_format_gate
|
|
1615
|
+
: baseDecision.enforce_item_format_gate,
|
|
1616
|
+
run_preflight_item_format_sync: typeof delta.run_preflight_item_format_sync === "boolean"
|
|
1617
|
+
? delta.run_preflight_item_format_sync
|
|
1618
|
+
: baseDecision.run_preflight_item_format_sync,
|
|
1619
|
+
run_extension_migrations: typeof delta.run_extension_migrations === "boolean"
|
|
1620
|
+
? delta.run_extension_migrations
|
|
1621
|
+
: baseDecision.run_extension_migrations,
|
|
1622
|
+
enforce_mandatory_migration_gate: typeof delta.enforce_mandatory_migration_gate === "boolean"
|
|
1623
|
+
? delta.enforce_mandatory_migration_gate
|
|
1624
|
+
: baseDecision.enforce_mandatory_migration_gate,
|
|
1625
|
+
};
|
|
1626
|
+
return {
|
|
1627
|
+
overridden: true,
|
|
1628
|
+
context: nextContext,
|
|
1629
|
+
decision: nextDecision,
|
|
1630
|
+
warnings: [],
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
catch {
|
|
1634
|
+
return {
|
|
1635
|
+
overridden: false,
|
|
1636
|
+
context: baseContext,
|
|
1637
|
+
decision: baseDecision,
|
|
1638
|
+
warnings: [`extension_preflight_override_failed:${matched.layer}:${matched.name}`],
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
function resolveDefaultServiceResult(context) {
|
|
1643
|
+
return {
|
|
1644
|
+
handled: false,
|
|
1645
|
+
result: context.payload,
|
|
1646
|
+
warnings: [],
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
export function runServiceOverrideSync(services, context) {
|
|
1650
|
+
const matched = [...services.overrides].reverse().find((entry) => entry.service === context.service);
|
|
1651
|
+
if (!matched) {
|
|
1652
|
+
return resolveDefaultServiceResult(context);
|
|
1653
|
+
}
|
|
1654
|
+
try {
|
|
1655
|
+
const result = matched.run({
|
|
1656
|
+
service: context.service,
|
|
1657
|
+
command: context.command ? normalizeCommandName(context.command) : undefined,
|
|
1658
|
+
args: context.args ? cloneContextSnapshot(context.args) : undefined,
|
|
1659
|
+
options: context.options ? cloneCommandOptionsSnapshot(context.options) : undefined,
|
|
1660
|
+
global: context.global ? cloneGlobalOptionsSnapshot(context.global) : undefined,
|
|
1661
|
+
pm_root: context.pm_root,
|
|
1662
|
+
payload: cloneContextSnapshot(context.payload),
|
|
1663
|
+
});
|
|
1664
|
+
if (result instanceof Promise) {
|
|
1665
|
+
return {
|
|
1666
|
+
handled: false,
|
|
1667
|
+
result: context.payload,
|
|
1668
|
+
warnings: [`extension_service_override_async_unsupported:${matched.layer}:${matched.name}:${matched.service}`],
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
return {
|
|
1672
|
+
handled: true,
|
|
1673
|
+
result,
|
|
1674
|
+
warnings: [],
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
catch {
|
|
1678
|
+
return {
|
|
1679
|
+
handled: false,
|
|
1680
|
+
result: context.payload,
|
|
1681
|
+
warnings: [`extension_service_override_failed:${matched.layer}:${matched.name}:${matched.service}`],
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
export async function runServiceOverride(services, context) {
|
|
1686
|
+
const matched = [...services.overrides].reverse().find((entry) => entry.service === context.service);
|
|
1687
|
+
if (!matched) {
|
|
1688
|
+
return resolveDefaultServiceResult(context);
|
|
1689
|
+
}
|
|
1690
|
+
try {
|
|
1691
|
+
const result = await Promise.resolve(matched.run({
|
|
1692
|
+
service: context.service,
|
|
1693
|
+
command: context.command ? normalizeCommandName(context.command) : undefined,
|
|
1694
|
+
args: context.args ? cloneContextSnapshot(context.args) : undefined,
|
|
1695
|
+
options: context.options ? cloneCommandOptionsSnapshot(context.options) : undefined,
|
|
1696
|
+
global: context.global ? cloneGlobalOptionsSnapshot(context.global) : undefined,
|
|
1697
|
+
pm_root: context.pm_root,
|
|
1698
|
+
payload: cloneContextSnapshot(context.payload),
|
|
1699
|
+
}));
|
|
1700
|
+
return {
|
|
1701
|
+
handled: true,
|
|
1702
|
+
result,
|
|
1703
|
+
warnings: [],
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
catch {
|
|
1707
|
+
return {
|
|
1708
|
+
handled: false,
|
|
1709
|
+
result: context.payload,
|
|
1710
|
+
warnings: [`extension_service_override_failed:${matched.layer}:${matched.name}:${matched.service}`],
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
823
1714
|
export function runCommandOverride(commands, context) {
|
|
824
1715
|
const command = normalizeCommandName(context.command);
|
|
825
1716
|
if (command.length === 0) {
|