@apiquest/fracture 1.0.2 → 1.0.4

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.
Files changed (168) hide show
  1. package/README.md +119 -0
  2. package/bin/cli.js +2 -2
  3. package/dist/CollectionRunner.js +3 -3
  4. package/dist/ScriptEngine.js +4 -4
  5. package/dist/cli/plugin-commands.d.ts.map +1 -1
  6. package/dist/cli/plugin-commands.js +2 -1
  7. package/dist/cli/plugin-commands.js.map +1 -1
  8. package/package.json +55 -50
  9. package/src/CollectionAnalyzer.ts +102 -102
  10. package/src/CollectionRunner.ts +1423 -1423
  11. package/src/CollectionRunner.types.ts +9 -9
  12. package/src/CollectionValidator.ts +289 -289
  13. package/src/ConsoleReporter.ts +143 -143
  14. package/src/CookieJar.ts +258 -258
  15. package/src/DagScheduler.ts +439 -439
  16. package/src/Logger.ts +85 -85
  17. package/src/PluginLoader.ts +126 -126
  18. package/src/PluginManager.ts +208 -208
  19. package/src/PluginResolver.ts +154 -154
  20. package/src/QuestAPI.ts +764 -764
  21. package/src/QuestAPI.types.ts +33 -33
  22. package/src/QuestTestAPI.ts +164 -164
  23. package/src/RequestFilter.ts +224 -224
  24. package/src/ScriptEngine.ts +219 -219
  25. package/src/ScriptValidator.ts +428 -428
  26. package/src/TaskGraph.ts +598 -598
  27. package/src/TestCounter.ts +109 -109
  28. package/src/VariableResolver.ts +114 -114
  29. package/src/cli/index.ts +480 -480
  30. package/src/cli/plugin-commands.ts +342 -341
  31. package/src/cli/plugin-discovery.ts +44 -44
  32. package/src/index.ts +24 -24
  33. package/src/utils.ts +52 -52
  34. package/tsconfig.json +20 -20
  35. package/tsconfig.test.json +5 -5
  36. package/vitest.config.ts +22 -22
  37. package/dist/ExecutionTree.d.ts +0 -77
  38. package/dist/ExecutionTree.d.ts.map +0 -1
  39. package/dist/ExecutionTree.js +0 -265
  40. package/dist/ExecutionTree.js.map +0 -1
  41. package/dist/fracture/src/CollectionAnalyzer.d.ts +0 -17
  42. package/dist/fracture/src/CollectionAnalyzer.d.ts.map +0 -1
  43. package/dist/fracture/src/CollectionAnalyzer.js +0 -70
  44. package/dist/fracture/src/CollectionAnalyzer.js.map +0 -1
  45. package/dist/fracture/src/CollectionRunner.d.ts +0 -39
  46. package/dist/fracture/src/CollectionRunner.d.ts.map +0 -1
  47. package/dist/fracture/src/CollectionRunner.js +0 -802
  48. package/dist/fracture/src/CollectionRunner.js.map +0 -1
  49. package/dist/fracture/src/CollectionRunner.types.d.ts +0 -8
  50. package/dist/fracture/src/CollectionRunner.types.d.ts.map +0 -1
  51. package/dist/fracture/src/CollectionRunner.types.js +0 -2
  52. package/dist/fracture/src/CollectionRunner.types.js.map +0 -1
  53. package/dist/fracture/src/CollectionValidator.d.ts +0 -14
  54. package/dist/fracture/src/CollectionValidator.d.ts.map +0 -1
  55. package/dist/fracture/src/CollectionValidator.js +0 -145
  56. package/dist/fracture/src/CollectionValidator.js.map +0 -1
  57. package/dist/fracture/src/ConsoleReporter.d.ts +0 -24
  58. package/dist/fracture/src/ConsoleReporter.d.ts.map +0 -1
  59. package/dist/fracture/src/ConsoleReporter.js +0 -123
  60. package/dist/fracture/src/ConsoleReporter.js.map +0 -1
  61. package/dist/fracture/src/CookieJar.d.ts +0 -70
  62. package/dist/fracture/src/CookieJar.d.ts.map +0 -1
  63. package/dist/fracture/src/CookieJar.js +0 -233
  64. package/dist/fracture/src/CookieJar.js.map +0 -1
  65. package/dist/fracture/src/ExecutionTree.d.ts +0 -77
  66. package/dist/fracture/src/ExecutionTree.d.ts.map +0 -1
  67. package/dist/fracture/src/ExecutionTree.js +0 -258
  68. package/dist/fracture/src/ExecutionTree.js.map +0 -1
  69. package/dist/fracture/src/Logger.d.ts +0 -25
  70. package/dist/fracture/src/Logger.d.ts.map +0 -1
  71. package/dist/fracture/src/Logger.js +0 -78
  72. package/dist/fracture/src/Logger.js.map +0 -1
  73. package/dist/fracture/src/PluginLoader.d.ts +0 -23
  74. package/dist/fracture/src/PluginLoader.d.ts.map +0 -1
  75. package/dist/fracture/src/PluginLoader.js +0 -102
  76. package/dist/fracture/src/PluginLoader.js.map +0 -1
  77. package/dist/fracture/src/PluginManager.d.ts +0 -64
  78. package/dist/fracture/src/PluginManager.d.ts.map +0 -1
  79. package/dist/fracture/src/PluginManager.js +0 -162
  80. package/dist/fracture/src/PluginManager.js.map +0 -1
  81. package/dist/fracture/src/PluginResolver.d.ts +0 -35
  82. package/dist/fracture/src/PluginResolver.d.ts.map +0 -1
  83. package/dist/fracture/src/PluginResolver.js +0 -128
  84. package/dist/fracture/src/PluginResolver.js.map +0 -1
  85. package/dist/fracture/src/QuestAPI.d.ts +0 -9
  86. package/dist/fracture/src/QuestAPI.d.ts.map +0 -1
  87. package/dist/fracture/src/QuestAPI.js +0 -679
  88. package/dist/fracture/src/QuestAPI.js.map +0 -1
  89. package/dist/fracture/src/QuestAPI.types.d.ts +0 -35
  90. package/dist/fracture/src/QuestAPI.types.d.ts.map +0 -1
  91. package/dist/fracture/src/QuestAPI.types.js +0 -3
  92. package/dist/fracture/src/QuestAPI.types.js.map +0 -1
  93. package/dist/fracture/src/QuestTestAPI.d.ts +0 -12
  94. package/dist/fracture/src/QuestTestAPI.d.ts.map +0 -1
  95. package/dist/fracture/src/QuestTestAPI.js +0 -133
  96. package/dist/fracture/src/QuestTestAPI.js.map +0 -1
  97. package/dist/fracture/src/ScriptEngine.d.ts +0 -21
  98. package/dist/fracture/src/ScriptEngine.d.ts.map +0 -1
  99. package/dist/fracture/src/ScriptEngine.js +0 -183
  100. package/dist/fracture/src/ScriptEngine.js.map +0 -1
  101. package/dist/fracture/src/ScriptValidator.d.ts +0 -68
  102. package/dist/fracture/src/ScriptValidator.d.ts.map +0 -1
  103. package/dist/fracture/src/ScriptValidator.js +0 -351
  104. package/dist/fracture/src/ScriptValidator.js.map +0 -1
  105. package/dist/fracture/src/TestCounter.d.ts +0 -18
  106. package/dist/fracture/src/TestCounter.d.ts.map +0 -1
  107. package/dist/fracture/src/TestCounter.js +0 -82
  108. package/dist/fracture/src/TestCounter.js.map +0 -1
  109. package/dist/fracture/src/VariableResolver.d.ts +0 -20
  110. package/dist/fracture/src/VariableResolver.d.ts.map +0 -1
  111. package/dist/fracture/src/VariableResolver.js +0 -100
  112. package/dist/fracture/src/VariableResolver.js.map +0 -1
  113. package/dist/fracture/src/cli/index.d.ts +0 -3
  114. package/dist/fracture/src/cli/index.d.ts.map +0 -1
  115. package/dist/fracture/src/cli/index.js +0 -347
  116. package/dist/fracture/src/cli/index.js.map +0 -1
  117. package/dist/fracture/src/cli/plugin-commands.d.ts +0 -6
  118. package/dist/fracture/src/cli/plugin-commands.d.ts.map +0 -1
  119. package/dist/fracture/src/cli/plugin-commands.js +0 -263
  120. package/dist/fracture/src/cli/plugin-commands.js.map +0 -1
  121. package/dist/fracture/src/cli/plugin-discovery.d.ts +0 -11
  122. package/dist/fracture/src/cli/plugin-discovery.d.ts.map +0 -1
  123. package/dist/fracture/src/cli/plugin-discovery.js +0 -64
  124. package/dist/fracture/src/cli/plugin-discovery.js.map +0 -1
  125. package/dist/fracture/src/index.d.ts +0 -13
  126. package/dist/fracture/src/index.d.ts.map +0 -1
  127. package/dist/fracture/src/index.js +0 -17
  128. package/dist/fracture/src/index.js.map +0 -1
  129. package/dist/fracture/src/utils.d.ts +0 -28
  130. package/dist/fracture/src/utils.d.ts.map +0 -1
  131. package/dist/fracture/src/utils.js +0 -48
  132. package/dist/fracture/src/utils.js.map +0 -1
  133. package/dist/plugin-auth/src/apikey-auth.d.ts +0 -3
  134. package/dist/plugin-auth/src/apikey-auth.d.ts.map +0 -1
  135. package/dist/plugin-auth/src/apikey-auth.js +0 -73
  136. package/dist/plugin-auth/src/apikey-auth.js.map +0 -1
  137. package/dist/plugin-auth/src/basic-auth.d.ts +0 -3
  138. package/dist/plugin-auth/src/basic-auth.d.ts.map +0 -1
  139. package/dist/plugin-auth/src/basic-auth.js +0 -61
  140. package/dist/plugin-auth/src/basic-auth.js.map +0 -1
  141. package/dist/plugin-auth/src/bearer-auth.d.ts +0 -3
  142. package/dist/plugin-auth/src/bearer-auth.d.ts.map +0 -1
  143. package/dist/plugin-auth/src/bearer-auth.js +0 -49
  144. package/dist/plugin-auth/src/bearer-auth.js.map +0 -1
  145. package/dist/plugin-auth/src/helpers.d.ts +0 -3
  146. package/dist/plugin-auth/src/helpers.d.ts.map +0 -1
  147. package/dist/plugin-auth/src/helpers.js +0 -8
  148. package/dist/plugin-auth/src/helpers.js.map +0 -1
  149. package/dist/plugin-auth/src/index.d.ts +0 -10
  150. package/dist/plugin-auth/src/index.d.ts.map +0 -1
  151. package/dist/plugin-auth/src/index.js +0 -25
  152. package/dist/plugin-auth/src/index.js.map +0 -1
  153. package/dist/plugin-auth/src/oauth2-auth.d.ts +0 -35
  154. package/dist/plugin-auth/src/oauth2-auth.d.ts.map +0 -1
  155. package/dist/plugin-auth/src/oauth2-auth.js +0 -266
  156. package/dist/plugin-auth/src/oauth2-auth.js.map +0 -1
  157. package/dist/plugin-http/src/index.d.ts +0 -4
  158. package/dist/plugin-http/src/index.d.ts.map +0 -1
  159. package/dist/plugin-http/src/index.js +0 -266
  160. package/dist/plugin-http/src/index.js.map +0 -1
  161. package/dist/plugin-vault-file/src/index.d.ts +0 -67
  162. package/dist/plugin-vault-file/src/index.d.ts.map +0 -1
  163. package/dist/plugin-vault-file/src/index.js +0 -171
  164. package/dist/plugin-vault-file/src/index.js.map +0 -1
  165. package/dist/types.d.ts +0 -374
  166. package/dist/types.d.ts.map +0 -1
  167. package/dist/types.js +0 -13
  168. package/dist/types.js.map +0 -1
