@a16njs/engine 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +45 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +84 -19
- package/dist/index.js.map +1 -1
- package/dist/path-rewriter.d.ts +68 -0
- package/dist/path-rewriter.d.ts.map +1 -0
- package/dist/path-rewriter.js +144 -0
- package/dist/path-rewriter.js.map +1 -0
- package/dist/plugin-discovery.d.ts +64 -0
- package/dist/plugin-discovery.d.ts.map +1 -0
- package/dist/plugin-discovery.js +161 -0
- package/dist/plugin-discovery.js.map +1 -0
- package/dist/plugin-loader.d.ts +96 -0
- package/dist/plugin-loader.d.ts.map +1 -0
- package/dist/plugin-loader.js +112 -0
- package/dist/plugin-loader.js.map +1 -0
- package/dist/plugin-registry.d.ts +94 -0
- package/dist/plugin-registry.d.ts.map +1 -0
- package/dist/plugin-registry.js +89 -0
- package/dist/plugin-registry.js.map +1 -0
- package/dist/transformation.d.ts +93 -0
- package/dist/transformation.d.ts.map +1 -0
- package/dist/transformation.js +56 -0
- package/dist/transformation.js.map +1 -0
- package/dist/workspace.d.ts +73 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +178 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import { statSync } from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { pathToFileURL, fileURLToPath } from 'url';
|
|
5
|
+
/** Pattern used to match plugin package directory names. */
|
|
6
|
+
const PLUGIN_PREFIX = 'a16n-plugin-';
|
|
7
|
+
/**
|
|
8
|
+
* Scan search paths for installed `a16n-plugin-*` packages, dynamically import
|
|
9
|
+
* each one, validate its default export, and return the valid plugins along
|
|
10
|
+
* with error info for any that failed.
|
|
11
|
+
*
|
|
12
|
+
* @param options - Optional search path overrides
|
|
13
|
+
* @returns Discovered plugins and any load errors
|
|
14
|
+
*/
|
|
15
|
+
export async function discoverInstalledPlugins(options) {
|
|
16
|
+
const searchPaths = options?.searchPaths ?? getDefaultSearchPaths();
|
|
17
|
+
const plugins = [];
|
|
18
|
+
const errors = [];
|
|
19
|
+
const seenPluginIds = new Set();
|
|
20
|
+
for (const searchPath of searchPaths) {
|
|
21
|
+
// Read directory entries; skip if path doesn't exist
|
|
22
|
+
let entries;
|
|
23
|
+
try {
|
|
24
|
+
entries = await fs.readdir(searchPath);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Non-existent or unreadable path — skip silently
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// Filter for directories matching the plugin naming convention
|
|
31
|
+
const pluginDirs = entries.filter((name) => name.startsWith(PLUGIN_PREFIX));
|
|
32
|
+
for (const dirName of pluginDirs) {
|
|
33
|
+
const pkgPath = path.join(searchPath, dirName);
|
|
34
|
+
try {
|
|
35
|
+
// Resolve entry point from package.json main field, falling back to index.js
|
|
36
|
+
const entryFile = await resolvePluginEntry(pkgPath);
|
|
37
|
+
const moduleUrl = pathToFileURL(entryFile).href;
|
|
38
|
+
const mod = await import(moduleUrl);
|
|
39
|
+
// Extract default export (handles both `export default` and CJS interop)
|
|
40
|
+
const candidate = mod.default ?? mod;
|
|
41
|
+
if (isValidPlugin(candidate)) {
|
|
42
|
+
if (seenPluginIds.has(candidate.id)) {
|
|
43
|
+
errors.push({
|
|
44
|
+
packageName: dirName,
|
|
45
|
+
error: `Duplicate plugin id: ${candidate.id} — already discovered`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
seenPluginIds.add(candidate.id);
|
|
50
|
+
plugins.push(candidate);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
errors.push({
|
|
55
|
+
packageName: dirName,
|
|
56
|
+
error: `Invalid plugin export: missing or incorrect required fields (id, name, supports, discover, emit)`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
errors.push({
|
|
62
|
+
packageName: dirName,
|
|
63
|
+
error: err instanceof Error ? err.message : String(err),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { plugins, errors };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the JavaScript entry point for a plugin package directory.
|
|
72
|
+
*
|
|
73
|
+
* Reads the `main` field from the package's `package.json` if it exists,
|
|
74
|
+
* otherwise falls back to `index.js` at the package root.
|
|
75
|
+
*
|
|
76
|
+
* @param pkgPath - Absolute path to the plugin package directory
|
|
77
|
+
* @returns Absolute path to the entry JavaScript file
|
|
78
|
+
*/
|
|
79
|
+
async function resolvePluginEntry(pkgPath) {
|
|
80
|
+
try {
|
|
81
|
+
const raw = await fs.readFile(path.join(pkgPath, 'package.json'), 'utf-8');
|
|
82
|
+
const pkg = JSON.parse(raw);
|
|
83
|
+
if (typeof pkg.main === 'string' && pkg.main.length > 0) {
|
|
84
|
+
return path.resolve(pkgPath, pkg.main);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// No package.json or unreadable — fall through to default
|
|
89
|
+
}
|
|
90
|
+
return path.join(pkgPath, 'index.js');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Type-guard that checks whether an unknown value satisfies the A16nPlugin interface.
|
|
94
|
+
*
|
|
95
|
+
* Validates presence and types of: id (string), name (string), supports (array),
|
|
96
|
+
* discover (function), emit (function).
|
|
97
|
+
*
|
|
98
|
+
* @param obj - The value to check
|
|
99
|
+
* @returns True if obj is a valid A16nPlugin
|
|
100
|
+
*/
|
|
101
|
+
export function isValidPlugin(obj) {
|
|
102
|
+
if (obj == null || typeof obj !== 'object') {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const candidate = obj;
|
|
106
|
+
return (typeof candidate.id === 'string' &&
|
|
107
|
+
typeof candidate.name === 'string' &&
|
|
108
|
+
Array.isArray(candidate.supports) &&
|
|
109
|
+
typeof candidate.discover === 'function' &&
|
|
110
|
+
typeof candidate.emit === 'function');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Compute the default search paths for plugin discovery.
|
|
114
|
+
*
|
|
115
|
+
* Returns paths where `a16n-plugin-*` packages might be installed:
|
|
116
|
+
* - The global npm `node_modules` directory (derived from this package's location)
|
|
117
|
+
* - The local `node_modules` in the current working directory
|
|
118
|
+
*
|
|
119
|
+
* In a global install, the engine lives inside node_modules:
|
|
120
|
+
* .../lib/node_modules/@a16njs/engine/dist/plugin-discovery.js
|
|
121
|
+
* so walking up finds the node_modules parent directly.
|
|
122
|
+
*
|
|
123
|
+
* In a monorepo (e.g. pnpm workspace), the engine lives at:
|
|
124
|
+
* <root>/packages/engine/dist/plugin-discovery.js
|
|
125
|
+
* so we also check for a node_modules child directory at each level.
|
|
126
|
+
*
|
|
127
|
+
* @returns Array of directory paths to scan
|
|
128
|
+
*/
|
|
129
|
+
export function getDefaultSearchPaths() {
|
|
130
|
+
const paths = [];
|
|
131
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
132
|
+
let dir = path.dirname(thisFile);
|
|
133
|
+
while (dir !== path.dirname(dir)) {
|
|
134
|
+
// Global install: this file is inside a node_modules tree
|
|
135
|
+
if (path.basename(dir) === 'node_modules') {
|
|
136
|
+
paths.push(dir);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
// Monorepo / local dev: check for a sibling node_modules directory.
|
|
140
|
+
// Don't stop — keep walking up to find all ancestor node_modules
|
|
141
|
+
// (e.g. packages/engine/node_modules AND root/node_modules).
|
|
142
|
+
const siblingNodeModules = path.join(dir, 'node_modules');
|
|
143
|
+
try {
|
|
144
|
+
const stat = statSync(siblingNodeModules);
|
|
145
|
+
if (stat.isDirectory()) {
|
|
146
|
+
paths.push(siblingNodeModules);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Directory doesn't exist, keep walking
|
|
151
|
+
}
|
|
152
|
+
dir = path.dirname(dir);
|
|
153
|
+
}
|
|
154
|
+
// Local node_modules in cwd
|
|
155
|
+
const localNodeModules = path.join(process.cwd(), 'node_modules');
|
|
156
|
+
if (!paths.includes(localNodeModules)) {
|
|
157
|
+
paths.push(localNodeModules);
|
|
158
|
+
}
|
|
159
|
+
return paths;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=plugin-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-discovery.js","sourceRoot":"","sources":["../src/plugin-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { A16nPlugin } from '@a16njs/models';
|
|
2
|
+
import type { PluginRegistrationInput } from './plugin-registry.js';
|
|
3
|
+
import { PluginRegistry } from './plugin-registry.js';
|
|
4
|
+
import { type PluginDiscoveryOptions } from './plugin-discovery.js';
|
|
5
|
+
/**
|
|
6
|
+
* Strategy for resolving conflicts when a discovered plugin has the
|
|
7
|
+
* same ID as an already-registered plugin.
|
|
8
|
+
*/
|
|
9
|
+
export declare enum PluginConflictStrategy {
|
|
10
|
+
/** Keep the existing (bundled) plugin, skip the discovered one. This is the default and matches current behavior. */
|
|
11
|
+
PREFER_BUNDLED = "prefer-bundled",
|
|
12
|
+
/** Replace the existing plugin with the discovered (installed) one. */
|
|
13
|
+
PREFER_INSTALLED = "prefer-installed",
|
|
14
|
+
/** Throw an error when a conflict is detected. */
|
|
15
|
+
FAIL = "fail"
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Information about a plugin that was skipped during conflict resolution.
|
|
19
|
+
*/
|
|
20
|
+
export interface SkippedPlugin {
|
|
21
|
+
/** The plugin that was skipped */
|
|
22
|
+
plugin: A16nPlugin;
|
|
23
|
+
/** Human-readable reason for skipping */
|
|
24
|
+
reason: string;
|
|
25
|
+
/** ID of the plugin it conflicted with */
|
|
26
|
+
conflictsWith: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Result of loading and resolving plugins.
|
|
30
|
+
*/
|
|
31
|
+
export interface PluginLoadResult {
|
|
32
|
+
/** Plugins that passed conflict resolution and are ready for registration */
|
|
33
|
+
loaded: PluginRegistrationInput[];
|
|
34
|
+
/** Plugins that were skipped due to conflicts */
|
|
35
|
+
skipped: SkippedPlugin[];
|
|
36
|
+
/** Errors encountered during discovery */
|
|
37
|
+
errors: Array<{
|
|
38
|
+
packageName: string;
|
|
39
|
+
error: string;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Orchestrates plugin loading by coordinating discovery and conflict
|
|
44
|
+
* resolution as separate, testable phases.
|
|
45
|
+
*
|
|
46
|
+
* Separates three distinct concerns:
|
|
47
|
+
* 1. **Discovery** - Finding plugins in node_modules (delegated to discoverInstalledPlugins)
|
|
48
|
+
* 2. **Conflict Resolution** - Deciding what to do when discovered plugins conflict with existing ones
|
|
49
|
+
* 3. **Registration** - Adding resolved plugins to the registry (done by the caller)
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const loader = new PluginLoader(PluginConflictStrategy.PREFER_BUNDLED);
|
|
54
|
+
* const candidates = await loader.loadInstalled({ searchPaths: ['/path/to/node_modules'] });
|
|
55
|
+
* const resolved = loader.resolveConflicts(registry, candidates);
|
|
56
|
+
* for (const reg of resolved.loaded) {
|
|
57
|
+
* registry.register(reg);
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare class PluginLoader {
|
|
62
|
+
private readonly _conflictStrategy;
|
|
63
|
+
/**
|
|
64
|
+
* Create a new PluginLoader with the given conflict resolution strategy.
|
|
65
|
+
*
|
|
66
|
+
* @param conflictStrategy - How to handle plugin ID conflicts (default: PREFER_BUNDLED)
|
|
67
|
+
*/
|
|
68
|
+
constructor(_conflictStrategy?: PluginConflictStrategy);
|
|
69
|
+
/**
|
|
70
|
+
* The conflict resolution strategy in use.
|
|
71
|
+
*/
|
|
72
|
+
get conflictStrategy(): PluginConflictStrategy;
|
|
73
|
+
/**
|
|
74
|
+
* Discover installed plugins from node_modules and wrap them
|
|
75
|
+
* as PluginRegistrationInput candidates with source='installed'.
|
|
76
|
+
*
|
|
77
|
+
* This is Phase 1 (Discovery) of the loading pipeline.
|
|
78
|
+
*
|
|
79
|
+
* @param options - Plugin discovery options (search paths, etc.)
|
|
80
|
+
* @returns Load result with candidates ready for conflict resolution
|
|
81
|
+
*/
|
|
82
|
+
loadInstalled(options?: PluginDiscoveryOptions): Promise<PluginLoadResult>;
|
|
83
|
+
/**
|
|
84
|
+
* Resolve conflicts between discovered plugin candidates and
|
|
85
|
+
* already-registered plugins using the configured strategy.
|
|
86
|
+
*
|
|
87
|
+
* This is Phase 2 (Conflict Resolution) of the loading pipeline.
|
|
88
|
+
* Pure function: does not modify the registry.
|
|
89
|
+
*
|
|
90
|
+
* @param existing - The current plugin registry to check for conflicts
|
|
91
|
+
* @param candidates - The load result from loadInstalled()
|
|
92
|
+
* @returns Resolved load result with loaded (ready to register) and skipped plugins
|
|
93
|
+
*/
|
|
94
|
+
resolveConflicts(existing: PluginRegistry, candidates: PluginLoadResult): PluginLoadResult;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=plugin-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-loader.d.ts","sourceRoot":"","sources":["../src/plugin-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAsB,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,oBAAY,sBAAsB;IAChC,qHAAqH;IACrH,cAAc,mBAAmB;IACjC,uEAAuE;IACvE,gBAAgB,qBAAqB;IACrC,kDAAkD;IAClD,IAAI,SAAS;CACd;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,MAAM,EAAE,UAAU,CAAC;IACnB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,MAAM,EAAE,uBAAuB,EAAE,CAAC;IAClC,iDAAiD;IACjD,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,0CAA0C;IAC1C,MAAM,EAAE,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,YAAY;IAOrB,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IANpC;;;;OAIG;gBAEgB,iBAAiB,GAAE,sBAA8D;IAGpG;;OAEG;IACH,IAAI,gBAAgB,IAAI,sBAAsB,CAE7C;IAED;;;;;;;;OAQG;IACG,aAAa,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAahF;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE,gBAAgB,GAAG,gBAAgB;CAoC3F"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { discoverInstalledPlugins, } from './plugin-discovery.js';
|
|
2
|
+
/**
|
|
3
|
+
* Strategy for resolving conflicts when a discovered plugin has the
|
|
4
|
+
* same ID as an already-registered plugin.
|
|
5
|
+
*/
|
|
6
|
+
export var PluginConflictStrategy;
|
|
7
|
+
(function (PluginConflictStrategy) {
|
|
8
|
+
/** Keep the existing (bundled) plugin, skip the discovered one. This is the default and matches current behavior. */
|
|
9
|
+
PluginConflictStrategy["PREFER_BUNDLED"] = "prefer-bundled";
|
|
10
|
+
/** Replace the existing plugin with the discovered (installed) one. */
|
|
11
|
+
PluginConflictStrategy["PREFER_INSTALLED"] = "prefer-installed";
|
|
12
|
+
/** Throw an error when a conflict is detected. */
|
|
13
|
+
PluginConflictStrategy["FAIL"] = "fail";
|
|
14
|
+
})(PluginConflictStrategy || (PluginConflictStrategy = {}));
|
|
15
|
+
/**
|
|
16
|
+
* Orchestrates plugin loading by coordinating discovery and conflict
|
|
17
|
+
* resolution as separate, testable phases.
|
|
18
|
+
*
|
|
19
|
+
* Separates three distinct concerns:
|
|
20
|
+
* 1. **Discovery** - Finding plugins in node_modules (delegated to discoverInstalledPlugins)
|
|
21
|
+
* 2. **Conflict Resolution** - Deciding what to do when discovered plugins conflict with existing ones
|
|
22
|
+
* 3. **Registration** - Adding resolved plugins to the registry (done by the caller)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const loader = new PluginLoader(PluginConflictStrategy.PREFER_BUNDLED);
|
|
27
|
+
* const candidates = await loader.loadInstalled({ searchPaths: ['/path/to/node_modules'] });
|
|
28
|
+
* const resolved = loader.resolveConflicts(registry, candidates);
|
|
29
|
+
* for (const reg of resolved.loaded) {
|
|
30
|
+
* registry.register(reg);
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class PluginLoader {
|
|
35
|
+
_conflictStrategy;
|
|
36
|
+
/**
|
|
37
|
+
* Create a new PluginLoader with the given conflict resolution strategy.
|
|
38
|
+
*
|
|
39
|
+
* @param conflictStrategy - How to handle plugin ID conflicts (default: PREFER_BUNDLED)
|
|
40
|
+
*/
|
|
41
|
+
constructor(_conflictStrategy = PluginConflictStrategy.PREFER_BUNDLED) {
|
|
42
|
+
this._conflictStrategy = _conflictStrategy;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The conflict resolution strategy in use.
|
|
46
|
+
*/
|
|
47
|
+
get conflictStrategy() {
|
|
48
|
+
return this._conflictStrategy;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Discover installed plugins from node_modules and wrap them
|
|
52
|
+
* as PluginRegistrationInput candidates with source='installed'.
|
|
53
|
+
*
|
|
54
|
+
* This is Phase 1 (Discovery) of the loading pipeline.
|
|
55
|
+
*
|
|
56
|
+
* @param options - Plugin discovery options (search paths, etc.)
|
|
57
|
+
* @returns Load result with candidates ready for conflict resolution
|
|
58
|
+
*/
|
|
59
|
+
async loadInstalled(options) {
|
|
60
|
+
const discovered = await discoverInstalledPlugins(options);
|
|
61
|
+
return {
|
|
62
|
+
loaded: discovered.plugins.map((plugin) => ({
|
|
63
|
+
plugin,
|
|
64
|
+
source: 'installed',
|
|
65
|
+
})),
|
|
66
|
+
skipped: [],
|
|
67
|
+
errors: discovered.errors,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve conflicts between discovered plugin candidates and
|
|
72
|
+
* already-registered plugins using the configured strategy.
|
|
73
|
+
*
|
|
74
|
+
* This is Phase 2 (Conflict Resolution) of the loading pipeline.
|
|
75
|
+
* Pure function: does not modify the registry.
|
|
76
|
+
*
|
|
77
|
+
* @param existing - The current plugin registry to check for conflicts
|
|
78
|
+
* @param candidates - The load result from loadInstalled()
|
|
79
|
+
* @returns Resolved load result with loaded (ready to register) and skipped plugins
|
|
80
|
+
*/
|
|
81
|
+
resolveConflicts(existing, candidates) {
|
|
82
|
+
const loaded = [];
|
|
83
|
+
const skipped = [...candidates.skipped];
|
|
84
|
+
for (const candidate of candidates.loaded) {
|
|
85
|
+
const existingReg = existing.get(candidate.plugin.id);
|
|
86
|
+
if (!existingReg) {
|
|
87
|
+
loaded.push(candidate);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
switch (this._conflictStrategy) {
|
|
91
|
+
case PluginConflictStrategy.PREFER_BUNDLED:
|
|
92
|
+
skipped.push({
|
|
93
|
+
plugin: candidate.plugin,
|
|
94
|
+
reason: `Conflict: ${existingReg.source} plugin '${existingReg.plugin.id}' already registered`,
|
|
95
|
+
conflictsWith: candidate.plugin.id,
|
|
96
|
+
});
|
|
97
|
+
break;
|
|
98
|
+
case PluginConflictStrategy.PREFER_INSTALLED:
|
|
99
|
+
loaded.push(candidate);
|
|
100
|
+
break;
|
|
101
|
+
case PluginConflictStrategy.FAIL:
|
|
102
|
+
throw new Error(`Plugin conflict: '${candidate.plugin.id}' is already registered`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
loaded,
|
|
107
|
+
skipped,
|
|
108
|
+
errors: candidates.errors,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=plugin-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-loader.js","sourceRoot":"","sources":["../src/plugin-loader.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,wBAAwB,GAEzB,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,MAAM,CAAN,IAAY,sBAOX;AAPD,WAAY,sBAAsB;IAChC,qHAAqH;IACrH,2DAAiC,CAAA;IACjC,uEAAuE;IACvE,+DAAqC,CAAA;IACrC,kDAAkD;IAClD,uCAAa,CAAA;AACf,CAAC,EAPW,sBAAsB,KAAtB,sBAAsB,QAOjC;AA0BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,YAAY;IAOJ;IANnB;;;;OAIG;IACH,YACmB,oBAA4C,sBAAsB,CAAC,cAAc;QAAjF,sBAAiB,GAAjB,iBAAiB,CAAgE;IACjG,CAAC;IAEJ;;OAEG;IACH,IAAI,gBAAgB;QAClB,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAChC,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,aAAa,CAAC,OAAgC;QAClD,MAAM,UAAU,GAAG,MAAM,wBAAwB,CAAC,OAAO,CAAC,CAAC;QAE3D,OAAO;YACL,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAC1C,MAAM;gBACN,MAAM,EAAE,WAAoB;aAC7B,CAAC,CAAC;YACH,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC;IACJ,CAAC;IAED;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,QAAwB,EAAE,UAA4B;QACrE,MAAM,MAAM,GAA8B,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAoB,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QAEzD,KAAK,MAAM,SAAS,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YAC1C,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAEtD,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACvB,SAAS;YACX,CAAC;YAED,QAAQ,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBAC/B,KAAK,sBAAsB,CAAC,cAAc;oBACxC,OAAO,CAAC,IAAI,CAAC;wBACX,MAAM,EAAE,SAAS,CAAC,MAAM;wBACxB,MAAM,EAAE,aAAa,WAAW,CAAC,MAAM,YAAY,WAAW,CAAC,MAAM,CAAC,EAAE,sBAAsB;wBAC9F,aAAa,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE;qBACnC,CAAC,CAAC;oBACH,MAAM;gBAER,KAAK,sBAAsB,CAAC,gBAAgB;oBAC1C,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBACvB,MAAM;gBAER,KAAK,sBAAsB,CAAC,IAAI;oBAC9B,MAAM,IAAI,KAAK,CAAC,qBAAqB,SAAS,CAAC,MAAM,CAAC,EAAE,yBAAyB,CAAC,CAAC;YACvF,CAAC;QACH,CAAC;QAED,OAAO;YACL,MAAM;YACN,OAAO;YACP,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { A16nPlugin } from '@a16njs/models';
|
|
2
|
+
/**
|
|
3
|
+
* Full metadata for a registered plugin.
|
|
4
|
+
* Wraps the plugin instance with registration metadata such as
|
|
5
|
+
* source (bundled vs installed), registration timestamp, and
|
|
6
|
+
* optional diagnostic information.
|
|
7
|
+
*/
|
|
8
|
+
export interface PluginRegistration {
|
|
9
|
+
/** The plugin instance */
|
|
10
|
+
plugin: A16nPlugin;
|
|
11
|
+
/** Whether this plugin was bundled with the engine or installed from node_modules */
|
|
12
|
+
source: 'bundled' | 'installed';
|
|
13
|
+
/** When this plugin was registered */
|
|
14
|
+
registeredAt: Date;
|
|
15
|
+
/** Semantic version of the installed plugin (for installed plugins) */
|
|
16
|
+
version?: string;
|
|
17
|
+
/** Filesystem path where the plugin was installed from (for diagnostics) */
|
|
18
|
+
installPath?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Input for registering a plugin. Omits `registeredAt` which is
|
|
22
|
+
* automatically set by the registry.
|
|
23
|
+
*/
|
|
24
|
+
export type PluginRegistrationInput = Omit<PluginRegistration, 'registeredAt'>;
|
|
25
|
+
/**
|
|
26
|
+
* Unified plugin registry that serves as the single source of truth
|
|
27
|
+
* for all plugin metadata. Replaces the dual-Map pattern
|
|
28
|
+
* (plugins Map + pluginSources Map) with a single registry that
|
|
29
|
+
* tracks all plugin information in one place.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const registry = new PluginRegistry();
|
|
34
|
+
* registry.register({ plugin: cursorPlugin, source: 'bundled' });
|
|
35
|
+
* registry.register({ plugin: claudePlugin, source: 'installed', version: '1.0.0' });
|
|
36
|
+
*
|
|
37
|
+
* const cursor = registry.getPlugin('cursor');
|
|
38
|
+
* const installed = registry.listBySource('installed');
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare class PluginRegistry {
|
|
42
|
+
private registrations;
|
|
43
|
+
/**
|
|
44
|
+
* Register a plugin with the registry.
|
|
45
|
+
* If a plugin with the same ID is already registered, it will be overwritten.
|
|
46
|
+
*
|
|
47
|
+
* @param input - Plugin and metadata to register (registeredAt is set automatically)
|
|
48
|
+
*/
|
|
49
|
+
register(input: PluginRegistrationInput): void;
|
|
50
|
+
/**
|
|
51
|
+
* Get the full registration for a plugin by its ID.
|
|
52
|
+
*
|
|
53
|
+
* @param id - The plugin ID to look up
|
|
54
|
+
* @returns The full PluginRegistration, or undefined if not found
|
|
55
|
+
*/
|
|
56
|
+
get(id: string): PluginRegistration | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Get just the plugin instance by its ID.
|
|
59
|
+
* Convenience method equivalent to `registry.get(id)?.plugin`.
|
|
60
|
+
*
|
|
61
|
+
* @param id - The plugin ID to look up
|
|
62
|
+
* @returns The A16nPlugin instance, or undefined if not found
|
|
63
|
+
*/
|
|
64
|
+
getPlugin(id: string): A16nPlugin | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Check whether a plugin with the given ID is registered.
|
|
67
|
+
*
|
|
68
|
+
* @param id - The plugin ID to check
|
|
69
|
+
* @returns true if the plugin is registered, false otherwise
|
|
70
|
+
*/
|
|
71
|
+
has(id: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* List all plugin registrations.
|
|
74
|
+
*
|
|
75
|
+
* @returns Array of all PluginRegistration objects in insertion order
|
|
76
|
+
*/
|
|
77
|
+
list(): PluginRegistration[];
|
|
78
|
+
/**
|
|
79
|
+
* List plugin registrations filtered by source.
|
|
80
|
+
*
|
|
81
|
+
* @param source - The source to filter by ('bundled' or 'installed')
|
|
82
|
+
* @returns Array of matching PluginRegistration objects
|
|
83
|
+
*/
|
|
84
|
+
listBySource(source: 'bundled' | 'installed'): PluginRegistration[];
|
|
85
|
+
/**
|
|
86
|
+
* The number of registered plugins.
|
|
87
|
+
*/
|
|
88
|
+
get size(): number;
|
|
89
|
+
/**
|
|
90
|
+
* Remove all plugin registrations.
|
|
91
|
+
*/
|
|
92
|
+
clear(): void;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=plugin-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-registry.d.ts","sourceRoot":"","sources":["../src/plugin-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,qFAAqF;IACrF,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;IAChC,sCAAsC;IACtC,YAAY,EAAE,IAAI,CAAC;IACnB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC;AAE/E;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,aAAa,CAA8C;IAEnE;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI;IAO9C;;;;;OAKG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAI/C;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI7C;;;;;OAKG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB;;;;OAIG;IACH,IAAI,IAAI,kBAAkB,EAAE;IAI5B;;;;;OAKG;IACH,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,kBAAkB,EAAE;IAInE;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified plugin registry that serves as the single source of truth
|
|
3
|
+
* for all plugin metadata. Replaces the dual-Map pattern
|
|
4
|
+
* (plugins Map + pluginSources Map) with a single registry that
|
|
5
|
+
* tracks all plugin information in one place.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const registry = new PluginRegistry();
|
|
10
|
+
* registry.register({ plugin: cursorPlugin, source: 'bundled' });
|
|
11
|
+
* registry.register({ plugin: claudePlugin, source: 'installed', version: '1.0.0' });
|
|
12
|
+
*
|
|
13
|
+
* const cursor = registry.getPlugin('cursor');
|
|
14
|
+
* const installed = registry.listBySource('installed');
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export class PluginRegistry {
|
|
18
|
+
registrations = new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Register a plugin with the registry.
|
|
21
|
+
* If a plugin with the same ID is already registered, it will be overwritten.
|
|
22
|
+
*
|
|
23
|
+
* @param input - Plugin and metadata to register (registeredAt is set automatically)
|
|
24
|
+
*/
|
|
25
|
+
register(input) {
|
|
26
|
+
this.registrations.set(input.plugin.id, {
|
|
27
|
+
...input,
|
|
28
|
+
registeredAt: new Date(),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get the full registration for a plugin by its ID.
|
|
33
|
+
*
|
|
34
|
+
* @param id - The plugin ID to look up
|
|
35
|
+
* @returns The full PluginRegistration, or undefined if not found
|
|
36
|
+
*/
|
|
37
|
+
get(id) {
|
|
38
|
+
return this.registrations.get(id);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get just the plugin instance by its ID.
|
|
42
|
+
* Convenience method equivalent to `registry.get(id)?.plugin`.
|
|
43
|
+
*
|
|
44
|
+
* @param id - The plugin ID to look up
|
|
45
|
+
* @returns The A16nPlugin instance, or undefined if not found
|
|
46
|
+
*/
|
|
47
|
+
getPlugin(id) {
|
|
48
|
+
return this.registrations.get(id)?.plugin;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check whether a plugin with the given ID is registered.
|
|
52
|
+
*
|
|
53
|
+
* @param id - The plugin ID to check
|
|
54
|
+
* @returns true if the plugin is registered, false otherwise
|
|
55
|
+
*/
|
|
56
|
+
has(id) {
|
|
57
|
+
return this.registrations.has(id);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* List all plugin registrations.
|
|
61
|
+
*
|
|
62
|
+
* @returns Array of all PluginRegistration objects in insertion order
|
|
63
|
+
*/
|
|
64
|
+
list() {
|
|
65
|
+
return Array.from(this.registrations.values());
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* List plugin registrations filtered by source.
|
|
69
|
+
*
|
|
70
|
+
* @param source - The source to filter by ('bundled' or 'installed')
|
|
71
|
+
* @returns Array of matching PluginRegistration objects
|
|
72
|
+
*/
|
|
73
|
+
listBySource(source) {
|
|
74
|
+
return this.list().filter((r) => r.source === source);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The number of registered plugins.
|
|
78
|
+
*/
|
|
79
|
+
get size() {
|
|
80
|
+
return this.registrations.size;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Remove all plugin registrations.
|
|
84
|
+
*/
|
|
85
|
+
clear() {
|
|
86
|
+
this.registrations.clear();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=plugin-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-registry.js","sourceRoot":"","sources":["../src/plugin-registry.ts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,cAAc;IACjB,aAAa,GAAoC,IAAI,GAAG,EAAE,CAAC;IAEnE;;;;;OAKG;IACH,QAAQ,CAAC,KAA8B;QACrC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE;YACtC,GAAG,KAAK;YACR,YAAY,EAAE,IAAI,IAAI,EAAE;SACzB,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IAED;;;;;;OAMG;IACH,SAAS,CAAC,EAAU;QAClB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACH,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACH,IAAI;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC;IACjD,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,MAA+B;QAC1C,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACxD,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { AgentCustomization, A16nPlugin, EmitResult, Warning } from '@a16njs/models';
|
|
2
|
+
/**
|
|
3
|
+
* Context provided to a content transformation during the conversion pipeline.
|
|
4
|
+
*/
|
|
5
|
+
export interface TransformationContext {
|
|
6
|
+
/** The items to transform */
|
|
7
|
+
items: AgentCustomization[];
|
|
8
|
+
/** The source plugin */
|
|
9
|
+
sourcePlugin: A16nPlugin;
|
|
10
|
+
/** The target plugin */
|
|
11
|
+
targetPlugin: A16nPlugin;
|
|
12
|
+
/** Root directory for source (discovery) */
|
|
13
|
+
sourceRoot: string;
|
|
14
|
+
/** Root directory for target (emission) */
|
|
15
|
+
targetRoot: string;
|
|
16
|
+
/**
|
|
17
|
+
* Perform a trial emission (dry-run) to discover file mapping.
|
|
18
|
+
* Only available when dryRun is false; stateful transformations
|
|
19
|
+
* like path rewriting use this to know target paths without
|
|
20
|
+
* actually writing files.
|
|
21
|
+
*/
|
|
22
|
+
trialEmit: (items: AgentCustomization[]) => Promise<EmitResult>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Result of applying a content transformation.
|
|
26
|
+
*/
|
|
27
|
+
export interface TransformationResult {
|
|
28
|
+
/** The transformed items */
|
|
29
|
+
items: AgentCustomization[];
|
|
30
|
+
/** Warnings produced by this transformation */
|
|
31
|
+
warnings: Warning[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A composable content transformation in the conversion pipeline.
|
|
35
|
+
*
|
|
36
|
+
* Transformations receive discovered items and produce transformed items.
|
|
37
|
+
* They run in sequence between discovery and final emission, enabling
|
|
38
|
+
* composable, extensible processing without double emission.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const transform: ContentTransformation = {
|
|
43
|
+
* id: 'path-rewriting',
|
|
44
|
+
* name: 'Path Reference Rewriting',
|
|
45
|
+
* transform: async (context) => {
|
|
46
|
+
* // Transform items...
|
|
47
|
+
* return { items: transformedItems, warnings: [] };
|
|
48
|
+
* },
|
|
49
|
+
* };
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export interface ContentTransformation {
|
|
53
|
+
/** Unique identifier for this transformation */
|
|
54
|
+
id: string;
|
|
55
|
+
/** Human-readable name */
|
|
56
|
+
name: string;
|
|
57
|
+
/**
|
|
58
|
+
* Apply this transformation to the items.
|
|
59
|
+
* @param context - The transformation context with items, plugins, and trial emit
|
|
60
|
+
* @returns Transformed items and any warnings
|
|
61
|
+
*/
|
|
62
|
+
transform(context: TransformationContext): Promise<TransformationResult>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Path rewriting transformation.
|
|
66
|
+
*
|
|
67
|
+
* Rewrites file path references in content during format conversion.
|
|
68
|
+
* Uses a trial emission to discover the source-to-target path mapping,
|
|
69
|
+
* then rewrites all path references in content. Also detects orphan
|
|
70
|
+
* references (paths that weren't converted) using plugin-provided
|
|
71
|
+
* path patterns.
|
|
72
|
+
*
|
|
73
|
+
* This replaces the hardcoded path rewriting logic that was previously
|
|
74
|
+
* embedded in the engine's convert() method, eliminating the need for
|
|
75
|
+
* double emission and removing hardcoded plugin knowledge.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const transform = new PathRewritingTransformation();
|
|
80
|
+
* engine.convert({
|
|
81
|
+
* source: 'cursor',
|
|
82
|
+
* target: 'claude',
|
|
83
|
+
* root: '/project',
|
|
84
|
+
* transformations: [transform],
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare class PathRewritingTransformation implements ContentTransformation {
|
|
89
|
+
readonly id = "path-rewriting";
|
|
90
|
+
readonly name = "Path Reference Rewriting";
|
|
91
|
+
transform(context: TransformationContext): Promise<TransformationResult>;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=transformation.d.ts.map
|