@apiquest/fracture 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -2
- package/dist/CollectionRunner.d.ts +2 -0
- package/dist/CollectionRunner.d.ts.map +1 -1
- package/dist/CollectionRunner.js +20 -2
- package/dist/CollectionRunner.js.map +1 -1
- package/dist/LibraryLoader.d.ts +49 -0
- package/dist/LibraryLoader.d.ts.map +1 -0
- package/dist/LibraryLoader.js +198 -0
- package/dist/LibraryLoader.js.map +1 -0
- package/dist/PluginLoader.d.ts.map +1 -1
- package/dist/PluginLoader.js +9 -6
- package/dist/PluginLoader.js.map +1 -1
- package/dist/PluginResolver.d.ts +1 -1
- package/dist/PluginResolver.d.ts.map +1 -1
- package/dist/PluginResolver.js +1 -1
- package/dist/PluginResolver.js.map +1 -1
- package/dist/ScriptEngine.d.ts +2 -1
- package/dist/ScriptEngine.d.ts.map +1 -1
- package/dist/ScriptEngine.js +15 -8
- package/dist/ScriptEngine.js.map +1 -1
- package/dist/cli/index.js +35 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-commands.d.ts.map +1 -1
- package/dist/cli/plugin-commands.js +47 -81
- package/dist/cli/plugin-commands.js.map +1 -1
- package/dist/cli/plugin-installer.d.ts +48 -0
- package/dist/cli/plugin-installer.d.ts.map +1 -0
- package/dist/cli/plugin-installer.js +136 -0
- package/dist/cli/plugin-installer.js.map +1 -0
- package/dist/cli/plugin-registry.d.ts +17 -0
- package/dist/cli/plugin-registry.d.ts.map +1 -0
- package/dist/cli/plugin-registry.js +77 -0
- package/dist/cli/plugin-registry.js.map +1 -0
- package/package.json +1 -1
- package/src/CollectionAnalyzer.ts +0 -102
- package/src/CollectionRunner.ts +0 -1423
- package/src/CollectionRunner.types.ts +0 -9
- package/src/CollectionValidator.ts +0 -289
- package/src/ConsoleReporter.ts +0 -143
- package/src/CookieJar.ts +0 -258
- package/src/DagScheduler.ts +0 -439
- package/src/Logger.ts +0 -85
- package/src/PluginLoader.ts +0 -126
- package/src/PluginManager.ts +0 -208
- package/src/PluginResolver.ts +0 -154
- package/src/QuestAPI.ts +0 -764
- package/src/QuestAPI.types.ts +0 -33
- package/src/QuestTestAPI.ts +0 -164
- package/src/RequestFilter.ts +0 -224
- package/src/ScriptEngine.ts +0 -219
- package/src/ScriptValidator.ts +0 -428
- package/src/TaskGraph.ts +0 -598
- package/src/TestCounter.ts +0 -109
- package/src/VariableResolver.ts +0 -114
- package/src/cli/index.ts +0 -480
- package/src/cli/plugin-commands.ts +0 -342
- package/src/cli/plugin-discovery.ts +0 -44
- package/src/index.ts +0 -24
- package/src/utils.ts +0 -52
package/src/PluginManager.ts
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import type { IProtocolPlugin, IAuthPlugin, IValueProviderPlugin, Request, ExecutionContext, ProtocolResponse, RuntimeOptions, Auth } from '@apiquest/types';
|
|
2
|
-
import { Logger } from './Logger.js';
|
|
3
|
-
|
|
4
|
-
export class PluginManager {
|
|
5
|
-
private plugins: Map<string, IProtocolPlugin> = new Map();
|
|
6
|
-
private authPlugins: Map<string, IAuthPlugin> = new Map();
|
|
7
|
-
private variableProviders: Map<string, IValueProviderPlugin> = new Map();
|
|
8
|
-
private logger: Logger;
|
|
9
|
-
|
|
10
|
-
constructor(baseLogger?: Logger) {
|
|
11
|
-
this.logger = baseLogger?.createLogger('PluginManager') ?? new Logger('PluginManager');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Register a protocol plugin
|
|
16
|
-
*/
|
|
17
|
-
registerPlugin(plugin: IProtocolPlugin): void {
|
|
18
|
-
// Register plugin for each protocol it provides
|
|
19
|
-
for (const protocol of plugin.protocols) {
|
|
20
|
-
this.plugins.set(protocol, plugin);
|
|
21
|
-
this.logger.debug(`Registered protocol plugin: ${plugin.name} for protocol '${protocol}'`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Register an auth plugin
|
|
27
|
-
*/
|
|
28
|
-
registerAuthPlugin(plugin: IAuthPlugin): void {
|
|
29
|
-
// Register plugin for each auth type it provides
|
|
30
|
-
for (const authType of plugin.authTypes) {
|
|
31
|
-
this.authPlugins.set(authType, plugin);
|
|
32
|
-
this.logger.debug(`Registered auth plugin: ${plugin.name} for type '${authType}'`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Register a variable provider plugin
|
|
38
|
-
*/
|
|
39
|
-
registerVariableProvider(plugin: IValueProviderPlugin): void {
|
|
40
|
-
this.variableProviders.set(plugin.provider, plugin);
|
|
41
|
-
this.logger.debug(`Registered vault provider: ${plugin.name} (${plugin.provider})`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Get plugin for a protocol
|
|
46
|
-
*/
|
|
47
|
-
getPlugin(protocol: string): IProtocolPlugin | undefined {
|
|
48
|
-
return this.plugins.get(protocol);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Get auth plugin for a type
|
|
53
|
-
*/
|
|
54
|
-
getAuthPlugin(type: string): IAuthPlugin | undefined {
|
|
55
|
-
return this.authPlugins.get(type);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Apply auth to request if auth is configured
|
|
60
|
-
*/
|
|
61
|
-
private async applyAuth(request: Request, auth: Auth, options: RuntimeOptions): Promise<Request> {
|
|
62
|
-
if (auth.type === 'none' || auth.type === 'inherit') {
|
|
63
|
-
return request;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const authPlugin = this.authPlugins.get(auth.type);
|
|
67
|
-
if (authPlugin === null || authPlugin === undefined) {
|
|
68
|
-
this.logger.error(`No auth plugin registered for type: ${auth.type}`);
|
|
69
|
-
throw new Error(`No auth plugin registered for type: ${auth.type}`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
this.logger.debug(`Applying auth: ${auth.type} (plugin: ${authPlugin.name})`);
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const pluginLogger = this.logger.createLogger(`Auth:${authPlugin.name}`);
|
|
76
|
-
return await authPlugin.apply(request, auth, options, pluginLogger);
|
|
77
|
-
} catch (error: unknown) {
|
|
78
|
-
const errorMsg = (error as { message?: string }).message ?? 'Unknown error';
|
|
79
|
-
this.logger.error(`Auth plugin error (${auth.type}): ${errorMsg}`);
|
|
80
|
-
throw new Error(`Auth plugin error (${auth.type}): ${errorMsg}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Execute request using appropriate plugin
|
|
86
|
-
* @param protocol - Protocol name from collection.protocol
|
|
87
|
-
* @param emitEvent - Optional callback for plugin events (e.g., WebSocket onMessage)
|
|
88
|
-
*/
|
|
89
|
-
async execute(
|
|
90
|
-
protocol: string,
|
|
91
|
-
request: Request,
|
|
92
|
-
context: ExecutionContext,
|
|
93
|
-
options: RuntimeOptions,
|
|
94
|
-
emitEvent?: (eventName: string, eventData: unknown) => Promise<void>
|
|
95
|
-
): Promise<ProtocolResponse> {
|
|
96
|
-
// Check abort signal before execution
|
|
97
|
-
if ((context.abortSignal as AbortSignal | undefined)?.aborted === true) {
|
|
98
|
-
this.logger.debug('Plugin execution aborted before start');
|
|
99
|
-
return {
|
|
100
|
-
status: 0,
|
|
101
|
-
statusText: 'Aborted',
|
|
102
|
-
body: '',
|
|
103
|
-
headers: {},
|
|
104
|
-
duration: 0,
|
|
105
|
-
error: 'Request aborted'
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const plugin = this.plugins.get(protocol);
|
|
110
|
-
|
|
111
|
-
if (plugin === null || plugin === undefined) {
|
|
112
|
-
this.logger.error(`No plugin registered for protocol: ${protocol}`);
|
|
113
|
-
throw new Error(`No plugin registered for protocol: ${protocol}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
this.logger.debug(`Executing request using ${protocol} plugin: ${plugin.name}`);
|
|
117
|
-
|
|
118
|
-
// Validate auth compatibility with protocol
|
|
119
|
-
if (request.auth !== null && request.auth !== undefined && request.auth.type !== 'none' && request.auth.type !== 'inherit') {
|
|
120
|
-
if (!plugin.supportedAuthTypes.includes(request.auth.type)) {
|
|
121
|
-
this.logger.error(`Protocol '${protocol}' does not support auth type '${request.auth.type}'`);
|
|
122
|
-
throw new Error(
|
|
123
|
-
`Protocol '${protocol}' does not support auth type '${request.auth.type}'. ` +
|
|
124
|
-
`Supported types: ${plugin.supportedAuthTypes.join(', ')}`
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Apply auth if configured (should already be resolved)
|
|
130
|
-
let modifiedRequest = request;
|
|
131
|
-
if (request.auth !== null && request.auth !== undefined) {
|
|
132
|
-
modifiedRequest = await this.applyAuth(modifiedRequest, request.auth, options);
|
|
133
|
-
|
|
134
|
-
// Update context.currentRequest to reflect auth modifications
|
|
135
|
-
context.currentRequest = modifiedRequest;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Validate request
|
|
139
|
-
this.logger.trace('Validating request with plugin');
|
|
140
|
-
const validation = plugin.validate(modifiedRequest, options);
|
|
141
|
-
if (validation.valid === false) {
|
|
142
|
-
const errorMessages = validation.errors?.map(e => e.message).join(', ') ?? 'Unknown error';
|
|
143
|
-
this.logger.error(`Request validation failed: ${errorMessages}`);
|
|
144
|
-
throw new Error(`Request validation failed: ${errorMessages}`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Execute plugin with merged runtime options, event emitter, and logger
|
|
148
|
-
const pluginLogger = this.logger.createLogger(`Protocol:${plugin.name}`);
|
|
149
|
-
const response = await plugin.execute(modifiedRequest, context, options, emitEvent, pluginLogger);
|
|
150
|
-
|
|
151
|
-
this.logger.debug(`Plugin execution completed in ${response.duration}ms (status: ${response.status})`);
|
|
152
|
-
|
|
153
|
-
return response;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Get all registered plugins
|
|
158
|
-
*/
|
|
159
|
-
getAllPlugins(): IProtocolPlugin[] {
|
|
160
|
-
return Array.from(this.plugins.values());
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Get all registered auth plugins
|
|
165
|
-
*/
|
|
166
|
-
getAllAuthPlugins(): IAuthPlugin[] {
|
|
167
|
-
return Array.from(this.authPlugins.values());
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get variable provider plugin
|
|
172
|
-
*/
|
|
173
|
-
getVariableProvider(provider: string): IValueProviderPlugin | undefined {
|
|
174
|
-
return this.variableProviders.get(provider);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Get all registered variable providers
|
|
179
|
-
*/
|
|
180
|
-
getAllVariableProviders(): IValueProviderPlugin[] {
|
|
181
|
-
return Array.from(this.variableProviders.values());
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Resolve variable value using provider plugin
|
|
186
|
-
* Called when a variable has a provider specified
|
|
187
|
-
*/
|
|
188
|
-
async resolveVariableProvider(
|
|
189
|
-
provider: string,
|
|
190
|
-
key: string,
|
|
191
|
-
config?: Record<string, unknown>,
|
|
192
|
-
context?: ExecutionContext
|
|
193
|
-
): Promise<string | null> {
|
|
194
|
-
const providerPlugin = this.variableProviders.get(provider);
|
|
195
|
-
|
|
196
|
-
if (providerPlugin === null || providerPlugin === undefined) {
|
|
197
|
-
throw new Error(`No variable provider plugin registered for: ${provider}`);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
const providerLogger = this.logger.createLogger(`Vault:${providerPlugin.name}`);
|
|
202
|
-
return await providerPlugin.getValue(key, config, context, providerLogger);
|
|
203
|
-
} catch (error: unknown) {
|
|
204
|
-
const errorMsg = (error as { message?: string }).message ?? 'Unknown error';
|
|
205
|
-
throw new Error(`Variable provider error (${provider}): ${errorMsg}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
package/src/PluginResolver.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import type { PluginPackageJson } from '@apiquest/types';
|
|
2
|
-
import { Logger } from './Logger.js';
|
|
3
|
-
|
|
4
|
-
export interface ResolvedPlugin {
|
|
5
|
-
name: string;
|
|
6
|
-
version: string;
|
|
7
|
-
type: 'protocol' | 'auth' | 'value';
|
|
8
|
-
path: string;
|
|
9
|
-
entryPoint: string;
|
|
10
|
-
protocols?: string[];
|
|
11
|
-
authTypes?: string[];
|
|
12
|
-
provider?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class PluginResolver {
|
|
16
|
-
private resolved: Map<string, ResolvedPlugin> = new Map();
|
|
17
|
-
private logger: Logger;
|
|
18
|
-
|
|
19
|
-
constructor(baseLogger?: Logger) {
|
|
20
|
-
this.logger = baseLogger?.createLogger('PluginResolver') ?? new Logger('PluginResolver');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Scan multiple directories and resolve all available plugins
|
|
25
|
-
* This is fast - just file I/O, no module loading
|
|
26
|
-
*/
|
|
27
|
-
async scanDirectories(dirs: string[]): Promise<ResolvedPlugin[]> {
|
|
28
|
-
const scanPromises = dirs.map(dir =>
|
|
29
|
-
this.scanDirectory(dir).catch(err => {
|
|
30
|
-
this.logger.error('Plugin scanning failed:', err);
|
|
31
|
-
})
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
await Promise.all(scanPromises);
|
|
35
|
-
|
|
36
|
-
return Array.from(this.resolved.values());
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Scan single directory for plugins
|
|
41
|
-
*/
|
|
42
|
-
private async scanDirectory(pluginsDir: string): Promise<void> {
|
|
43
|
-
const { readdir, readFile, access } = await import('fs/promises');
|
|
44
|
-
const path = await import('path');
|
|
45
|
-
|
|
46
|
-
this.logger.debug(`Scanning plugins: ${pluginsDir}`);
|
|
47
|
-
|
|
48
|
-
// Check if directory exists
|
|
49
|
-
try {
|
|
50
|
-
await access(pluginsDir);
|
|
51
|
-
} catch {
|
|
52
|
-
this.logger.debug(`Plugins directory does not exist: ${pluginsDir}`);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const entries = await readdir(pluginsDir, { withFileTypes: true });
|
|
57
|
-
|
|
58
|
-
for (const entry of entries) {
|
|
59
|
-
// Only process plugin-* directories
|
|
60
|
-
if (!entry.isDirectory() || !entry.name.startsWith('plugin-')) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const pluginPath = path.join(pluginsDir, entry.name);
|
|
65
|
-
await this.resolvePlugin(pluginPath);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Resolve a single plugin - read metadata but don't load module
|
|
71
|
-
*/
|
|
72
|
-
private async resolvePlugin(pluginPath: string): Promise<void> {
|
|
73
|
-
const { readFile } = await import('fs/promises');
|
|
74
|
-
const path = await import('path');
|
|
75
|
-
|
|
76
|
-
const packageJsonPath = path.join(pluginPath, 'package.json');
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
// Read package.json
|
|
80
|
-
const pkgContent = await readFile(packageJsonPath, 'utf-8');
|
|
81
|
-
const pkg = JSON.parse(pkgContent) as PluginPackageJson;
|
|
82
|
-
|
|
83
|
-
// Check if plugin is for fracture runtime
|
|
84
|
-
const runtime = pkg.apiquest?.runtime;
|
|
85
|
-
const runtimeArray = Array.isArray(runtime) ? runtime : [runtime];
|
|
86
|
-
if (pkg.apiquest === null || pkg.apiquest === undefined || !runtimeArray.includes('fracture')) {
|
|
87
|
-
this.logger.debug(`Skipping ${pkg.name} (runtime: ${runtime ?? 'undefined'})`);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Extract metadata from package.json
|
|
92
|
-
const type = pkg.apiquest.type;
|
|
93
|
-
if (!['protocol', 'auth', 'value'].includes(type)) {
|
|
94
|
-
this.logger.warn(`Unknown plugin type: ${type} (${pkg.name})`);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Resolve entry point
|
|
99
|
-
const entryPoint = pkg.main ?? 'dist/index.js';
|
|
100
|
-
const fullEntryPath = path.join(pluginPath, entryPoint);
|
|
101
|
-
|
|
102
|
-
// Extract capabilities from apiquest.capabilities.provides
|
|
103
|
-
const provides = pkg.apiquest.capabilities?.provides ?? {};
|
|
104
|
-
|
|
105
|
-
// Create resolved plugin info
|
|
106
|
-
const resolved: ResolvedPlugin = {
|
|
107
|
-
name: pkg.name,
|
|
108
|
-
version: pkg.version,
|
|
109
|
-
type: type as 'protocol' | 'auth' | 'value',
|
|
110
|
-
path: pluginPath,
|
|
111
|
-
entryPoint: fullEntryPath,
|
|
112
|
-
protocols: provides.protocols,
|
|
113
|
-
authTypes: provides.authTypes,
|
|
114
|
-
provider: provides.provider,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Check for version conflicts
|
|
118
|
-
const existing = this.resolved.get(pkg.name);
|
|
119
|
-
if (existing !== null && existing !== undefined) {
|
|
120
|
-
if (this.compareVersions(pkg.version, existing.version) > 0) {
|
|
121
|
-
this.logger.debug(`Upgrading ${pkg.name} from v${existing.version} to v${pkg.version}`);
|
|
122
|
-
this.resolved.set(pkg.name, resolved);
|
|
123
|
-
} else {
|
|
124
|
-
this.logger.debug(`Skipping ${pkg.name} v${pkg.version} (v${existing.version} already resolved)`);
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
this.logger.debug(`Resolved ${pkg.name} v${pkg.version} (${type})`);
|
|
128
|
-
this.resolved.set(pkg.name, resolved);
|
|
129
|
-
}
|
|
130
|
-
} catch (error: unknown) {
|
|
131
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
132
|
-
this.logger.error(`Failed to resolve plugin at ${pluginPath}:`, errorMsg);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Compare semantic versions
|
|
138
|
-
* Returns: 1 if a > b, -1 if a < b, 0 if equal
|
|
139
|
-
*/
|
|
140
|
-
private compareVersions(a: string, b: string): number {
|
|
141
|
-
const aParts = a.split('.').map(Number);
|
|
142
|
-
const bParts = b.split('.').map(Number);
|
|
143
|
-
|
|
144
|
-
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
145
|
-
const aNum = aParts[i] ?? 0;
|
|
146
|
-
const bNum = bParts[i] ?? 0;
|
|
147
|
-
|
|
148
|
-
if (aNum > bNum) return 1;
|
|
149
|
-
if (aNum < bNum) return -1;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return 0;
|
|
153
|
-
}
|
|
154
|
-
}
|