wormajs 0.4.0 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/bin/actions.js +182 -5
  2. package/dist/bin/cli.js +8 -1
  3. package/dist/bin/renderer.js +2 -8
  4. package/dist/checkUpdates.js +98 -0
  5. package/dist/config.js +7 -0
  6. package/dist/constant.js +1 -2
  7. package/dist/core/WorkerPool.js +14 -0
  8. package/dist/core/loader/callingCodeLoader/helper.js +2 -3
  9. package/dist/core/loader/callingCodeLoader/index.js +1 -1
  10. package/dist/core/parser/openApiParser/helper.js +32 -19
  11. package/dist/core/parser/templateParser/index.js +25 -12
  12. package/dist/core/workerPool/index.js +2 -1
  13. package/dist/functions/changeReport.js +265 -0
  14. package/dist/functions/diffApis.js +82 -0
  15. package/dist/functions/diffDocument.js +542 -0
  16. package/dist/functions/sourceSnapshot.js +107 -0
  17. package/dist/functions/wormaJson.js +306 -66
  18. package/dist/generate.js +24 -2
  19. package/dist/helper/config/ConfigHelper.js +1 -2
  20. package/dist/helper/config/ConfigManager.js +7 -0
  21. package/dist/helper/config/GeneratorHelper.js +74 -21
  22. package/dist/helper/config/zType.js +24 -1
  23. package/dist/helper/template/index.js +113 -5
  24. package/dist/index.js +18 -1
  25. package/dist/plugins/index.js +2 -2
  26. package/dist/plugins/presets/aiDoc.js +27 -2
  27. package/dist/plugins/presets/payloadModifier/dsl.js +147 -0
  28. package/dist/plugins/presets/payloadModifier/index.js +122 -135
  29. package/dist/plugins/presets/payloadModifier/patch.js +171 -0
  30. package/dist/plugins/presets/payloadModifier/scope.js +109 -0
  31. package/dist/plugins/presets/platform/index.js +1 -3
  32. package/dist/plugins/presets/postman.js +105 -0
  33. package/dist/template/presets/alova/common/services/{tag}.d.cts.handlebars +1 -1
  34. package/dist/template/presets/alova/module/services/{tag}.d.ts.handlebars +1 -1
  35. package/dist/template/presets/alova/partials/dts-fn-declare.handlebars +1 -1
  36. package/dist/template/presets/alova/partials/dts-types.handlebars +12 -0
  37. package/dist/template/presets/alova/typescript/services/{tag}.ts.handlebars +11 -6
  38. package/dist/template/presets/axios/partials/dts-types.handlebars +9 -5
  39. package/dist/template/presets/axios/typescript/services/{tag}.ts.handlebars +9 -5
  40. package/dist/template/presets/fetch/partials/dts-types.handlebars +8 -5
  41. package/dist/template/presets/fetch/typescript/services/{tag}.ts.handlebars +9 -5
  42. package/dist/template/presets/ky/partials/dts-types.handlebars +8 -5
  43. package/dist/template/presets/ky/typescript/services/{tag}.ts.handlebars +9 -5
  44. package/dist/utils/format.js +62 -15
  45. package/dist/utils/template.js +1 -1
  46. package/package.json +3 -2
  47. package/typings/index.d.ts +279 -13
  48. package/typings/plugins.d.ts +166 -81
  49. package/dist/plugins/presets/payloadModifier/hepler.js +0 -347
  50. package/dist/plugins/presets/platform/fastapi.js +0 -22
  51. package/dist/template/presets/alova/partials/dts-extra-config.handlebars +0 -8
@@ -10,9 +10,11 @@ const constant_1 = require("../../constant");
10
10
  const parser_1 = require("../../core/parser");
11
11
  const helper_1 = require("../../core/parser/openApiParser/helper");
12
12
  const getAutoTemplateType_1 = __importDefault(require("../../functions/getAutoTemplateType"));
13
+ const sourceSnapshot_1 = require("../../functions/sourceSnapshot");
13
14
  const wormaJson_1 = require("../../functions/wormaJson");
14
15
  const helper_2 = require("../../helper");
15
16
  const progress_1 = require("../../helper/progress");
17
+ const format_1 = require("../../utils/format");
16
18
  const zType_1 = require("./zType");
17
19
  class GeneratorHelper {
18
20
  static instance;
@@ -29,6 +31,15 @@ class GeneratorHelper {
29
31
  bodyMediaType: 'application/json',
30
32
  type: constant_1.ConfigTypeEnum.AUTO,
31
33
  defaultRequire: false,
34
+ /**
35
+ * `transformConcurrency` is intentionally omitted: leaving it unset means
36
+ * "auto" (`min(64, max(8, cpus*4))`), which the parser resolves at runtime.
37
+ */
38
+ performance: {
39
+ workerPool: 'auto',
40
+ writeConcurrency: 32,
41
+ deterministicSort: true,
42
+ },
32
43
  });