@@ -1,341 +1,342 @@
1
- import { Command } from 'commander';
2
- import { exec } from 'child_process';
3
- import { promisify } from 'util';
4
- import { readdir, readFile, access } from 'fs/promises';
5
- import path from 'path';
6
- import { execSync } from 'child_process';
7
-
8
- const execAsync = promisify(exec);
9
-
10
- /**
11
- * Add plugin management commands to the CLI program
12
- */
13
- export function addPluginCommands(program: Command): void {
14
- const pluginCommand = program
15
- .command('plugin')
16
- .description('Manage ApiQuest plugins');
17
-
18
- // quest plugin install <name>
19
- pluginCommand
20
- .command('install')
21
- .description('Install a plugin')
22
- .argument('<names...>', 'Plugin name(s) to install')
23
- .action(async (names: string[]) => {
24
- for (const name of names) {
25
- const packageName = name.startsWith('@') ? name : `@apiquest/plugin-${name}`;
26
-
27
- console.log(`Installing ${packageName}...`);
28
-
29
- try {
30
- // Use npm to install globally
31
- await execAsync(`npm install -g ${packageName}`);
32
- console.log(`✓ ${packageName} installed`);
33
- } catch (error) {
34
- const err = error as { message?: string };
35
- console.error(`✗ Failed to install ${packageName}:`, err.message ?? String(error));
36
- }
37
- }
38
- });
39
-
40
- // quest plugin list
41
- pluginCommand
42
- .command('list')
43
- .description('List installed plugins')
44
- .action(async () => {
45
- console.log('Installed plugins:\n');
46
-
47
- // Check global npm packages
48
- try {
49
- const globalPath = execSync('npm root -g', { encoding: 'utf-8' }).trim();
50
- const apiquestPath = path.join(globalPath, '@apiquest');
51
- await listPluginsFromDir(apiquestPath, 'Global (npm)');
52
- } catch (error) {
53
- console.log(' [Global (npm)] Could not access global npm packages\n');
54
- }
55
- });
56
-
57
- // quest plugin available
58
- pluginCommand
59
- .command('available')
60
- .description('List available plugins from npm registry (fracture runtime)')
61
- .action(async () => {
62
- console.log('Available plugins (npm registry):\n');
63
-
64
- try {
65
- const plugins = await fetchAvailablePlugins();
66
- if (plugins.length === 0) {
67
- console.log(' No plugins found for fracture runtime.');
68
- return;
69
- }
70
-
71
- for (const plugin of plugins) {
72
- console.log(` - ${plugin.name}@${plugin.version}`);
73
- if (plugin.type !== undefined) {
74
- console.log(` Type: ${plugin.type}`);
75
- }
76
- if (plugin.runtime.length > 0) {
77
- console.log(` Runtime: ${plugin.runtime.join(', ')}`);
78
- }
79
- if (plugin.description !== undefined) {
80
- console.log(` Description: ${plugin.description}`);
81
- }
82
- printCapabilities(plugin.provides, ' ');
83
- }
84
-
85
- console.log('');
86
- } catch (error) {
87
- console.error('Failed to query npm registry:', error instanceof Error ? error.message : String(error));
88
- process.exit(4);
89
- }
90
- });
91
-
92
- // quest plugin remove <name>
93
- pluginCommand
94
- .command('remove')
95
- .description('Remove a plugin')
96
- .argument('<names...>', 'Plugin name(s) to remove')
97
- .action(async (names: string[]) => {
98
- for (const name of names) {
99
- const packageName = name.startsWith('@') ? name : `@apiquest/plugin-${name}`;
100
-
101
- console.log(`Removing ${packageName}...`);
102
-
103
- try {
104
- await execAsync(`npm uninstall -g ${packageName}`);
105
- console.log(`✓ ${packageName} removed`);
106
- } catch (error) {
107
- const err = error as { message?: string };
108
- console.error(`✗ Failed to remove ${packageName}:`, err.message ?? String(error));
109
- }
110
- }
111
- });
112
-
113
- // quest plugin update <name>
114
- pluginCommand
115
- .command('update')
116
- .description('Update plugin(s)')
117
- .argument('[names...]', 'Plugin name(s) to update (all if not specified)')
118
- .action(async (names: string[]) => {
119
- if (names.length === 0) {
120
- // Update all @apiquest plugins
121
- console.log('Updating all @apiquest plugins...');
122
-
123
- try {
124
- await execAsync('npm update -g @apiquest/*');
125
- console.log(' All plugins updated');
126
- } catch (error) {
127
- const err = error as { message?: string };
128
- console.error('✗ Failed to update plugins:', err.message ?? String(error));
129
- }
130
- } else {
131
- for (const name of names) {
132
- const packageName = name.startsWith('@') ? name : `@apiquest/plugin-${name}`;
133
-
134
- console.log(`Updating ${packageName}...`);
135
-
136
- try {
137
- await execAsync(`npm update -g ${packageName}`);
138
- console.log(`✓ ${packageName} updated`);
139
- } catch (error) {
140
- const err = error as { message?: string };
141
- console.error(`✗ Failed to update ${packageName}:`, err.message ?? String(error));
142
- }
143
- }
144
- }
145
- });
146
- }
147
-
148
- /**
149
- * List plugins from a specific directory
150
- */
151
- async function listPluginsFromDir(dir: string, label: string): Promise<void> {
152
- try {
153
- await access(dir);
154
- const entries = await readdir(dir, { withFileTypes: true });
155
- const plugins = entries.filter(e => e.isDirectory() && e.name.startsWith('plugin-'));
156
-
157
- let foundAny = false;
158
- console.log(` [${label}]`);
159
-
160
- for (const plugin of plugins) {
161
- const pluginPath = path.join(dir, plugin.name);
162
- const packageJsonPath = path.join(pluginPath, 'package.json');
163
-
164
- try {
165
- const pkgContent = await readFile(packageJsonPath, 'utf-8');
166
- const pkg: {
167
- name?: string;
168
- version?: string;
169
- apiquest?: ApiquestMetadata;
170
- } = JSON.parse(pkgContent) as { name?: string; version?: string; apiquest?: ApiquestMetadata };
171
-
172
- // Only list fracture runtime plugins
173
- if (pkg.apiquest?.runtime?.includes('fracture') !== true) {
174
- continue;
175
- }
176
-
177
- foundAny = true;
178
- const version = pkg.version ?? 'unknown';
179
- const type = pkg.apiquest?.type ?? 'unknown';
180
- const runtime = pkg.apiquest?.runtime?.join(', ') ?? 'unknown';
181
-
182
- console.log(` - ${pkg.name ?? 'unknown'}@${version}`);
183
- console.log(` Type: ${type}`);
184
- console.log(` Runtime: ${runtime}`);
185
-
186
- printCapabilities(pkg.apiquest?.capabilities?.provides, ' ');
187
-
188
- } catch {
189
- // Skip invalid package.json
190
- }
191
- }
192
-
193
- if (!foundAny) {
194
- console.log(` No fracture plugins found`);
195
- }
196
- console.log('');
197
- } catch {
198
- console.log(` [${label}] Directory not found or not accessible\n`);
199
- }
200
- }
201
-
202
- interface PluginProvides {
203
- protocols?: string[];
204
- authTypes?: string[];
205
- reportTypes?: string[];
206
- importFormats?: string[];
207
- exportFormats?: string[];
208
- visualizations?: string[];
209
- provider?: string;
210
- }
211
-
212
- interface ApiquestMetadata {
213
- type?: string;
214
- runtime?: string[];
215
- capabilities?: {
216
- provides?: PluginProvides;
217
- };
218
- }
219
-
220
- interface RegistrySearchResponse {
221
- objects: Array<{
222
- package?: {
223
- name?: string;
224
- };
225
- }>;
226
- }
227
-
228
- interface RegistryPackageResponse {
229
- name: string;
230
- version?: string;
231
- description?: string;
232
- 'dist-tags'?: {
233
- latest?: string;
234
- };
235
- versions?: Record<string, {
236
- description?: string;
237
- apiquest?: ApiquestMetadata;
238
- }>;
239
- }
240
-
241
- interface AvailablePlugin {
242
- name: string;
243
- version: string;
244
- description?: string;
245
- type?: string;
246
- runtime: string[];
247
- provides?: PluginProvides;
248
- }
249
-
250
- async function fetchAvailablePlugins(): Promise<AvailablePlugin[]> {
251
- const searchUrl = new URL('https://registry.npmjs.org/-/v1/search');
252
- searchUrl.searchParams.set('text', 'scope:@apiquest plugin-');
253
- searchUrl.searchParams.set('size', '250');
254
-
255
- const response = await fetch(searchUrl.toString(), {
256
- headers: {
257
- 'Accept': 'application/json'
258
- }
259
- });
260
-
261
- if (!response.ok) {
262
- throw new Error(`Registry search failed: ${response.status} ${response.statusText}`);
263
- }
264
-
265
- const data = await response.json() as RegistrySearchResponse;
266
- const names = data.objects
267
- .map(obj => obj.package?.name)
268
- .filter((name): name is string => typeof name === 'string' && name.startsWith('@apiquest/plugin-'));
269
-
270
- const pluginResults = await Promise.all(names.map(async (name) => {
271
- try {
272
- return await fetchRegistryPluginInfo(name);
273
- } catch {
274
- return null;
275
- }
276
- }));
277
-
278
- return pluginResults
279
- .filter((plugin): plugin is AvailablePlugin => plugin !== null)
280
- .filter(plugin => plugin.runtime.includes('fracture'))
281
- .sort((a, b) => a.name.localeCompare(b.name));
282
- }
283
-
284
- async function fetchRegistryPluginInfo(name: string): Promise<AvailablePlugin | null> {
285
- const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}`, {
286
- headers: {
287
- 'Accept': 'application/json'
288
- }
289
- });
290
-
291
- if (!response.ok) {
292
- return null;
293
- }
294
-
295
- const data = await response.json() as RegistryPackageResponse;
296
- const latest = data['dist-tags']?.latest;
297
- const versionData = latest !== undefined && data.versions !== undefined ? data.versions[latest] : undefined;
298
- const metadata = versionData?.apiquest;
299
-
300
- if (latest === undefined || metadata === undefined) {
301
- return null;
302
- }
303
-
304
- const runtime = Array.isArray(metadata.runtime) ? metadata.runtime : [];
305
- return {
306
- name: data.name,
307
- version: latest,
308
- description: versionData?.description,
309
- type: metadata.type,
310
- runtime,
311
- provides: metadata.capabilities?.provides
312
- };
313
- }
314
-
315
- function printCapabilities(provides?: PluginProvides, indent = ' '): void {
316
- if (provides === undefined) {
317
- return;
318
- }
319
-
320
- if (provides.protocols !== undefined && provides.protocols.length > 0) {
321
- console.log(`${indent}Protocols: ${provides.protocols.join(', ')}`);
322
- }
323
- if (provides.authTypes !== undefined && provides.authTypes.length > 0) {
324
- console.log(`${indent}Auth Types: ${provides.authTypes.join(', ')}`);
325
- }
326
- if (provides.reportTypes !== undefined && provides.reportTypes.length > 0) {
327
- console.log(`${indent}Report Types: ${provides.reportTypes.join(', ')}`);
328
- }
329
- if (provides.importFormats !== undefined && provides.importFormats.length > 0) {
330
- console.log(`${indent}Import Formats: ${provides.importFormats.join(', ')}`);
331
- }
332
- if (provides.exportFormats !== undefined && provides.exportFormats.length > 0) {
333
- console.log(`${indent}Export Formats: ${provides.exportFormats.join(', ')}`);
334
- }
335
- if (provides.visualizations !== undefined && provides.visualizations.length > 0) {
336
- console.log(`${indent}Visualizations: ${provides.visualizations.join(', ')}`);
337
- }
338
- if (provides.provider !== undefined) {
339
- console.log(`${indent}Provider: ${provides.provider}`);
340
- }
341
- }
1
+ import { Command } from 'commander';
2
+ import { exec } from 'child_process';
3
+ import { promisify } from 'util';
4
+ import { readdir, readFile, access } from 'fs/promises';
5
+ import path from 'path';
6
+ import { execSync } from 'child_process';
7
+
8
+ const execAsync = promisify(exec);
9
+
10
+ /**
11
+ * Add plugin management commands to the CLI program
12
+ */
13
+ export function addPluginCommands(program: Command): void {
14
+ const pluginCommand = program
15
+ .command('plugin')
16
+ .description('Manage ApiQuest plugins');
17
+
18
+ // quest plugin install <name>
19
+ pluginCommand
20
+ .command('install')
21
+ .description('Install a plugin')
22
+ .argument('<names...>', 'Plugin name(s) to install')
23
+ .action(async (names: string[]) => {
24
+ for (const name of names) {
25
+ const packageName = name.startsWith('@') ? name : `@apiquest/plugin-${name}`;
26
+
27
+ console.log(`Installing ${packageName}...`);
28
+
29
+ try {
30
+ // Use npm to install globally
31
+ await execAsync(`npm install -g ${packageName}`);
32
+ console.log(`✓ ${packageName} installed`);
33
+ } catch (error) {
34
+ const err = error as { message?: string };
35
+ console.error(`✗ Failed to install ${packageName}:`, err.message ?? String(error));
36
+ }
37
+ }
38
+ });
39
+
40
+ // quest plugin list
41
+ pluginCommand
42
+ .command('list')
43
+ .description('List installed plugins')
44
+ .action(async () => {
45
+ console.log('Installed plugins:\n');
46
+
47
+ // Check global npm packages
48
+ try {
49
+ const globalPath = execSync('npm root -g', { encoding: 'utf-8' }).trim();
50
+ const apiquestPath = path.join(globalPath, '@apiquest');
51
+ await listPluginsFromDir(apiquestPath, 'Global (npm)');
52
+ } catch (error) {
53
+ console.log(' [Global (npm)] Could not access global npm packages\n');
54
+ }
55
+ });
56
+
57
+ // quest plugin available
58
+ pluginCommand
59
+ .command('available')
60
+ .description('List available plugins from npm registry (fracture runtime)')
61
+ .action(async () => {
62
+ console.log('Available plugins (npm registry):\n');
63
+
64
+ try {
65
+ const plugins = await fetchAvailablePlugins();
66
+ if (plugins.length === 0) {
67
+ console.log(' No plugins found for fracture runtime.');
68
+ return;
69
+ }
70
+
71
+ for (const plugin of plugins) {
72
+ console.log(` - ${plugin.name}@${plugin.version}`);
73
+ if (plugin.type !== undefined) {
74
+ console.log(` Type: ${plugin.type}`);
75
+ }
76
+ if (plugin.runtime.length > 0) {
77
+ console.log(` Runtime: ${plugin.runtime.join(', ')}`);
78
+ }
79
+ if (plugin.description !== undefined) {
80
+ console.log(` Description: ${plugin.description}`);
81
+ }
82
+ printCapabilities(plugin.provides, ' ');
83
+ console.log('');
84
+ }
85
+
86
+ console.log('');
87
+ } catch (error) {
88
+ console.error('Failed to query npm registry:', error instanceof Error ? error.message : String(error));
89
+ process.exit(4);
90
+ }
91
+ });
92
+
93
+ // quest plugin remove <name>
94
+ pluginCommand
95
+ .command('remove')
96
+ .description('Remove a plugin')
97
+ .argument('<names...>', 'Plugin name(s) to remove')
98
+ .action(async (names: string[]) => {
99
+ for (const name of names) {
100
+ const packageName = name.startsWith('@') ? name : `@apiquest/plugin-${name}`;
101
+
102
+ console.log(`Removing ${packageName}...`);
103
+
104
+ try {
105
+ await execAsync(`npm uninstall -g ${packageName}`);
106
+ console.log(`✓ ${packageName} removed`);
107
+ } catch (error) {
108
+ const err = error as { message?: string };
109
+ console.error(`✗ Failed to remove ${packageName}:`, err.message ?? String(error));
110
+ }
111
+ }
112
+ });
113
+
114
+ // quest plugin update <name>
115
+ pluginCommand
116
+ .command('update')
117
+ .description('Update plugin(s)')
118
+ .argument('[names...]', 'Plugin name(s) to update (all if not specified)')
119
+ .action(async (names: string[]) => {
120
+ if (names.length === 0) {
121
+ // Update all @apiquest plugins
122
+ console.log('Updating all @apiquest plugins...');
123
+
124
+ try {
125
+ await execAsync('npm update -g @apiquest/*');
126
+ console.log('✓ All plugins updated');
127
+ } catch (error) {
128
+ const err = error as { message?: string };
129
+ console.error('✗ Failed to update plugins:', err.message ?? String(error));
130
+ }
131
+ } else {
132
+ for (const name of names) {
133
+ const packageName = name.startsWith('@') ? name : `@apiquest/plugin-${name}`;
134
+
135
+ console.log(`Updating ${packageName}...`);
136
+
137
+ try {
138
+ await execAsync(`npm update -g ${packageName}`);
139
+ console.log(`✓ ${packageName} updated`);
140
+ } catch (error) {
141
+ const err = error as { message?: string };
142
+ console.error(`✗ Failed to update ${packageName}:`, err.message ?? String(error));
143
+ }
144
+ }
145
+ }
146
+ });
147
+ }
148
+
149
+ /**
150
+ * List plugins from a specific directory
151
+ */
152
+ async function listPluginsFromDir(dir: string, label: string): Promise<void> {
153
+ try {
154
+ await access(dir);
155
+ const entries = await readdir(dir, { withFileTypes: true });
156
+ const plugins = entries.filter(e => e.isDirectory() && e.name.startsWith('plugin-'));
157
+
158
+ let foundAny = false;
159
+ console.log(` [${label}]`);
160
+
161
+ for (const plugin of plugins) {
162
+ const pluginPath = path.join(dir, plugin.name);
163
+ const packageJsonPath = path.join(pluginPath, 'package.json');
164
+
165
+ try {
166
+ const pkgContent = await readFile(packageJsonPath, 'utf-8');
167
+ const pkg: {
168
+ name?: string;
169
+ version?: string;
170
+ apiquest?: ApiquestMetadata;
171
+ } = JSON.parse(pkgContent) as { name?: string; version?: string; apiquest?: ApiquestMetadata };
172
+
173
+ // Only list fracture runtime plugins
174
+ if (pkg.apiquest?.runtime?.includes('fracture') !== true) {
175
+ continue;
176
+ }
177
+
178
+ foundAny = true;
179
+ const version = pkg.version ?? 'unknown';
180
+ const type = pkg.apiquest?.type ?? 'unknown';
181
+ const runtime = pkg.apiquest?.runtime?.join(', ') ?? 'unknown';
182
+
183
+ console.log(` - ${pkg.name ?? 'unknown'}@${version}`);
184
+ console.log(` Type: ${type}`);
185
+ console.log(` Runtime: ${runtime}`);
186
+
187
+ printCapabilities(pkg.apiquest?.capabilities?.provides, ' ');
188
+
189
+ } catch {
190
+ // Skip invalid package.json
191
+ }
192
+ }
193
+
194
+ if (!foundAny) {
195
+ console.log(` No fracture plugins found`);
196
+ }
197
+ console.log('');
198
+ } catch {
199
+ console.log(` [${label}] Directory not found or not accessible\n`);
200
+ }
201
+ }
202
+
203
+ interface PluginProvides {
204
+ protocols?: string[];
205
+ authTypes?: string[];
206
+ reportTypes?: string[];
207
+ importFormats?: string[];
208
+ exportFormats?: string[];
209
+ visualizations?: string[];
210
+ provider?: string;
211
+ }
212
+
213
+ interface ApiquestMetadata {
214
+ type?: string;
215
+ runtime?: string[];
216
+ capabilities?: {
217
+ provides?: PluginProvides;
218
+ };
219
+ }
220
+
221
+ interface RegistrySearchResponse {
222
+ objects: Array<{
223
+ package?: {
224
+ name?: string;
225
+ };
226
+ }>;
227
+ }
228
+
229
+ interface RegistryPackageResponse {
230
+ name: string;
231
+ version?: string;
232
+ description?: string;
233
+ 'dist-tags'?: {
234
+ latest?: string;
235
+ };
236
+ versions?: Record<string, {
237
+ description?: string;
238
+ apiquest?: ApiquestMetadata;
239
+ }>;
240
+ }
241
+
242
+ interface AvailablePlugin {
243
+ name: string;
244
+ version: string;
245
+ description?: string;
246
+ type?: string;
247
+ runtime: string[];
248
+ provides?: PluginProvides;
249
+ }
250
+
251
+ async function fetchAvailablePlugins(): Promise<AvailablePlugin[]> {
252
+ const searchUrl = new URL('https://registry.npmjs.org/-/v1/search');
253
+ searchUrl.searchParams.set('text', '@apiquest/plugin');
254
+ searchUrl.searchParams.set('size', '250');
255
+
256
+ const response = await fetch(searchUrl.toString(), {
257
+ headers: {
258
+ 'Accept': 'application/json'
259
+ }
260
+ });
261
+
262
+ if (!response.ok) {
263
+ throw new Error(`Registry search failed: ${response.status} ${response.statusText}`);
264
+ }
265
+
266
+ const data = await response.json() as RegistrySearchResponse;
267
+ const names = data.objects
268
+ .map(obj => obj.package?.name)
269
+ .filter((name): name is string => typeof name === 'string' && name.startsWith('@apiquest/plugin-'));
270
+
271
+ const pluginResults = await Promise.all(names.map(async (name) => {
272
+ try {
273
+ return await fetchRegistryPluginInfo(name);
274
+ } catch {
275
+ return null;
276
+ }
277
+ }));
278
+
279
+ return pluginResults
280
+ .filter((plugin): plugin is AvailablePlugin => plugin !== null)
281
+ .filter(plugin => plugin.runtime.includes('fracture'))
282
+ .sort((a, b) => a.name.localeCompare(b.name));
283
+ }
284
+
285
+ async function fetchRegistryPluginInfo(name: string): Promise<AvailablePlugin | null> {
286
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}`, {
287
+ headers: {
288
+ 'Accept': 'application/json'
289
+ }
290
+ });
291
+
292
+ if (!response.ok) {
293
+ return null;
294
+ }
295
+
296
+ const data = await response.json() as RegistryPackageResponse;
297
+ const latest = data['dist-tags']?.latest;
298
+ const versionData = latest !== undefined && data.versions !== undefined ? data.versions[latest] : undefined;
299
+ const metadata = versionData?.apiquest;
300
+
301
+ if (latest === undefined || metadata === undefined) {
302
+ return null;
303
+ }
304
+
305
+ const runtime = Array.isArray(metadata.runtime) ? metadata.runtime : [];
306
+ return {
307
+ name: data.name,
308
+ version: latest,
309
+ description: versionData?.description,
310
+ type: metadata.type,
311
+ runtime,
312
+ provides: metadata.capabilities?.provides
313
+ };
314
+ }
315
+
316
+ function printCapabilities(provides?: PluginProvides, indent = ' '): void {
317
+ if (provides === undefined) {
318
+ return;
319
+ }
320
+
321
+ if (provides.protocols !== undefined && provides.protocols.length > 0) {
322
+ console.log(`${indent}Protocols: ${provides.protocols.join(', ')}`);
323
+ }
324
+ if (provides.authTypes !== undefined && provides.authTypes.length > 0) {
325
+ console.log(`${indent}Auth Types: ${provides.authTypes.join(', ')}`);
326
+ }
327
+ if (provides.reportTypes !== undefined && provides.reportTypes.length > 0) {
328
+ console.log(`${indent}Report Types: ${provides.reportTypes.join(', ')}`);
329
+ }
330
+ if (provides.importFormats !== undefined && provides.importFormats.length > 0) {
331
+ console.log(`${indent}Import Formats: ${provides.importFormats.join(', ')}`);
332
+ }
333
+ if (provides.exportFormats !== undefined && provides.exportFormats.length > 0) {
334
+ console.log(`${indent}Export Formats: ${provides.exportFormats.join(', ')}`);
335
+ }
336
+ if (provides.visualizations !== undefined && provides.visualizations.length > 0) {
337
+ console.log(`${indent}Visualizations: ${provides.visualizations.join(', ')}`);
338
+ }
339
+ if (provides.provider !== undefined) {
340
+ console.log(`${indent}Provider: ${provides.provider}`);
341
+ }
342
+ }