@unbrained/pm-cli 2026.3.9
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/.pi/extensions/pm-cli/index.ts +778 -0
- package/AGENTS.md +475 -0
- package/LICENSE +21 -0
- package/PRD.md +1390 -0
- package/README.md +655 -0
- package/dist/cli/commands/activity.d.ts +14 -0
- package/dist/cli/commands/activity.js +80 -0
- package/dist/cli/commands/activity.js.map +1 -0
- package/dist/cli/commands/append.d.ts +13 -0
- package/dist/cli/commands/append.js +46 -0
- package/dist/cli/commands/append.js.map +1 -0
- package/dist/cli/commands/beads.d.ts +15 -0
- package/dist/cli/commands/beads.js +475 -0
- package/dist/cli/commands/beads.js.map +1 -0
- package/dist/cli/commands/claim.d.ts +19 -0
- package/dist/cli/commands/claim.js +79 -0
- package/dist/cli/commands/claim.js.map +1 -0
- package/dist/cli/commands/close.d.ts +12 -0
- package/dist/cli/commands/close.js +58 -0
- package/dist/cli/commands/close.js.map +1 -0
- package/dist/cli/commands/comments.d.ts +15 -0
- package/dist/cli/commands/comments.js +80 -0
- package/dist/cli/commands/comments.js.map +1 -0
- package/dist/cli/commands/completion.d.ts +10 -0
- package/dist/cli/commands/completion.js +469 -0
- package/dist/cli/commands/completion.js.map +1 -0
- package/dist/cli/commands/config.d.ts +15 -0
- package/dist/cli/commands/config.js +72 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/create.d.ts +60 -0
- package/dist/cli/commands/create.js +456 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/delete.d.ts +12 -0
- package/dist/cli/commands/delete.js +33 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/docs.d.ts +16 -0
- package/dist/cli/commands/docs.js +113 -0
- package/dist/cli/commands/docs.js.map +1 -0
- package/dist/cli/commands/files.d.ts +17 -0
- package/dist/cli/commands/files.js +113 -0
- package/dist/cli/commands/files.js.map +1 -0
- package/dist/cli/commands/gc.d.ts +9 -0
- package/dist/cli/commands/gc.js +80 -0
- package/dist/cli/commands/gc.js.map +1 -0
- package/dist/cli/commands/get.d.ts +12 -0
- package/dist/cli/commands/get.js +28 -0
- package/dist/cli/commands/get.js.map +1 -0
- package/dist/cli/commands/health.d.ts +15 -0
- package/dist/cli/commands/health.js +288 -0
- package/dist/cli/commands/health.js.map +1 -0
- package/dist/cli/commands/history.d.ts +13 -0
- package/dist/cli/commands/history.js +72 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/index.d.ts +26 -0
- package/dist/cli/commands/index.js +27 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +10 -0
- package/dist/cli/commands/init.js +59 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/install.d.ts +18 -0
- package/dist/cli/commands/install.js +87 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/list.d.ts +21 -0
- package/dist/cli/commands/list.js +137 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/reindex.d.ts +16 -0
- package/dist/cli/commands/reindex.js +154 -0
- package/dist/cli/commands/reindex.js.map +1 -0
- package/dist/cli/commands/restore.d.ts +20 -0
- package/dist/cli/commands/restore.js +208 -0
- package/dist/cli/commands/restore.js.map +1 -0
- package/dist/cli/commands/search.d.ts +45 -0
- package/dist/cli/commands/search.js +531 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +13 -0
- package/dist/cli/commands/stats.js +88 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/test-all.d.ts +30 -0
- package/dist/cli/commands/test-all.js +157 -0
- package/dist/cli/commands/test-all.js.map +1 -0
- package/dist/cli/commands/test.d.ts +29 -0
- package/dist/cli/commands/test.js +492 -0
- package/dist/cli/commands/test.js.map +1 -0
- package/dist/cli/commands/update.d.ts +52 -0
- package/dist/cli/commands/update.js +467 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/extension-command-options.d.ts +1 -0
- package/dist/cli/extension-command-options.js +76 -0
- package/dist/cli/extension-command-options.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +1494 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -0
- package/dist/command-types.d.ts +1 -0
- package/dist/command-types.js +2 -0
- package/dist/command-types.js.map +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/extensions/builtins.d.ts +3 -0
- package/dist/core/extensions/builtins.js +47 -0
- package/dist/core/extensions/builtins.js.map +1 -0
- package/dist/core/extensions/index.d.ts +13 -0
- package/dist/core/extensions/index.js +88 -0
- package/dist/core/extensions/index.js.map +1 -0
- package/dist/core/extensions/loader.d.ts +301 -0
- package/dist/core/extensions/loader.js +917 -0
- package/dist/core/extensions/loader.js.map +1 -0
- package/dist/core/fs/fs-utils.d.ts +6 -0
- package/dist/core/fs/fs-utils.js +58 -0
- package/dist/core/fs/fs-utils.js.map +1 -0
- package/dist/core/fs/index.d.ts +1 -0
- package/dist/core/fs/index.js +2 -0
- package/dist/core/fs/index.js.map +1 -0
- package/dist/core/history/history.d.ts +12 -0
- package/dist/core/history/history.js +44 -0
- package/dist/core/history/history.js.map +1 -0
- package/dist/core/history/index.d.ts +1 -0
- package/dist/core/history/index.js +2 -0
- package/dist/core/history/index.js.map +1 -0
- package/dist/core/item/id.d.ts +3 -0
- package/dist/core/item/id.js +54 -0
- package/dist/core/item/id.js.map +1 -0
- package/dist/core/item/index.d.ts +3 -0
- package/dist/core/item/index.js +4 -0
- package/dist/core/item/index.js.map +1 -0
- package/dist/core/item/item-format.d.ts +9 -0
- package/dist/core/item/item-format.js +363 -0
- package/dist/core/item/item-format.js.map +1 -0
- package/dist/core/item/parse.d.ts +3 -0
- package/dist/core/item/parse.js +72 -0
- package/dist/core/item/parse.js.map +1 -0
- package/dist/core/lock/index.d.ts +1 -0
- package/dist/core/lock/index.js +2 -0
- package/dist/core/lock/index.js.map +1 -0
- package/dist/core/lock/lock.d.ts +1 -0
- package/dist/core/lock/lock.js +100 -0
- package/dist/core/lock/lock.js.map +1 -0
- package/dist/core/output/output.d.ts +7 -0
- package/dist/core/output/output.js +79 -0
- package/dist/core/output/output.js.map +1 -0
- package/dist/core/search/cache.d.ts +17 -0
- package/dist/core/search/cache.js +212 -0
- package/dist/core/search/cache.js.map +1 -0
- package/dist/core/search/embedding-batches.d.ts +7 -0
- package/dist/core/search/embedding-batches.js +54 -0
- package/dist/core/search/embedding-batches.js.map +1 -0
- package/dist/core/search/providers.d.ts +59 -0
- package/dist/core/search/providers.js +265 -0
- package/dist/core/search/providers.js.map +1 -0
- package/dist/core/search/vector-stores.d.ts +89 -0
- package/dist/core/search/vector-stores.js +546 -0
- package/dist/core/search/vector-stores.js.map +1 -0
- package/dist/core/shared/command-types.d.ts +7 -0
- package/dist/core/shared/command-types.js +2 -0
- package/dist/core/shared/command-types.js.map +1 -0
- package/dist/core/shared/constants.d.ts +19 -0
- package/dist/core/shared/constants.js +134 -0
- package/dist/core/shared/constants.js.map +1 -0
- package/dist/core/shared/errors.d.ts +4 -0
- package/dist/core/shared/errors.js +9 -0
- package/dist/core/shared/errors.js.map +1 -0
- package/dist/core/shared/index.d.ts +3 -0
- package/dist/core/shared/index.js +4 -0
- package/dist/core/shared/index.js.map +1 -0
- package/dist/core/shared/serialization.d.ts +3 -0
- package/dist/core/shared/serialization.js +70 -0
- package/dist/core/shared/serialization.js.map +1 -0
- package/dist/core/shared/time.d.ts +3 -0
- package/dist/core/shared/time.js +28 -0
- package/dist/core/shared/time.js.map +1 -0
- package/dist/core/store/index.d.ts +3 -0
- package/dist/core/store/index.js +4 -0
- package/dist/core/store/index.js.map +1 -0
- package/dist/core/store/item-store.d.ts +42 -0
- package/dist/core/store/item-store.js +186 -0
- package/dist/core/store/item-store.js.map +1 -0
- package/dist/core/store/paths.d.ts +8 -0
- package/dist/core/store/paths.js +29 -0
- package/dist/core/store/paths.js.map +1 -0
- package/dist/core/store/settings.d.ts +4 -0
- package/dist/core/store/settings.js +148 -0
- package/dist/core/store/settings.js.map +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -0
- package/dist/extensions/builtins/beads/index.d.ts +8 -0
- package/dist/extensions/builtins/beads/index.js +29 -0
- package/dist/extensions/builtins/beads/index.js.map +1 -0
- package/dist/extensions/builtins/todos/import-export.d.ts +26 -0
- package/dist/extensions/builtins/todos/import-export.js +460 -0
- package/dist/extensions/builtins/todos/import-export.js.map +1 -0
- package/dist/extensions/builtins/todos/index.d.ts +8 -0
- package/dist/extensions/builtins/todos/index.js +38 -0
- package/dist/extensions/builtins/todos/index.js.map +1 -0
- package/dist/fs-utils.d.ts +1 -0
- package/dist/fs-utils.js +2 -0
- package/dist/fs-utils.js.map +1 -0
- package/dist/history.d.ts +1 -0
- package/dist/history.js +2 -0
- package/dist/history.js.map +1 -0
- package/dist/id.d.ts +1 -0
- package/dist/id.js +2 -0
- package/dist/id.js.map +1 -0
- package/dist/item-format.d.ts +1 -0
- package/dist/item-format.js +2 -0
- package/dist/item-format.js.map +1 -0
- package/dist/item-store.d.ts +1 -0
- package/dist/item-store.js +2 -0
- package/dist/item-store.js.map +1 -0
- package/dist/lock.d.ts +1 -0
- package/dist/lock.js +2 -0
- package/dist/lock.js.map +1 -0
- package/dist/output.d.ts +1 -0
- package/dist/output.js +2 -0
- package/dist/output.js.map +1 -0
- package/dist/parse.d.ts +1 -0
- package/dist/parse.js +2 -0
- package/dist/parse.js.map +1 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +2 -0
- package/dist/paths.js.map +1 -0
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +2 -0
- package/dist/serialization.js.map +1 -0
- package/dist/settings.d.ts +1 -0
- package/dist/settings.js +2 -0
- package/dist/settings.js.map +1 -0
- package/dist/time.d.ts +1 -0
- package/dist/time.js +2 -0
- package/dist/time.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types.d.ts +179 -0
- package/dist/types.js +21 -0
- package/dist/types.js.map +1 -0
- package/docs/ARCHITECTURE.md +246 -0
- package/docs/EXTENSIONS.md +329 -0
- package/docs/RELEASING.md +65 -0
- package/package.json +79 -0
- package/scripts/install.ps1 +112 -0
- package/scripts/install.sh +113 -0
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { pathExists } from "../fs/fs-utils.js";
|
|
5
|
+
import { resolveGlobalPmRoot } from "../store/paths.js";
|
|
6
|
+
const DEFAULT_EXTENSION_PRIORITY = 100;
|
|
7
|
+
const KNOWN_EXTENSION_CAPABILITIES = ["commands", "renderers", "hooks", "schema", "importers", "search"];
|
|
8
|
+
function normalizeNames(values) {
|
|
9
|
+
return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))].sort((a, b) => a.localeCompare(b));
|
|
10
|
+
}
|
|
11
|
+
function isKnownExtensionCapability(value) {
|
|
12
|
+
return KNOWN_EXTENSION_CAPABILITIES.includes(value);
|
|
13
|
+
}
|
|
14
|
+
function collectUnknownExtensionCapabilities(capabilities) {
|
|
15
|
+
return capabilities.filter((capability) => !isKnownExtensionCapability(capability));
|
|
16
|
+
}
|
|
17
|
+
function parseManifest(raw) {
|
|
18
|
+
if (typeof raw !== "object" || raw === null) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const candidate = raw;
|
|
22
|
+
if (typeof candidate.name !== "string" || candidate.name.trim().length === 0) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (typeof candidate.version !== "string" || candidate.version.trim().length === 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (typeof candidate.entry !== "string" || candidate.entry.trim().length === 0) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
let priority = DEFAULT_EXTENSION_PRIORITY;
|
|
32
|
+
if ("priority" in candidate && candidate.priority !== undefined && candidate.priority !== null) {
|
|
33
|
+
if (typeof candidate.priority !== "number" || !Number.isInteger(candidate.priority)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
priority = candidate.priority;
|
|
37
|
+
}
|
|
38
|
+
let capabilities = [];
|
|
39
|
+
if ("capabilities" in candidate && candidate.capabilities !== undefined && candidate.capabilities !== null) {
|
|
40
|
+
if (!Array.isArray(candidate.capabilities) || candidate.capabilities.some((value) => typeof value !== "string")) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
capabilities = normalizeNames(candidate.capabilities.map((value) => value.toLowerCase()));
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
name: candidate.name.trim(),
|
|
47
|
+
version: candidate.version.trim(),
|
|
48
|
+
entry: candidate.entry.trim(),
|
|
49
|
+
priority,
|
|
50
|
+
capabilities,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function asRecord(value) {
|
|
54
|
+
if (typeof value !== "object" || value === null) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
function shouldEnable(name, enabled, disabled) {
|
|
60
|
+
if (disabled.has(name)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (enabled.size === 0) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return enabled.has(name);
|
|
67
|
+
}
|
|
68
|
+
function isPathWithinDirectory(directory, targetPath) {
|
|
69
|
+
const relative = path.relative(directory, targetPath);
|
|
70
|
+
if (relative.length === 0) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
74
|
+
}
|
|
75
|
+
async function isCanonicalPathWithinDirectory(directory, targetPath) {
|
|
76
|
+
const [resolvedDirectory, resolvedTargetPath] = await Promise.all([fs.realpath(directory), fs.realpath(targetPath)]);
|
|
77
|
+
return isPathWithinDirectory(resolvedDirectory, resolvedTargetPath);
|
|
78
|
+
}
|
|
79
|
+
export function resolveExtensionRoots(pmRoot, cwd = process.cwd()) {
|
|
80
|
+
return {
|
|
81
|
+
global: path.join(resolveGlobalPmRoot(cwd), "extensions"),
|
|
82
|
+
project: path.join(pmRoot, "extensions"),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function createEmptyExtensionHookRegistry() {
|
|
86
|
+
return {
|
|
87
|
+
beforeCommand: [],
|
|
88
|
+
afterCommand: [],
|
|
89
|
+
onWrite: [],
|
|
90
|
+
onRead: [],
|
|
91
|
+
onIndex: [],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function createEmptyExtensionCommandRegistry() {
|
|
95
|
+
return {
|
|
96
|
+
overrides: [],
|
|
97
|
+
handlers: [],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function createEmptyExtensionRendererRegistry() {
|
|
101
|
+
return {
|
|
102
|
+
overrides: [],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export function createEmptyExtensionRegistrationRegistry() {
|
|
106
|
+
return {
|
|
107
|
+
flags: [],
|
|
108
|
+
item_fields: [],
|
|
109
|
+
migrations: [],
|
|
110
|
+
importers: [],
|
|
111
|
+
exporters: [],
|
|
112
|
+
search_providers: [],
|
|
113
|
+
vector_store_adapters: [],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function listExtensionDirectories(extensionsRoot) {
|
|
117
|
+
try {
|
|
118
|
+
const entries = await fs.readdir(extensionsRoot, { withFileTypes: true });
|
|
119
|
+
return entries
|
|
120
|
+
.filter((entry) => entry.isDirectory())
|
|
121
|
+
.map((entry) => entry.name)
|
|
122
|
+
.sort((a, b) => a.localeCompare(b));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function summarizeCandidate(candidate) {
|
|
129
|
+
return {
|
|
130
|
+
layer: candidate.layer,
|
|
131
|
+
directory: candidate.directory,
|
|
132
|
+
manifest_path: candidate.manifest_path,
|
|
133
|
+
name: candidate.manifest.name,
|
|
134
|
+
version: candidate.manifest.version,
|
|
135
|
+
entry: candidate.manifest.entry,
|
|
136
|
+
priority: candidate.manifest.priority,
|
|
137
|
+
entry_path: candidate.entry_path,
|
|
138
|
+
capabilities: [...candidate.manifest.capabilities],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function sortCandidates(candidates) {
|
|
142
|
+
return [...candidates].sort((left, right) => {
|
|
143
|
+
if (left.manifest.priority !== right.manifest.priority) {
|
|
144
|
+
return left.manifest.priority - right.manifest.priority;
|
|
145
|
+
}
|
|
146
|
+
const byName = left.manifest.name.localeCompare(right.manifest.name);
|
|
147
|
+
if (byName !== 0) {
|
|
148
|
+
return byName;
|
|
149
|
+
}
|
|
150
|
+
return left.directory.localeCompare(right.directory);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function buildEffectiveExtensions(globalCandidates, projectCandidates) {
|
|
154
|
+
const ordered = [...sortCandidates(globalCandidates), ...sortCandidates(projectCandidates)];
|
|
155
|
+
const effective = [];
|
|
156
|
+
for (const candidate of ordered) {
|
|
157
|
+
const existingIndex = effective.findIndex((entry) => entry.manifest.name === candidate.manifest.name);
|
|
158
|
+
if (existingIndex >= 0) {
|
|
159
|
+
effective.splice(existingIndex, 1);
|
|
160
|
+
}
|
|
161
|
+
effective.push(candidate);
|
|
162
|
+
}
|
|
163
|
+
return effective;
|
|
164
|
+
}
|
|
165
|
+
async function scanExtensionLayer(layer, extensionsRoot, enabled, disabled) {
|
|
166
|
+
const diagnostics = [];
|
|
167
|
+
const warnings = [];
|
|
168
|
+
const candidates = [];
|
|
169
|
+
const directories = await listExtensionDirectories(extensionsRoot);
|
|
170
|
+
for (const directory of directories) {
|
|
171
|
+
const scanned = await scanExtensionDirectory(layer, extensionsRoot, directory, enabled, disabled);
|
|
172
|
+
diagnostics.push(scanned.diagnostic);
|
|
173
|
+
warnings.push(...scanned.warnings);
|
|
174
|
+
if (scanned.candidate) {
|
|
175
|
+
candidates.push(scanned.candidate);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { diagnostics, warnings, candidates };
|
|
179
|
+
}
|
|
180
|
+
async function scanExtensionDirectory(layer, extensionsRoot, directory, enabled, disabled) {
|
|
181
|
+
const extensionDir = path.join(extensionsRoot, directory);
|
|
182
|
+
const manifestPath = path.join(extensionDir, "manifest.json");
|
|
183
|
+
if (!(await pathExists(manifestPath))) {
|
|
184
|
+
return {
|
|
185
|
+
diagnostic: {
|
|
186
|
+
layer,
|
|
187
|
+
directory,
|
|
188
|
+
manifest_path: manifestPath,
|
|
189
|
+
name: null,
|
|
190
|
+
version: null,
|
|
191
|
+
entry: null,
|
|
192
|
+
priority: null,
|
|
193
|
+
entry_path: null,
|
|
194
|
+
enabled: null,
|
|
195
|
+
status: "warn",
|
|
196
|
+
},
|
|
197
|
+
warnings: [`extension_manifest_missing:${layer}:${directory}`],
|
|
198
|
+
candidate: null,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
let manifest = null;
|
|
202
|
+
try {
|
|
203
|
+
const parsed = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
204
|
+
manifest = parseManifest(parsed);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
manifest = null;
|
|
208
|
+
}
|
|
209
|
+
if (!manifest) {
|
|
210
|
+
return {
|
|
211
|
+
diagnostic: {
|
|
212
|
+
layer,
|
|
213
|
+
directory,
|
|
214
|
+
manifest_path: manifestPath,
|
|
215
|
+
name: null,
|
|
216
|
+
version: null,
|
|
217
|
+
entry: null,
|
|
218
|
+
priority: null,
|
|
219
|
+
entry_path: null,
|
|
220
|
+
enabled: null,
|
|
221
|
+
status: "warn",
|
|
222
|
+
},
|
|
223
|
+
warnings: [`extension_manifest_invalid:${layer}:${directory}`],
|
|
224
|
+
candidate: null,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const entryPath = path.resolve(extensionDir, manifest.entry);
|
|
228
|
+
const entryWithinDirectoryByPath = isPathWithinDirectory(extensionDir, entryPath);
|
|
229
|
+
const entryExists = entryWithinDirectoryByPath ? await pathExists(entryPath) : false;
|
|
230
|
+
const entryWithinDirectory = entryWithinDirectoryByPath && entryExists
|
|
231
|
+
? await isCanonicalPathWithinDirectory(extensionDir, entryPath)
|
|
232
|
+
: entryWithinDirectoryByPath;
|
|
233
|
+
const enabledForLoad = shouldEnable(manifest.name, enabled, disabled);
|
|
234
|
+
const extensionWarnings = [];
|
|
235
|
+
for (const capability of collectUnknownExtensionCapabilities(manifest.capabilities)) {
|
|
236
|
+
extensionWarnings.push(`extension_capability_unknown:${layer}:${manifest.name}:${capability}`);
|
|
237
|
+
}
|
|
238
|
+
if (!entryWithinDirectory) {
|
|
239
|
+
extensionWarnings.push(`extension_entry_outside_extension:${layer}:${manifest.name}`);
|
|
240
|
+
}
|
|
241
|
+
else if (!entryExists) {
|
|
242
|
+
extensionWarnings.push(`extension_entry_missing:${layer}:${manifest.name}`);
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
diagnostic: {
|
|
246
|
+
layer,
|
|
247
|
+
directory,
|
|
248
|
+
manifest_path: manifestPath,
|
|
249
|
+
name: manifest.name,
|
|
250
|
+
version: manifest.version,
|
|
251
|
+
entry: manifest.entry,
|
|
252
|
+
priority: manifest.priority,
|
|
253
|
+
entry_path: entryPath,
|
|
254
|
+
enabled: enabledForLoad,
|
|
255
|
+
status: entryWithinDirectory && entryExists ? "ok" : "warn",
|
|
256
|
+
},
|
|
257
|
+
warnings: extensionWarnings,
|
|
258
|
+
candidate: entryWithinDirectory && entryExists && enabledForLoad
|
|
259
|
+
? {
|
|
260
|
+
layer,
|
|
261
|
+
directory,
|
|
262
|
+
manifest_path: manifestPath,
|
|
263
|
+
entry_path: entryPath,
|
|
264
|
+
manifest,
|
|
265
|
+
}
|
|
266
|
+
: null,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
export async function discoverExtensions(options) {
|
|
270
|
+
const roots = resolveExtensionRoots(options.pmRoot, options.cwd ?? process.cwd());
|
|
271
|
+
const configured_enabled = normalizeNames(options.settings.extensions.enabled);
|
|
272
|
+
const configured_disabled = normalizeNames(options.settings.extensions.disabled);
|
|
273
|
+
if (options.noExtensions) {
|
|
274
|
+
return {
|
|
275
|
+
disabled_by_flag: true,
|
|
276
|
+
roots,
|
|
277
|
+
configured_enabled,
|
|
278
|
+
configured_disabled,
|
|
279
|
+
discovered: [],
|
|
280
|
+
effective: [],
|
|
281
|
+
warnings: [],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const enabled = new Set(configured_enabled);
|
|
285
|
+
const disabled = new Set(configured_disabled);
|
|
286
|
+
const globalScan = await scanExtensionLayer("global", roots.global, enabled, disabled);
|
|
287
|
+
const projectScan = await scanExtensionLayer("project", roots.project, enabled, disabled);
|
|
288
|
+
const effective = buildEffectiveExtensions(globalScan.candidates, projectScan.candidates).map(summarizeCandidate);
|
|
289
|
+
return {
|
|
290
|
+
disabled_by_flag: false,
|
|
291
|
+
roots,
|
|
292
|
+
configured_enabled,
|
|
293
|
+
configured_disabled,
|
|
294
|
+
discovered: [...globalScan.diagnostics, ...projectScan.diagnostics],
|
|
295
|
+
effective,
|
|
296
|
+
warnings: [...globalScan.warnings, ...projectScan.warnings],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function formatUnknownError(error) {
|
|
300
|
+
if (error instanceof Error && error.message.trim().length > 0) {
|
|
301
|
+
return error.message;
|
|
302
|
+
}
|
|
303
|
+
return String(error);
|
|
304
|
+
}
|
|
305
|
+
export async function loadExtensions(options) {
|
|
306
|
+
const discovery = await discoverExtensions(options);
|
|
307
|
+
const loaded = [];
|
|
308
|
+
const failed = [];
|
|
309
|
+
const warnings = [...discovery.warnings];
|
|
310
|
+
if (discovery.disabled_by_flag) {
|
|
311
|
+
return {
|
|
312
|
+
...discovery,
|
|
313
|
+
warnings,
|
|
314
|
+
loaded,
|
|
315
|
+
failed,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
for (const extension of discovery.effective) {
|
|
319
|
+
try {
|
|
320
|
+
const module = await import(pathToFileURL(extension.entry_path).href);
|
|
321
|
+
loaded.push({
|
|
322
|
+
...extension,
|
|
323
|
+
module,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
warnings.push(`extension_load_failed:${extension.layer}:${extension.name}`);
|
|
328
|
+
failed.push({
|
|
329
|
+
layer: extension.layer,
|
|
330
|
+
name: extension.name,
|
|
331
|
+
entry_path: extension.entry_path,
|
|
332
|
+
error: formatUnknownError(error),
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
...discovery,
|
|
338
|
+
warnings,
|
|
339
|
+
loaded,
|
|
340
|
+
failed,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function resolveActivatableExtension(module) {
|
|
344
|
+
const moduleRecord = asRecord(module);
|
|
345
|
+
if (!moduleRecord) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
if (typeof moduleRecord.activate === "function") {
|
|
349
|
+
return {
|
|
350
|
+
activate: moduleRecord.activate,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const defaultExport = asRecord(moduleRecord.default);
|
|
354
|
+
if (defaultExport && typeof defaultExport.activate === "function") {
|
|
355
|
+
return {
|
|
356
|
+
activate: defaultExport.activate,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
function normalizeCommandName(command) {
|
|
362
|
+
return command
|
|
363
|
+
.trim()
|
|
364
|
+
.toLowerCase()
|
|
365
|
+
.split(/\s+/)
|
|
366
|
+
.filter((part) => part.length > 0)
|
|
367
|
+
.join(" ");
|
|
368
|
+
}
|
|
369
|
+
function defaultGlobalOptions() {
|
|
370
|
+
return {
|
|
371
|
+
json: false,
|
|
372
|
+
quiet: false,
|
|
373
|
+
noExtensions: false,
|
|
374
|
+
profile: false,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function cloneCommandOptionsSnapshot(options) {
|
|
378
|
+
return options ? cloneContextSnapshot(options) : {};
|
|
379
|
+
}
|
|
380
|
+
function cloneGlobalOptionsSnapshot(options) {
|
|
381
|
+
return options ? cloneContextSnapshot(options) : defaultGlobalOptions();
|
|
382
|
+
}
|
|
383
|
+
function cloneContextSnapshot(value) {
|
|
384
|
+
return structuredClone(value);
|
|
385
|
+
}
|
|
386
|
+
function isOutputRendererFormat(value) {
|
|
387
|
+
return value === "toon" || value === "json";
|
|
388
|
+
}
|
|
389
|
+
function assertHookHandler(hookName, hook) {
|
|
390
|
+
if (typeof hook !== "function") {
|
|
391
|
+
throw new TypeError(`api.hooks.${hookName} requires a function handler`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function assertNonEmptyString(name, value) {
|
|
395
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
396
|
+
throw new TypeError(`${name} requires a non-empty string`);
|
|
397
|
+
}
|
|
398
|
+
return value.trim();
|
|
399
|
+
}
|
|
400
|
+
function assertFunctionHandler(name, value) {
|
|
401
|
+
if (typeof value !== "function") {
|
|
402
|
+
throw new TypeError(`${name} requires a function handler`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function normalizeRegistrationName(name) {
|
|
406
|
+
return name
|
|
407
|
+
.trim()
|
|
408
|
+
.toLowerCase()
|
|
409
|
+
.split(/\s+/)
|
|
410
|
+
.filter((part) => part.length > 0)
|
|
411
|
+
.join(" ");
|
|
412
|
+
}
|
|
413
|
+
function toRegistrationCommandPath(name, action) {
|
|
414
|
+
return normalizeCommandName(`${name} ${action}`);
|
|
415
|
+
}
|
|
416
|
+
function sanitizeRegistrationValue(value) {
|
|
417
|
+
if (value === null) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
if (Array.isArray(value)) {
|
|
421
|
+
return value.map((entry) => sanitizeRegistrationValue(entry));
|
|
422
|
+
}
|
|
423
|
+
if (typeof value === "function") {
|
|
424
|
+
return "[Function]";
|
|
425
|
+
}
|
|
426
|
+
if (typeof value === "bigint") {
|
|
427
|
+
return value.toString();
|
|
428
|
+
}
|
|
429
|
+
if (typeof value === "symbol") {
|
|
430
|
+
return value.toString();
|
|
431
|
+
}
|
|
432
|
+
if (typeof value === "object") {
|
|
433
|
+
const record = value;
|
|
434
|
+
const normalized = {};
|
|
435
|
+
const keys = Object.keys(record).sort((left, right) => left.localeCompare(right));
|
|
436
|
+
for (const key of keys) {
|
|
437
|
+
normalized[key] = sanitizeRegistrationValue(record[key]);
|
|
438
|
+
}
|
|
439
|
+
return normalized;
|
|
440
|
+
}
|
|
441
|
+
return value;
|
|
442
|
+
}
|
|
443
|
+
function normalizeRegistrationRecord(name, value) {
|
|
444
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
445
|
+
throw new TypeError(`${name} requires an object definition`);
|
|
446
|
+
}
|
|
447
|
+
return sanitizeRegistrationValue(value);
|
|
448
|
+
}
|
|
449
|
+
function normalizeRegistrationRecordList(name, value) {
|
|
450
|
+
if (!Array.isArray(value)) {
|
|
451
|
+
throw new TypeError(`${name} requires an array of object definitions`);
|
|
452
|
+
}
|
|
453
|
+
return value.map((entry) => normalizeRegistrationRecord(name, entry));
|
|
454
|
+
}
|
|
455
|
+
function getDeclaredExtensionCapabilities(extension) {
|
|
456
|
+
if (!Array.isArray(extension.capabilities)) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
const declared = new Set();
|
|
460
|
+
for (const capability of extension.capabilities) {
|
|
461
|
+
const normalized = capability.trim().toLowerCase();
|
|
462
|
+
if (isKnownExtensionCapability(normalized)) {
|
|
463
|
+
declared.add(normalized);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return declared;
|
|
467
|
+
}
|
|
468
|
+
function assertExtensionCapability(extension, capability, method) {
|
|
469
|
+
const declared = getDeclaredExtensionCapabilities(extension);
|
|
470
|
+
// Keep direct unit tests that construct LoadedExtension fixtures without
|
|
471
|
+
// capability metadata backwards-compatible while enforcing manifest-declared
|
|
472
|
+
// capabilities for runtime-loaded extensions.
|
|
473
|
+
if (declared === null) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (!declared.has(capability)) {
|
|
477
|
+
throw new TypeError(`${method} requires capability '${capability}' in extension manifest capabilities`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function createExtensionApi(extension, hooks, commands, renderers, registrations) {
|
|
481
|
+
const registerCommand = (commandOrDefinition, override) => {
|
|
482
|
+
assertExtensionCapability(extension, "commands", "registerCommand");
|
|
483
|
+
if (typeof commandOrDefinition === "string") {
|
|
484
|
+
const normalizedCommand = normalizeCommandName(commandOrDefinition);
|
|
485
|
+
if (normalizedCommand.length === 0) {
|
|
486
|
+
throw new TypeError("registerCommand requires a non-empty command name");
|
|
487
|
+
}
|
|
488
|
+
if (typeof override !== "function") {
|
|
489
|
+
throw new TypeError("registerCommand requires an override function when command name is provided");
|
|
490
|
+
}
|
|
491
|
+
commands.overrides.push({
|
|
492
|
+
layer: extension.layer,
|
|
493
|
+
name: extension.name,
|
|
494
|
+
command: normalizedCommand,
|
|
495
|
+
run: override,
|
|
496
|
+
});
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (typeof commandOrDefinition !== "object" || commandOrDefinition === null) {
|
|
500
|
+
throw new TypeError("registerCommand requires a command definition object");
|
|
501
|
+
}
|
|
502
|
+
if (typeof commandOrDefinition.name !== "string") {
|
|
503
|
+
throw new TypeError("registerCommand requires a command definition name");
|
|
504
|
+
}
|
|
505
|
+
const normalizedCommand = normalizeCommandName(commandOrDefinition.name);
|
|
506
|
+
if (normalizedCommand.length === 0) {
|
|
507
|
+
throw new TypeError("registerCommand requires a non-empty command definition name");
|
|
508
|
+
}
|
|
509
|
+
if (typeof commandOrDefinition.run !== "function") {
|
|
510
|
+
throw new TypeError("registerCommand requires a command definition run handler");
|
|
511
|
+
}
|
|
512
|
+
commands.handlers.push({
|
|
513
|
+
layer: extension.layer,
|
|
514
|
+
name: extension.name,
|
|
515
|
+
command: normalizedCommand,
|
|
516
|
+
run: commandOrDefinition.run,
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
const registerRenderer = (format, renderer) => {
|
|
520
|
+
assertExtensionCapability(extension, "renderers", "registerRenderer");
|
|
521
|
+
if (typeof renderer !== "function") {
|
|
522
|
+
throw new TypeError("registerRenderer requires a renderer function");
|
|
523
|
+
}
|
|
524
|
+
const normalizedFormat = String(format).trim().toLowerCase();
|
|
525
|
+
if (!isOutputRendererFormat(normalizedFormat)) {
|
|
526
|
+
throw new Error(`registerRenderer format must be toon|json, received: ${String(format)}`);
|
|
527
|
+
}
|
|
528
|
+
renderers.overrides.push({
|
|
529
|
+
layer: extension.layer,
|
|
530
|
+
name: extension.name,
|
|
531
|
+
format: normalizedFormat,
|
|
532
|
+
run: renderer,
|
|
533
|
+
});
|
|
534
|
+
};
|
|
535
|
+
const registerFlags = (targetCommand, flags) => {
|
|
536
|
+
assertExtensionCapability(extension, "schema", "registerFlags");
|
|
537
|
+
const normalizedTargetCommand = normalizeCommandName(assertNonEmptyString("registerFlags targetCommand", targetCommand));
|
|
538
|
+
const normalizedFlags = normalizeRegistrationRecordList("registerFlags flags", flags);
|
|
539
|
+
if (normalizedFlags.length === 0) {
|
|
540
|
+
throw new TypeError("registerFlags requires at least one flag definition");
|
|
541
|
+
}
|
|
542
|
+
registrations.flags.push({
|
|
543
|
+
layer: extension.layer,
|
|
544
|
+
name: extension.name,
|
|
545
|
+
target_command: normalizedTargetCommand,
|
|
546
|
+
flags: normalizedFlags,
|
|
547
|
+
});
|
|
548
|
+
};
|
|
549
|
+
const registerItemFields = (fields) => {
|
|
550
|
+
assertExtensionCapability(extension, "schema", "registerItemFields");
|
|
551
|
+
const normalizedFields = normalizeRegistrationRecordList("registerItemFields fields", fields);
|
|
552
|
+
if (normalizedFields.length === 0) {
|
|
553
|
+
throw new TypeError("registerItemFields requires at least one field definition");
|
|
554
|
+
}
|
|
555
|
+
registrations.item_fields.push({
|
|
556
|
+
layer: extension.layer,
|
|
557
|
+
name: extension.name,
|
|
558
|
+
fields: normalizedFields,
|
|
559
|
+
});
|
|
560
|
+
};
|
|
561
|
+
const registerMigration = (definition) => {
|
|
562
|
+
assertExtensionCapability(extension, "schema", "registerMigration");
|
|
563
|
+
registrations.migrations.push({
|
|
564
|
+
layer: extension.layer,
|
|
565
|
+
name: extension.name,
|
|
566
|
+
definition: normalizeRegistrationRecord("registerMigration definition", definition),
|
|
567
|
+
});
|
|
568
|
+
};
|
|
569
|
+
const registerImporter = (name, importer) => {
|
|
570
|
+
assertExtensionCapability(extension, "importers", "registerImporter");
|
|
571
|
+
const normalizedName = normalizeRegistrationName(assertNonEmptyString("registerImporter name", name));
|
|
572
|
+
assertFunctionHandler("registerImporter importer", importer);
|
|
573
|
+
const commandPath = toRegistrationCommandPath(normalizedName, "import");
|
|
574
|
+
registrations.importers.push({
|
|
575
|
+
layer: extension.layer,
|
|
576
|
+
name: extension.name,
|
|
577
|
+
importer: normalizedName,
|
|
578
|
+
});
|
|
579
|
+
commands.handlers.push({
|
|
580
|
+
layer: extension.layer,
|
|
581
|
+
name: extension.name,
|
|
582
|
+
command: commandPath,
|
|
583
|
+
run: async (context) => importer({
|
|
584
|
+
registration: normalizedName,
|
|
585
|
+
action: "import",
|
|
586
|
+
command: context.command,
|
|
587
|
+
args: cloneContextSnapshot(context.args),
|
|
588
|
+
options: cloneContextSnapshot(context.options),
|
|
589
|
+
global: cloneContextSnapshot(context.global),
|
|
590
|
+
pm_root: context.pm_root,
|
|
591
|
+
}),
|
|
592
|
+
});
|
|
593
|
+
};
|
|
594
|
+
const registerExporter = (name, exporter) => {
|
|
595
|
+
assertExtensionCapability(extension, "importers", "registerExporter");
|
|
596
|
+
const normalizedName = normalizeRegistrationName(assertNonEmptyString("registerExporter name", name));
|
|
597
|
+
assertFunctionHandler("registerExporter exporter", exporter);
|
|
598
|
+
const commandPath = toRegistrationCommandPath(normalizedName, "export");
|
|
599
|
+
registrations.exporters.push({
|
|
600
|
+
layer: extension.layer,
|
|
601
|
+
name: extension.name,
|
|
602
|
+
exporter: normalizedName,
|
|
603
|
+
});
|
|
604
|
+
commands.handlers.push({
|
|
605
|
+
layer: extension.layer,
|
|
606
|
+
name: extension.name,
|
|
607
|
+
command: commandPath,
|
|
608
|
+
run: async (context) => exporter({
|
|
609
|
+
registration: normalizedName,
|
|
610
|
+
action: "export",
|
|
611
|
+
command: context.command,
|
|
612
|
+
args: cloneContextSnapshot(context.args),
|
|
613
|
+
options: cloneContextSnapshot(context.options),
|
|
614
|
+
global: cloneContextSnapshot(context.global),
|
|
615
|
+
pm_root: context.pm_root,
|
|
616
|
+
}),
|
|
617
|
+
});
|
|
618
|
+
};
|
|
619
|
+
const registerSearchProvider = (provider) => {
|
|
620
|
+
assertExtensionCapability(extension, "search", "registerSearchProvider");
|
|
621
|
+
registrations.search_providers.push({
|
|
622
|
+
layer: extension.layer,
|
|
623
|
+
name: extension.name,
|
|
624
|
+
definition: normalizeRegistrationRecord("registerSearchProvider provider", provider),
|
|
625
|
+
});
|
|
626
|
+
};
|
|
627
|
+
const registerVectorStoreAdapter = (adapter) => {
|
|
628
|
+
assertExtensionCapability(extension, "search", "registerVectorStoreAdapter");
|
|
629
|
+
registrations.vector_store_adapters.push({
|
|
630
|
+
layer: extension.layer,
|
|
631
|
+
name: extension.name,
|
|
632
|
+
definition: normalizeRegistrationRecord("registerVectorStoreAdapter adapter", adapter),
|
|
633
|
+
});
|
|
634
|
+
};
|
|
635
|
+
const registerBeforeCommand = (hook) => {
|
|
636
|
+
assertExtensionCapability(extension, "hooks", "api.hooks.beforeCommand");
|
|
637
|
+
assertHookHandler("beforeCommand", hook);
|
|
638
|
+
hooks.beforeCommand.push({
|
|
639
|
+
layer: extension.layer,
|
|
640
|
+
name: extension.name,
|
|
641
|
+
run: hook,
|
|
642
|
+
});
|
|
643
|
+
};
|
|
644
|
+
const registerAfterCommand = (hook) => {
|
|
645
|
+
assertExtensionCapability(extension, "hooks", "api.hooks.afterCommand");
|
|
646
|
+
assertHookHandler("afterCommand", hook);
|
|
647
|
+
hooks.afterCommand.push({
|
|
648
|
+
layer: extension.layer,
|
|
649
|
+
name: extension.name,
|
|
650
|
+
run: hook,
|
|
651
|
+
});
|
|
652
|
+
};
|
|
653
|
+
const registerOnWrite = (hook) => {
|
|
654
|
+
assertExtensionCapability(extension, "hooks", "api.hooks.onWrite");
|
|
655
|
+
assertHookHandler("onWrite", hook);
|
|
656
|
+
hooks.onWrite.push({
|
|
657
|
+
layer: extension.layer,
|
|
658
|
+
name: extension.name,
|
|
659
|
+
run: hook,
|
|
660
|
+
});
|
|
661
|
+
};
|
|
662
|
+
const registerOnRead = (hook) => {
|
|
663
|
+
assertExtensionCapability(extension, "hooks", "api.hooks.onRead");
|
|
664
|
+
assertHookHandler("onRead", hook);
|
|
665
|
+
hooks.onRead.push({
|
|
666
|
+
layer: extension.layer,
|
|
667
|
+
name: extension.name,
|
|
668
|
+
run: hook,
|
|
669
|
+
});
|
|
670
|
+
};
|
|
671
|
+
const registerOnIndex = (hook) => {
|
|
672
|
+
assertExtensionCapability(extension, "hooks", "api.hooks.onIndex");
|
|
673
|
+
assertHookHandler("onIndex", hook);
|
|
674
|
+
hooks.onIndex.push({
|
|
675
|
+
layer: extension.layer,
|
|
676
|
+
name: extension.name,
|
|
677
|
+
run: hook,
|
|
678
|
+
});
|
|
679
|
+
};
|
|
680
|
+
return {
|
|
681
|
+
registerCommand,
|
|
682
|
+
registerFlags,
|
|
683
|
+
registerItemFields,
|
|
684
|
+
registerMigration,
|
|
685
|
+
registerRenderer,
|
|
686
|
+
registerImporter,
|
|
687
|
+
registerExporter,
|
|
688
|
+
registerSearchProvider,
|
|
689
|
+
registerVectorStoreAdapter,
|
|
690
|
+
hooks: {
|
|
691
|
+
beforeCommand: registerBeforeCommand,
|
|
692
|
+
afterCommand: registerAfterCommand,
|
|
693
|
+
onWrite: registerOnWrite,
|
|
694
|
+
onRead: registerOnRead,
|
|
695
|
+
onIndex: registerOnIndex,
|
|
696
|
+
},
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
async function executeRegisteredHooks(entries, hookName, context) {
|
|
700
|
+
const warnings = [];
|
|
701
|
+
for (const entry of entries) {
|
|
702
|
+
try {
|
|
703
|
+
await entry.run(cloneContextSnapshot(context));
|
|
704
|
+
}
|
|
705
|
+
catch {
|
|
706
|
+
warnings.push(`extension_hook_failed:${entry.layer}:${entry.name}:${hookName}`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return warnings;
|
|
710
|
+
}
|
|
711
|
+
function getRegistrationCounts(registrations) {
|
|
712
|
+
const flagCount = registrations.flags.reduce((total, entry) => total + entry.flags.length, 0);
|
|
713
|
+
const itemFieldCount = registrations.item_fields.reduce((total, entry) => total + entry.fields.length, 0);
|
|
714
|
+
return {
|
|
715
|
+
flags: flagCount,
|
|
716
|
+
item_fields: itemFieldCount,
|
|
717
|
+
migrations: registrations.migrations.length,
|
|
718
|
+
importers: registrations.importers.length,
|
|
719
|
+
exporters: registrations.exporters.length,
|
|
720
|
+
search_providers: registrations.search_providers.length,
|
|
721
|
+
vector_store_adapters: registrations.vector_store_adapters.length,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
export async function activateExtensions(loadResult) {
|
|
725
|
+
const hooks = createEmptyExtensionHookRegistry();
|
|
726
|
+
const commands = createEmptyExtensionCommandRegistry();
|
|
727
|
+
const renderers = createEmptyExtensionRendererRegistry();
|
|
728
|
+
const registrations = createEmptyExtensionRegistrationRegistry();
|
|
729
|
+
const failed = [];
|
|
730
|
+
const warnings = [];
|
|
731
|
+
for (const extension of loadResult.loaded) {
|
|
732
|
+
const activatable = resolveActivatableExtension(extension.module);
|
|
733
|
+
if (!activatable) {
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
try {
|
|
737
|
+
await activatable.activate(createExtensionApi(extension, hooks, commands, renderers, registrations));
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
warnings.push(`extension_activate_failed:${extension.layer}:${extension.name}`);
|
|
741
|
+
failed.push({
|
|
742
|
+
layer: extension.layer,
|
|
743
|
+
name: extension.name,
|
|
744
|
+
entry_path: extension.entry_path,
|
|
745
|
+
error: formatUnknownError(error),
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
hooks,
|
|
751
|
+
commands,
|
|
752
|
+
renderers,
|
|
753
|
+
registrations,
|
|
754
|
+
failed,
|
|
755
|
+
warnings,
|
|
756
|
+
hook_counts: {
|
|
757
|
+
before_command: hooks.beforeCommand.length,
|
|
758
|
+
after_command: hooks.afterCommand.length,
|
|
759
|
+
on_write: hooks.onWrite.length,
|
|
760
|
+
on_read: hooks.onRead.length,
|
|
761
|
+
on_index: hooks.onIndex.length,
|
|
762
|
+
},
|
|
763
|
+
command_override_count: commands.overrides.length,
|
|
764
|
+
command_handler_count: commands.handlers.length,
|
|
765
|
+
renderer_override_count: renderers.overrides.length,
|
|
766
|
+
registration_counts: getRegistrationCounts(registrations),
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
export async function runBeforeCommandHooks(hooks, context) {
|
|
770
|
+
return executeRegisteredHooks(hooks.beforeCommand, "beforeCommand", context);
|
|
771
|
+
}
|
|
772
|
+
export async function runAfterCommandHooks(hooks, context) {
|
|
773
|
+
return executeRegisteredHooks(hooks.afterCommand, "afterCommand", context);
|
|
774
|
+
}
|
|
775
|
+
export async function runOnWriteHooks(hooks, context) {
|
|
776
|
+
return executeRegisteredHooks(hooks.onWrite, "onWrite", context);
|
|
777
|
+
}
|
|
778
|
+
export async function runOnReadHooks(hooks, context) {
|
|
779
|
+
return executeRegisteredHooks(hooks.onRead, "onRead", context);
|
|
780
|
+
}
|
|
781
|
+
export async function runOnIndexHooks(hooks, context) {
|
|
782
|
+
return executeRegisteredHooks(hooks.onIndex, "onIndex", context);
|
|
783
|
+
}
|
|
784
|
+
export async function runCommandHandler(commands, context) {
|
|
785
|
+
const command = normalizeCommandName(context.command);
|
|
786
|
+
if (command.length === 0) {
|
|
787
|
+
return {
|
|
788
|
+
handled: false,
|
|
789
|
+
result: null,
|
|
790
|
+
warnings: [],
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
const matched = [...commands.handlers].reverse().find((entry) => entry.command === command);
|
|
794
|
+
if (!matched) {
|
|
795
|
+
return {
|
|
796
|
+
handled: false,
|
|
797
|
+
result: null,
|
|
798
|
+
warnings: [],
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
try {
|
|
802
|
+
const result = await matched.run({
|
|
803
|
+
command,
|
|
804
|
+
args: cloneContextSnapshot(context.args),
|
|
805
|
+
options: cloneContextSnapshot(context.options),
|
|
806
|
+
global: cloneContextSnapshot(context.global),
|
|
807
|
+
pm_root: context.pm_root,
|
|
808
|
+
});
|
|
809
|
+
return {
|
|
810
|
+
handled: true,
|
|
811
|
+
result,
|
|
812
|
+
warnings: [],
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
catch {
|
|
816
|
+
return {
|
|
817
|
+
handled: false,
|
|
818
|
+
result: null,
|
|
819
|
+
warnings: [`extension_command_handler_failed:${matched.layer}:${matched.name}:${matched.command}`],
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
export function runCommandOverride(commands, context) {
|
|
824
|
+
const command = normalizeCommandName(context.command);
|
|
825
|
+
if (command.length === 0) {
|
|
826
|
+
return {
|
|
827
|
+
overridden: false,
|
|
828
|
+
result: context.result,
|
|
829
|
+
warnings: [],
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
const matched = [...commands.overrides].reverse().find((entry) => entry.command === command);
|
|
833
|
+
if (!matched) {
|
|
834
|
+
return {
|
|
835
|
+
overridden: false,
|
|
836
|
+
result: context.result,
|
|
837
|
+
warnings: [],
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
try {
|
|
841
|
+
const overrideOptions = cloneCommandOptionsSnapshot(context.options);
|
|
842
|
+
const overrideGlobal = cloneGlobalOptionsSnapshot(context.global);
|
|
843
|
+
const overrideResult = matched.run({
|
|
844
|
+
command,
|
|
845
|
+
args: cloneContextSnapshot(context.args),
|
|
846
|
+
options: overrideOptions,
|
|
847
|
+
global: overrideGlobal,
|
|
848
|
+
pm_root: context.pm_root,
|
|
849
|
+
result: cloneContextSnapshot(context.result),
|
|
850
|
+
});
|
|
851
|
+
if (overrideResult instanceof Promise) {
|
|
852
|
+
return {
|
|
853
|
+
overridden: false,
|
|
854
|
+
result: context.result,
|
|
855
|
+
warnings: [`extension_command_override_async_unsupported:${matched.layer}:${matched.name}:${matched.command}`],
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
return {
|
|
859
|
+
overridden: true,
|
|
860
|
+
result: overrideResult,
|
|
861
|
+
warnings: [],
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
catch {
|
|
865
|
+
return {
|
|
866
|
+
overridden: false,
|
|
867
|
+
result: context.result,
|
|
868
|
+
warnings: [`extension_command_override_failed:${matched.layer}:${matched.name}:${matched.command}`],
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
export function runRendererOverride(renderers, context) {
|
|
873
|
+
const matched = [...renderers.overrides].reverse().find((entry) => entry.format === context.format);
|
|
874
|
+
if (!matched) {
|
|
875
|
+
return {
|
|
876
|
+
overridden: false,
|
|
877
|
+
rendered: null,
|
|
878
|
+
warnings: [],
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
try {
|
|
882
|
+
const rendererCommand = typeof context.command === "string" ? normalizeCommandName(context.command) : "";
|
|
883
|
+
const rendererArgs = Array.isArray(context.args) ? cloneContextSnapshot(context.args) : [];
|
|
884
|
+
const rendererOptions = cloneCommandOptionsSnapshot(context.options);
|
|
885
|
+
const rendererGlobal = cloneGlobalOptionsSnapshot(context.global);
|
|
886
|
+
const rendererPmRoot = typeof context.pm_root === "string" ? context.pm_root : "";
|
|
887
|
+
const rendered = matched.run({
|
|
888
|
+
format: context.format,
|
|
889
|
+
command: rendererCommand,
|
|
890
|
+
args: rendererArgs,
|
|
891
|
+
options: rendererOptions,
|
|
892
|
+
global: rendererGlobal,
|
|
893
|
+
pm_root: rendererPmRoot,
|
|
894
|
+
result: cloneContextSnapshot(context.result),
|
|
895
|
+
});
|
|
896
|
+
if (typeof rendered !== "string") {
|
|
897
|
+
return {
|
|
898
|
+
overridden: false,
|
|
899
|
+
rendered: null,
|
|
900
|
+
warnings: [`extension_renderer_invalid_result:${matched.layer}:${matched.name}:${matched.format}`],
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
overridden: true,
|
|
905
|
+
rendered,
|
|
906
|
+
warnings: [],
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
catch {
|
|
910
|
+
return {
|
|
911
|
+
overridden: false,
|
|
912
|
+
rendered: null,
|
|
913
|
+
warnings: [`extension_renderer_failed:${matched.layer}:${matched.name}:${matched.format}`],
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
//# sourceMappingURL=loader.js.map
|