33
44
  static getInstance() {
34
45
  if (!GeneratorHelper.instance) {
@@ -52,8 +63,13 @@ class GeneratorHelper {
52
63
  * @returns GeneratorHelper instance
53
64
  */
54
65
  async load(config) {
55
- // Merge with default config
56
- const mergedConfig = { ...this.defaultConfig, ...config };
66
+ // Merge with default config. `performance` is an object, so a plain spread
67
+ // would drop the per-field defaults as soon as the user sets only part of it.
68
+ const mergedConfig = {
69
+ ...this.defaultConfig,
70
+ ...config,
71
+ performance: { ...this.defaultConfig.performance, ...config.performance },
72
+ };
57
73
  // Validate configuration
58
74
  const validatedConfig = await GeneratorHelper.validateConfig(mergedConfig);
59
75
  // Update config
@@ -137,7 +153,7 @@ class GeneratorHelper {
137
153
  beforeSpecParse: opts?.beforeSpecParse,
138
154
  });
139
155
  }
140
- static async generate(config, { projectPath, force, tracker }) {
156
+ static async generate(config, { projectPath, tracker }) {
141
157
  const reporter = (plugin) => tracker?.reporterFor(plugin.name ?? 'plugin') ?? progress_1.noopReportProgress;
142
158
  const pluginDriver = new helper_2.PluginDriver(config.plugins, { reporter });
143
159
  const reportCore = (progress, message) => {
@@ -147,7 +163,6 @@ class GeneratorHelper {
147
163
  const pluginNames = (config.plugins || []).map(p => p.name).filter(Boolean);
148
164
  helper_2.logger.debug('Starting generation process', {
149
165
  projectPath,
150
- force,
151
166
  input: config.input,
152
167
  output: config.output,
153
168
  plugins: pluginCount,
@@ -173,6 +188,11 @@ class GeneratorHelper {
173
188
  reportCore(100, 'skipped: no openapi document');
174
189
  return { success: false, resolvedInput };
175
190
  }
191
+ // Requirement: the change baseline is the **source** document — the one
192
+ // parsed from the `beforeSpecParse` output, taken before the `specParsed`
193
+ // hooks run. `document` is the very object those hooks receive and they may
194
+ // mutate it in place, so the snapshot has to be taken right here.
195
+ const sourceDocumentText = (0, wormaJson_1.stableStringify)(document);
176
196
  helper_2.logger.debug('OpenAPI document parsed successfully', {
177
197
  resolvedUrl: resolvedInput,
178
198
  version: document?.info?.version,
@@ -247,22 +267,21 @@ class GeneratorHelper {
247
267
  // P2: Pass tagedApis to avoid re-grouping; apiHashCache avoids re-hashing same Api objects
248
268
  const newApis = templateData.allApis || [];
249
269
  const newHashInfo = (0, wormaJson_1.computePerTagHashes)(newApis, templateData.tagedApis);
270
+ // `generate()` is "call means generate": there is deliberately NO whole-run
271
+ // skip here any more. Change detection is `checkUpdates()`'s job; rendering
272
+ // stays incremental (only changed tags are re-rendered) as a pure
273
+ // optimization that does not alter the output.
274
+ const oldEntry = await (0, wormaJson_1.getCacheEntry)(projectPath, config.output);
250
275
  let changedTags;
251
- if (!force) {
252
- const oldEntry = await (0, wormaJson_1.getCacheEntry)(projectPath, config.output);
253
- if (oldEntry) {
254
- if (oldEntry.hash === newHashInfo.hash) {
255
- helper_2.logger.debug('Template data unchanged (hash match), skipping generation');
256
- reportCore(100, 'skipped: template data unchanged');
257
- return { success: false, resolvedInput };
258
- }
259
- // Compute which tags changed for incremental rendering
260
- changedTags = (0, wormaJson_1.diffChangedTags)(oldEntry.tags, newHashInfo.tags);
261
- helper_2.logger.debug('Incremental update detected', {
262
- totalTags: Object.keys(newHashInfo.tags).length,
263
- changedTags: changedTags.size,
264
- });
265
- }
276
+ if ((0, wormaJson_1.hasGenerationBaseline)(oldEntry)) {
277
+ changedTags = (0, wormaJson_1.diffChangedTags)(oldEntry.tags, newHashInfo.tags);
278
+ helper_2.logger.debug('Incremental update detected', {
279
+ totalTags: Object.keys(newHashInfo.tags).length,
280
+ changedTags: changedTags.size,
281
+ });
282
+ }
283
+ else {
284
+ helper_2.logger.debug('No render baseline — rendering every tag');
266
285
  }
267
286
  reportCore(70, 'beforeCodeGenerate');
268
287
  helper_2.logger.debug('Running beforeCodeGenerate hook');
@@ -288,7 +307,7 @@ class GeneratorHelper {
288
307
  // 9.2.1: Unified streaming pipeline — render + beforeFileWrite hooks + write
289
308
  const perf = config.performance;
290
309
  const writeConcurrency = perf?.writeConcurrency ?? 32;
291
- const formatFile = perf?.formatFile ?? true;
310
+ const formatFile = (0, format_1.isFormatEnabled)();
292
311
  helper_2.logger.debug('Starting template generation', {
293
312
  writeConcurrency,
294
313
  formatFile,
@@ -340,7 +359,41 @@ class GeneratorHelper {
340
359
  reportCore(100, `failed: ${codeGenError.message}`);
341
360
  throw codeGenError;
342
361
  }
343
- return { success: true, resolvedInput };
362
+ // Requirement A: refresh the source-level baseline with the very same raw
363
+ // text that was just parsed — zero extra requests. Only the `source`
364
+ // sub-field is touched, so the render baseline (`hash` / `tags`) is kept.
365
+ if (openApiResult.rawText) {
366
+ await (0, wormaJson_1.updateSourceBaseline)(projectPath, config.output, {
367
+ resolvedInput,
368
+ rawHash: (0, wormaJson_1.computeSpecHash)(openApiResult.rawText),
369
+ updatedAt: Date.now(),
370
+ }, config.serverName ?? '');
371
+ }
372
+ // Requirement B: the source-document diff runs only **after** a successful
373
+ // generation, so a failed run never pays for the comparison nor advances the
374
+ // baseline the next run diffs against.
375
+ let change;
376
+ try {
377
+ const sourceChanges = await (0, sourceSnapshot_1.collectSourceChanges)({
378
+ projectRoot: projectPath,
379
+ outputPath: config.output,
380
+ documentText: sourceDocumentText,
381
+ resolvedInput,
382
+ });
383
+ if (sourceChanges) {
384
+ change = {
385
+ output: config.output,
386
+ serverName: config.serverName || templateData.title || '',
387
+ resolvedInput,
388
+ changes: sourceChanges,
389
+ };
390
+ }
391
+ }
392
+ catch (error) {
393
+ // Recording change history must never fail the generation itself.
394
+ helper_2.logger.debug('Failed to detect source changes', { error: error?.message });
395
+ }
396
+ return { success: true, resolvedInput, change };
344
397
  }
345
398
  }
346
399
  exports.GeneratorHelper = GeneratorHelper;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.zConfig = exports.zGeneratorConfig = exports._zGeneratorConfig = exports.zApiPlugin = exports.zFetchOptions = exports.zHandleApi = exports.zApiDescriptor = exports.zTemplateResult = exports.zTemplateType = exports.zConfigType = void 0;
6
+ exports.zConfig = exports.zGeneratorConfig = exports._zGeneratorConfig = exports.zPerformanceConfig = exports.zApiPlugin = exports.zFetchOptions = exports.zHandleApi = exports.zApiDescriptor = exports.zTemplateResult = exports.zTemplateType = exports.zConfigType = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
8
  const v3_1 = require("zod/v3"); // v4 is unstable, temporarily using v3
9
9
  /**
@@ -34,6 +34,20 @@ exports.zApiPlugin = v3_1.z.object({
34
34
  getTemplate: v3_1.z.function().optional(),
35
35
  onHandlebarsCreated: v3_1.z.function().optional(),
36
36
  });
37
+ /**
38
+ * Performance tuning options. Declared explicitly so zod's default strip
39
+ * behavior does not drop `generator[].performance` during validation.
40
+ */
41
+ exports.zPerformanceConfig = v3_1.z.object({
42
+ /** `'auto'` adapts the pool size to the API count, a number pins it, `false` disables workers */
43
+ workerPool: v3_1.z.union([v3_1.z.literal('auto'), v3_1.z.number(), v3_1.z.literal(false)]).optional(),
44
+ /** Max concurrency for the transform phase; defaults to `min(64, max(8, cpus*4))` */
45
+ transformConcurrency: v3_1.z.number().optional(),
46
+ /** Max parallelism for file writes; default 32 */
47
+ writeConcurrency: v3_1.z.number().optional(),
48
+ /** Sort components alphabetically for deterministic output; default true */
49
+ deterministicSort: v3_1.z.boolean().optional(),
50
+ });
37
51
  exports._zGeneratorConfig = v3_1.z.object({
38
52
  /**
39
53
  * Openapi file path, it supports json and yaml file, and network url.
@@ -109,6 +123,10 @@ exports._zGeneratorConfig = v3_1.z.object({
109
123
  * plugin will be executed before `handleApi`
110
124
  */
111
125
  plugins: v3_1.z.array(exports.zApiPlugin).optional(),
126
+ /**
127
+ * Performance tuning options for code generation.
128
+ */
129
+ performance: exports.zPerformanceConfig.optional(),
112
130
  /**
113
131
  * Filter or convert the generated api function and return a new `apiDescriptor` to generate the api.
114
132
  * When this function is not specified, `apiDescriptor` object is not converted.
@@ -143,6 +161,11 @@ exports._zGeneratorConfig = v3_1.z.object({
143
161
  });
144
162
  exports.zGeneratorConfig = exports._zGeneratorConfig;
145
163
  exports.zConfig = v3_1.z.object({
164
+ /**
165
+ * 产物格式化配置(oxfmt)。不做结构校验:字段原样透传给 oxfmt,由 oxfmt 自行校验并报错。
166
+ * 声明在 schema 中仅为避免 zod 的默认 strip 行为把该字段丢弃。
167
+ */
168
+ format: v3_1.z.any().optional(),
146
169
  /**
147
170
  * API generation settings are arrays. Each item represents an automatically generated rule, including the generated input and output directories, specification file addresses, etc.
148
171
  * Currently, only OpenAPI specifications are supported, including OpenAPI 2.0 and 3.0 specifications.
@@ -61,6 +61,28 @@ function joinPath(...parts) {
61
61
  const p = node_path_1.default.join(...parts);
62
62
  return p ? normalizeSlashes(p) : p;
63
63
  }
64
+ /**
65
+ * Recursively delete a directory, ignoring anything that is not there.
66
+ * Implemented with readdir/unlink/rmdir instead of `fs.rm` so it also works on
67
+ * virtual filesystems that do not implement `rm`.
68
+ */
69
+ async function removeDirRecursive(dir) {
70
+ let entries = [];
71
+ try {
72
+ entries = await promises_1.default.readdir(dir, { withFileTypes: true });
73
+ }
74
+ catch {
75
+ return;
76
+ }
77
+ for (const entry of entries) {
78
+ const full = node_path_1.default.join(dir, entry.name);
79
+ if (entry.isDirectory())
80
+ await removeDirRecursive(full);
81
+ else
82
+ await promises_1.default.unlink(full).catch(() => { });
83
+ }
84
+ await promises_1.default.rmdir(dir).catch(() => { });
85
+ }
64
86
  class TemplateHelper {
65
87
  static instance;
66
88
  /** Runtime cache of parsed template data, keyed by `${projectPath}::${output}`. Flushed to disk via flushAllData. */
@@ -286,7 +308,7 @@ class TemplateHelper {
286
308
  }
287
309
  /**
288
310
  * 9.2.3: Private file writing — now internal to the streaming pipeline.
289
- * Prettier formatting applied at file level (9.5.2).
311
+ * oxfmt formatting applied at file level (9.5.2).
290
312
  */
291
313
  async outputFiles(files, output, writeConcurrency = 32, formatFile = true) {
292
314
  const entries = Object.entries(files);
@@ -308,14 +330,14 @@ class TemplateHelper {
308
330
  const batch = entries.slice(i, i + concurrency);
309
331
  await Promise.all(batch.map(async ([rp, content]) => {
310
332
  const op = node_path_1.default.isAbsolute(rp) ? rp : node_path_1.default.join(output, rp);
311
- // 9.5.2: Apply prettier at file level for .ts/.js files
333
+ // 9.5.2: Apply oxfmt at file level for .ts/.js files
312
334
  let finalContent = content;
313
335
  if (formatFile && /\.(?:ts|js|mjs|cjs|tsx|jsx)$/.test(rp)) {
314
336
  try {
315
- finalContent = await (0, utils_1.format)(content);
337
+ finalContent = await (0, utils_1.format)(rp, content);
316
338
  }
317
339
  catch {
318
- // Prettier format failed, use original content
340
+ // oxfmt format failed, use original content
319
341
  }
320
342
  }
321
343
  return promises_1.default.writeFile(op, finalContent);
@@ -365,7 +387,20 @@ class TemplateHelper {
365
387
  // --- Phase 1: Per-tag streaming (render → collect → batch write) ---
366
388
  const nonDirTagTpls = tpls.filter(f => !f.insideTagDir && f.templateType === 'tag');
367
389
  const dirTpls = tpls.filter(f => f.insideTagDir);
368
- const effectiveTags = changedTags ? tags.filter(t => changedTags.has(t)) : tags;
390
+ // Incremental rendering normally skips a tag whose hash is unchanged, which
391
+ // assumes its previously generated files are still on disk. If those files
392
+ // were deleted manually while the cache survived, the unchanged tag would
393
+ // never be emitted again. So an unchanged tag is also re-rendered whenever
394
+ // any of its expected artifacts is missing from the output directory.
395
+ let effectiveTags;
396
+ if (changedTags) {
397
+ const unchangedTags = tags.filter(t => !changedTags.has(t));
398
+ const missingTags = await this.collectTagsWithMissingArtifacts(unchangedTags, nonDirTagTpls, dirTpls, tagApisMap, outputDir);
399
+ effectiveTags = tags.filter(t => changedTags.has(t) || missingTags.has(t));
400
+ }
401
+ else {
402
+ effectiveTags = tags;
403
+ }
369
404
  let tagFilesWritten = 0;
370
405
  logger_1.logger.debug('Phase 1: Per-tag streaming', {
371
406
  totalTags: tags.length,
@@ -431,10 +466,83 @@ class TemplateHelper {
431
466
  }
432
467
  }
433
468
  await this.applyHooksAndWrite(globalFiles, outputDir, beforeFileWrite, undefined, undefined, undefined, writeConcurrency, formatFile, allFilePaths);
469
+ // --- Phase 3: drop orphaned tag artifacts ---
470
+ // Tags that existed before but are gone from the spec must not leave stale
471
+ // files/directories behind in the output directory.
472
+ await this.removeOrphanTagArtifacts(changedTags, tags, tpls, outputDir);
434
473
  logger_1.logger.debug('Phase 2 complete', { globalFilesWritten: Object.keys(globalFiles).length });
435
474
  logger_1.logger.debug('Generation summary', { totalOutputFiles: allFilePaths.length });
436
475
  return { filePaths: allFilePaths };
437
476
  }
477
+ /**
478
+ * Detect tags whose previously generated artifacts are missing from disk.
479
+ *
480
+ * Mirrors the path derivation used by `renderOne` / `expandByApi` to compute
481
+ * the expected output path of every per-tag file without rendering anything,
482
+ * then returns the tags that must be re-rendered because at least one of
483
+ * their files is gone.
484
+ */
485
+ async collectTagsWithMissingArtifacts(candidateTags, nonDirTagTpls, dirTpls, tagApisMap, outputDir) {
486
+ const missingTags = new Set();
487
+ for (const tag of candidateTags) {
488
+ const tagApis = tagApisMap.get(tag);
489
+ const expectedPaths = [];
490
+ // Flat `[tag]`-named templates, e.g. `services/[tag].ts.handlebars`
491
+ for (const tf of nonDirTagTpls) {
492
+ expectedPaths.push(stripExt(normalizeSlashes(tf.relativePath.replace(constant_1.TemplatePlaceholder.TAG, tag))));
493
+ }
494
+ // Tag-dir templates, e.g. `[tag]/index.ts` and `[tag]/[api].ts`
495
+ for (const tf of dirTpls) {
496
+ if (tf.templateType === 'api') {
497
+ for (const api of tagApis?.apis || []) {
498
+ expectedPaths.push(stripExt(normalizeSlashes(tf.relativePath
499
+ .replace(constant_1.TemplatePlaceholder.TAG, tag)
500
+ .replace(constant_1.TemplatePlaceholder.API, api.name))));
501
+ }
502
+ }
503
+ else {
504
+ expectedPaths.push(stripExt(normalizeSlashes(tf.relativePath.replace(constant_1.TemplatePlaceholder.TAG, tag))));
505
+ }
506
+ }
507
+ for (const relPath of expectedPaths) {
508
+ if (!(await (0, utils_1.existsPromise)(node_path_1.default.join(outputDir, relPath)))) {
509
+ missingTags.add(tag);
510
+ break;
511
+ }
512
+ }
513
+ }
514
+ return missingTags;
515
+ }
516
+ /**
517
+ * Remove generated artifacts of tags that no longer exist in the spec.
518
+ *
519
+ * `changedTags` contains both changed and removed tags; the ones missing from
520
+ * `tags` (the current tag list) are the removed ones. For those we delete:
521
+ * - the `[tag]` directory produced by tag-dir templates
522
+ * - the files produced by `[tag]`-named templates
523
+ */
524
+ async removeOrphanTagArtifacts(changedTags, tags, tpls, outputDir) {
525
+ if (!changedTags || changedTags.size === 0)
526
+ return;
527
+ const removedTags = [...changedTags].filter(tag => !tags.includes(tag));
528
+ if (removedTags.length === 0)
529
+ return;
530
+ const tagDirTpls = tpls.filter(f => f.insideTagDir);
531
+ const tagTpls = tpls.filter(f => !f.insideTagDir && f.templateType === 'tag');
532
+ for (const tag of removedTags) {
533
+ // Tag-dir templates: delete the whole `[tag]` directory (may be nested)
534
+ const dirs = new Set(tagDirTpls.map(tf => node_path_1.default.dirname(tf.relativePath.replace(constant_1.TemplatePlaceholder.TAG, tag))));
535
+ for (const dir of dirs) {
536
+ await removeDirRecursive(node_path_1.default.join(outputDir, dir));
537
+ }
538
+ // Flat `[tag]`-named templates: delete the resolved file
539
+ for (const tf of tagTpls) {
540
+ const relPath = stripExt(normalizeSlashes(tf.relativePath.replace(constant_1.TemplatePlaceholder.TAG, tag)));
541
+ await promises_1.default.unlink(node_path_1.default.join(outputDir, relPath)).catch(() => { });
542
+ }
543
+ }
544
+ logger_1.logger.debug('Removed orphaned tag artifacts', { tags: removedTags });
545
+ }
438
546
  async applyHooksAndWrite(files, outputDir, beforeFileWrite, templateType, tag, api, writeConcurrency, formatFile, allFilePaths) {
439
547
  if (beforeFileWrite) {
440
548
  // Apply beforeFileWrite to each file sequentially for determinism
package/dist/index.js CHANGED
@@ -17,13 +17,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.resolveWorkspaces = exports.logger = exports.generate = exports.defineConfig = exports.createConfig = exports.setGlobalConfig = void 0;
20
+ exports.resolveWorkspaces = exports.logger = exports.generate = exports.writeSourceSnapshot = exports.sourceDocumentHash = exports.readSourceSnapshot = exports.diffSourceDocument = exports.diffApis = exports.apiDiffKey = exports.removeChange = exports.listChanges = exports.LATEST_CHANGE_ID = exports.getChange = exports.countChanges = exports.defineConfig = exports.createConfig = exports.setGlobalConfig = exports.checkUpdates = void 0;
21
+ var checkUpdates_1 = require("./checkUpdates");
22
+ Object.defineProperty(exports, "checkUpdates", { enumerable: true, get: function () { return checkUpdates_1.checkUpdates; } });
21
23
  var config_1 = require("./config");
22
24
  Object.defineProperty(exports, "setGlobalConfig", { enumerable: true, get: function () { return config_1.setGlobalConfig; } });
23
25
  var createConfig_1 = require("./createConfig");
24
26
  Object.defineProperty(exports, "createConfig", { enumerable: true, get: function () { return __importDefault(createConfig_1).default; } });
25
27
  var defineConfig_1 = require("./defineConfig");
26
28
  Object.defineProperty(exports, "defineConfig", { enumerable: true, get: function () { return defineConfig_1.defineConfig; } });
29
+ var changeReport_1 = require("./functions/changeReport");
30
+ Object.defineProperty(exports, "countChanges", { enumerable: true, get: function () { return changeReport_1.countChanges; } });
31
+ Object.defineProperty(exports, "getChange", { enumerable: true, get: function () { return changeReport_1.getChange; } });
32
+ Object.defineProperty(exports, "LATEST_CHANGE_ID", { enumerable: true, get: function () { return changeReport_1.LATEST_CHANGE_ID; } });
33
+ Object.defineProperty(exports, "listChanges", { enumerable: true, get: function () { return changeReport_1.listChanges; } });
34
+ Object.defineProperty(exports, "removeChange", { enumerable: true, get: function () { return changeReport_1.removeChange; } });
35
+ var diffApis_1 = require("./functions/diffApis");
36
+ Object.defineProperty(exports, "apiDiffKey", { enumerable: true, get: function () { return diffApis_1.apiDiffKey; } });
37
+ Object.defineProperty(exports, "diffApis", { enumerable: true, get: function () { return diffApis_1.diffApis; } });
38
+ var diffDocument_1 = require("./functions/diffDocument");
39
+ Object.defineProperty(exports, "diffSourceDocument", { enumerable: true, get: function () { return diffDocument_1.diffSourceDocument; } });
40
+ var sourceSnapshot_1 = require("./functions/sourceSnapshot");
41
+ Object.defineProperty(exports, "readSourceSnapshot", { enumerable: true, get: function () { return sourceSnapshot_1.readSourceSnapshot; } });
42
+ Object.defineProperty(exports, "sourceDocumentHash", { enumerable: true, get: function () { return sourceSnapshot_1.sourceDocumentHash; } });
43
+ Object.defineProperty(exports, "writeSourceSnapshot", { enumerable: true, get: function () { return sourceSnapshot_1.writeSourceSnapshot; } });
27
44
  var generate_1 = require("./generate");
28
45
  Object.defineProperty(exports, "generate", { enumerable: true, get: function () { return __importDefault(generate_1).default; } });
29
46
  var helper_1 = require("./helper");
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.ky = exports.config = exports.axios = exports.alovaGlobals = exports.alova = exports.yapi = exports.swagger = exports.knife4j = exports.fastapi = void 0;
17
+ exports.ky = exports.config = exports.axios = exports.alovaGlobals = exports.alova = exports.yapi = exports.swagger = exports.knife4j = void 0;
18
18
  __exportStar(require("./createPlugin"), exports);
19
19
  __exportStar(require("./presets/aiDoc"), exports);
20
20
  __exportStar(require("./presets/apifox"), exports);
@@ -22,10 +22,10 @@ __exportStar(require("./presets/filterApi"), exports);
22
22
  __exportStar(require("./presets/importType"), exports);
23
23
  __exportStar(require("./presets/payloadModifier"), exports);
24
24
  var platform_1 = require("./presets/platform");
25
- Object.defineProperty(exports, "fastapi", { enumerable: true, get: function () { return platform_1.fastapi; } });
26
25
  Object.defineProperty(exports, "knife4j", { enumerable: true, get: function () { return platform_1.knife4j; } });
27
26
  Object.defineProperty(exports, "swagger", { enumerable: true, get: function () { return platform_1.swagger; } });
28
27
  Object.defineProperty(exports, "yapi", { enumerable: true, get: function () { return platform_1.yapi; } });
28
+ __exportStar(require("./presets/postman"), exports);
29
29
  __exportStar(require("./presets/rename"), exports);
30
30
  __exportStar(require("./presets/tagModifier"), exports);
31
31
  // Template preset plugins
@@ -73,8 +73,15 @@ function aiDoc(config) {
73
73
  });
74
74
  if (agentValue) {
75
75
  const agentsToInstall = resolveInstallAgents(agentValue);
76
- for (const agent of agentsToInstall) {
77
- installSkill(aidocsDir, agent, projectPath);
76
+ if (agentsToInstall.length) {
77
+ for (const agent of agentsToInstall) {
78
+ installSkill(aidocsDir, agent, projectPath);
79
+ }
80
+ // The skill now lives in each agent's own skills directory. Keeping the
81
+ // generated copy under the output directory would store the same files
82
+ // twice, so it is dropped — but only after *every* install succeeded:
83
+ // a failing install throws above and leaves the source in place.
84
+ removeSkillSourceDir(aidocsDir);
78
85
  }
79
86
  }
80
87
  },
@@ -147,6 +154,24 @@ function installSkill(skillPath, agent, projectPath) {
147
154
  throw logger_1.logger.throwError(error);
148
155
  }
149
156
  }
157
+ /**
158
+ * Delete the generated skill directory once it has been installed.
159
+ *
160
+ * `skills add` copies the whole directory into the agent's own skills folder,
161
+ * so the copy under the generator output is pure duplication.
162
+ *
163
+ * A removal failure is reported but never thrown: the install already
164
+ * succeeded, and failing the whole generation over a leftover directory would
165
+ * be worse than keeping it.
166
+ */
167
+ function removeSkillSourceDir(skillPath) {
168
+ try {
169
+ node_fs_1.default.rmSync(skillPath, { recursive: true, force: true });
170
+ }
171
+ catch (error) {
172
+ console.error(`${prefix}Failed to remove the generated skill directory "${skillPath}".`, error?.stack ?? error);
173
+ }
174
+ }
150
175
  /**
151
176
  * Parse a `key=value` configuration file (same format as an environment file).
152
177
  *
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TYPE_FAMILY_KEYS = void 0;
4
+ exports.isSchemaPrimitive = isSchemaPrimitive;
5
+ exports.clearTypeFamily = clearTypeFamily;
6
+ exports.stripRef = stripRef;
7
+ exports.dslToSchemaObject = dslToSchemaObject;
8
+ exports.inferEnumType = inferEnumType;
9
+ const VALID_PRIMITIVES = new Set([
10
+ 'number',
11
+ 'string',
12
+ 'boolean',
13
+ 'undefined',
14
+ 'null',
15
+ 'unknown',
16
+ 'any',
17
+ 'never',
18
+ ]);
19
+ /**
20
+ * Keys describing the type shape. They are dropped when a type is replaced so that no
21
+ * leftover of the previous type survives (e.g. a stale `format: 'int64'`).
22
+ * Everything else — documentation keys above all — is kept.
23
+ */
24
+ exports.TYPE_FAMILY_KEYS = [
25
+ 'type',
26
+ 'properties',
27
+ 'items',
28
+ 'enum',
29
+ 'oneOf',
30
+ 'anyOf',
31
+ 'allOf',
32
+ 'format',
33
+ 'required',
34
+ ];
35
+ function isSchemaPrimitive(value) {
36
+ return typeof value === 'string' && VALID_PRIMITIVES.has(value);
37
+ }
38
+ /** Rejects anything that is not a known type expression. */
39
+ function assertPrimitive(value) {
40
+ if (!VALID_PRIMITIVES.has(value)) {
41
+ throw new Error(`[payloadModifier] Invalid schema type "${value}". Must be one of: ${[...VALID_PRIMITIVES].join(', ')}`);
42
+ }
43
+ return value;
44
+ }
45
+ /** Copies a schema keeping every documentation key and dropping every type-family key. */
46
+ function clearTypeFamily(schema) {
47
+ const rest = { ...schema };
48
+ for (const key of exports.TYPE_FAMILY_KEYS) {
49
+ delete rest[key];
50
+ }
51
+ return rest;
52
+ }
53
+ /**
54
+ * Drops the internal `_$ref` marker stamped by the dereference step. Without this, a
55
+ * replaced node would still look like a reference to the original component and the
56
+ * downstream merge step would restore the component instead of keeping the change.
57
+ */
58
+ function stripRef(schema) {
59
+ if (!schema || typeof schema !== 'object') {
60
+ return schema;
61
+ }
62
+ const rest = { ...schema };
63
+ delete rest._$ref;
64
+ return rest;
65
+ }
66
+ /**
67
+ * Converts a spec DSL expression into a raw OpenAPI schema object.
68
+ * This is the only conversion direction the plugin has: nothing ever converts a schema
69
+ * back into the DSL, so no documentation can be lost along the way.
70
+ */
71
+ function dslToSchemaObject(dsl) {
72
+ if (typeof dsl === 'string') {
73
+ return { type: assertPrimitive(dsl) };
74
+ }
75
+ if (Array.isArray(dsl)) {
76
+ return { type: 'array', items: dslToItems(dsl) };
77
+ }
78
+ const spec = dsl;
79
+ if (Array.isArray(spec.oneOf)) {
80
+ return { oneOf: toSchemaList(spec.oneOf) };
81
+ }
82
+ if (Array.isArray(spec.anyOf)) {
83
+ return { anyOf: toSchemaList(spec.anyOf) };
84
+ }
85
+ if (Array.isArray(spec.allOf)) {
86
+ return { allOf: toSchemaList(spec.allOf) };
87
+ }
88
+ if (Array.isArray(spec.enum)) {
89
+ return enumToSchemaObject(spec.enum, spec.type);
90
+ }
91
+ // object shorthand
92
+ const properties = {};
93
+ for (const key of Object.keys(spec)) {
94
+ properties[key] = dslToSchemaObject(spec[key]);
95
+ }
96
+ return { type: 'object', properties, required: Object.keys(spec) };
97
+ }
98
+ function toSchemaList(list) {
99
+ return list.map(item => dslToSchemaObject(item));
100
+ }
101
+ /** A single element becomes `items`, multiple elements become a tuple (array) `items`. */
102
+ function dslToItems(list) {
103
+ const items = toSchemaList(list);
104
+ return items.length === 1 ? items[0] : items;
105
+ }
106
+ function enumToSchemaObject(values, declared) {
107
+ const result = { enum: values };
108
+ const type = declared !== undefined
109
+ ? enumTypeToSchemaType(assertPrimitive(declared), values)
110
+ : inferEnumType(values);
111
+ if (type) {
112
+ result.type = type;
113
+ }
114
+ return result;
115
+ }
116
+ /**
117
+ * Maps a declared DSL type onto the OpenAPI type of an enum: `number` becomes `integer`
118
+ * when every value is an integer, otherwise it stays `number`. TS-only types such as
119
+ * `any` or `never` are written through as-is.
120
+ */
121
+ function enumTypeToSchemaType(declared, values) {
122
+ if (declared === 'number') {
123
+ return values.every(value => typeof value === 'number' && Number.isInteger(value))
124
+ ? 'integer'
125
+ : 'number';
126
+ }
127
+ return declared;
128
+ }
129
+ /**
130
+ * Infers the OpenAPI type of an enum from its values. Returns `undefined` for mixed or
131
+ * empty values so no (possibly wrong) type is written.
132
+ */
133
+ function inferEnumType(values) {
134
+ if (!values.length) {
135
+ return undefined;
136
+ }
137
+ if (values.every(value => typeof value === 'string')) {
138
+ return 'string';
139
+ }
140
+ if (values.every(value => typeof value === 'boolean')) {
141
+ return 'boolean';
142
+ }
143
+ if (values.every(value => typeof value === 'number')) {
144
+ return values.every(value => Number.isInteger(value)) ? 'integer' : 'number';
145
+ }
146
+ return undefined;
147
+ }