wormajs 0.3.1 → 1.0.0-beta.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.
Files changed (52) hide show
  1. package/dist/bin/actions.js +162 -5
  2. package/dist/bin/cli.js +7 -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 +230 -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 +60 -4
  24. package/dist/index.js +17 -1
  25. package/dist/plugins/index.js +2 -2
  26. package/dist/plugins/presets/aiDoc.js +4 -0
  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/ai-doc/SKILL.md.handlebars +1 -1
  34. package/dist/template/presets/alova/common/services/{tag}.d.cts.handlebars +1 -1
  35. package/dist/template/presets/alova/module/services/{tag}.d.ts.handlebars +1 -1
  36. package/dist/template/presets/alova/partials/dts-fn-declare.handlebars +1 -1
  37. package/dist/template/presets/alova/partials/dts-types.handlebars +12 -0
  38. package/dist/template/presets/alova/typescript/services/{tag}.ts.handlebars +11 -6
  39. package/dist/template/presets/axios/partials/dts-types.handlebars +9 -5
  40. package/dist/template/presets/axios/typescript/services/{tag}.ts.handlebars +9 -5
  41. package/dist/template/presets/fetch/partials/dts-types.handlebars +8 -5
  42. package/dist/template/presets/fetch/typescript/services/{tag}.ts.handlebars +9 -5
  43. package/dist/template/presets/ky/partials/dts-types.handlebars +8 -5
  44. package/dist/template/presets/ky/typescript/services/{tag}.ts.handlebars +9 -5
  45. package/dist/utils/format.js +62 -15
  46. package/dist/utils/template.js +1 -1
  47. package/package.json +3 -2
  48. package/typings/index.d.ts +266 -13
  49. package/typings/plugins.d.ts +171 -80
  50. package/dist/plugins/presets/payloadModifier/hepler.js +0 -289
  51. package/dist/plugins/presets/platform/fastapi.js +0 -22
  52. package/dist/template/presets/alova/partials/dts-extra-config.handlebars +0 -8
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LATEST_CHANGE_ID = void 0;
7
+ exports.countChanges = countChanges;
8
+ exports.changesDirPath = changesDirPath;
9
+ exports.captureChange = captureChange;
10
+ exports.listChanges = listChanges;
11
+ exports.getChange = getChange;
12
+ const promises_1 = __importDefault(require("node:fs/promises"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const config_1 = require("../config");
15
+ const wormaJson_1 = require("../functions/wormaJson");
16
+ /** Alias accepted by {@link getChange} — resolves to the newest record. */
17
+ exports.LATEST_CHANGE_ID = 'latest';
18
+ /** Aggregate the change rows of every generator into flat counts. */
19
+ function countChanges(generators) {
20
+ let added = 0;
21
+ let removed = 0;
22
+ let modified = 0;
23
+ for (const generator of generators) {
24
+ for (const change of generator.changes) {
25
+ if (change.op === '+')
26
+ added++;
27
+ else if (change.op === '-')
28
+ removed++;
29
+ else
30
+ modified++;
31
+ }
32
+ }
33
+ return { added, removed, modified };
34
+ }
35
+ /**
36
+ * Read one generator entry, upgrading legacy api-level entries on the fly.
37
+ *
38
+ * Legacy records stored `added` / `removed` / `modified` api lists with
39
+ * `changedFields` and carried no `schemaVersion`; normalising both shapes into
40
+ * flat rows means every consumer only ever handles the source-document model.
41
+ */
42
+ function normalizeItem(raw) {
43
+ const output = String(raw?.output ?? '');
44
+ const serverName = raw?.serverName || undefined;
45
+ if (Array.isArray(raw?.changes))
46
+ return { output, serverName, resolvedInput: raw?.resolvedInput || undefined, changes: raw.changes };
47
+ const targetOf = (entry) => `${String(entry?.method ?? '').toUpperCase()} ${entry?.path ?? ''}`.trim();
48
+ const changes = [];
49
+ for (const entry of raw?.added ?? [])
50
+ changes.push({ op: '+', kind: 'api', target: targetOf(entry), detail: entry?.name || undefined, level: 'additive' });
51
+ for (const entry of raw?.removed ?? [])
52
+ changes.push({ op: '-', kind: 'api', target: targetOf(entry), detail: entry?.name || undefined, level: 'breaking' });
53
+ for (const entry of raw?.modified ?? []) {
54
+ changes.push({
55
+ op: '~',
56
+ kind: 'api',
57
+ target: targetOf(entry),
58
+ detail: (entry?.changedFields ?? []).join(', ') || undefined,
59
+ level: 'breaking',
60
+ });
61
+ }
62
+ return { output, serverName, changes };
63
+ }
64
+ /** `<cacheRoot>/changes/` — change records live next to the cache index. */
65
+ function changesDirPath(projectRoot) {
66
+ return node_path_1.default.join((0, wormaJson_1.cacheDirPath)(projectRoot), 'changes');
67
+ }
68
+ function recordFile(projectRoot, id) {
69
+ return node_path_1.default.join(changesDirPath(projectRoot), `${id}.json`);
70
+ }
71
+ function padId(seq) {
72
+ return String(seq).padStart(4, '0');
73
+ }
74
+ function toSummary(change) {
75
+ return {
76
+ id: change.id,
77
+ createdAt: change.createdAt,
78
+ summary: {
79
+ generators: change.generators.length,
80
+ ...countChanges(change.generators),
81
+ },
82
+ outputs: change.generators.map(g => g.output),
83
+ };
84
+ }
85
+ /**
86
+ * Highest id already present in `<cacheRoot>/changes/`.
87
+ *
88
+ * The record files — not `index.json#changeSeq` — are the authoritative source:
89
+ * the counter lives in a file that is rewritten by several concurrent writers
90
+ * and can be reset (corrupt / unreadable / hand-edited `index.json`). Allocating
91
+ * from `max(counter, existing ids) + 1` keeps ids monotonic and, more
92
+ * importantly, never reuses an id — a reused id would silently overwrite an
93
+ * older record.
94
+ */
95
+ async function maxChangeSeq(projectRoot) {
96
+ let files = [];
97
+ try {
98
+ files = await promises_1.default.readdir(changesDirPath(projectRoot));
99
+ }
100
+ catch {
101
+ return 0;
102
+ }
103
+ let max = 0;
104
+ for (const name of files) {
105
+ const seq = Number.parseInt(name.replace(/\.json$/, ''), 10);
106
+ if (Number.isFinite(seq) && seq > max)
107
+ max = seq;
108
+ }
109
+ return max;
110
+ }
111
+ async function readRecord(projectRoot, id) {
112
+ try {
113
+ const content = JSON.parse(await promises_1.default.readFile(recordFile(projectRoot, id), 'utf-8'));
114
+ if (!content || typeof content !== 'object')
115
+ return null;
116
+ const record = content;
117
+ return { ...record, generators: (record.generators ?? []).map(normalizeItem) };
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ }
123
+ /**
124
+ * Persist one change record.
125
+ *
126
+ * Called by `generate()` **only when something actually changed**. Allocates the
127
+ * next `changes/<NNNN>.json` id from `index.json#changeSeq` and prunes records
128
+ * that fall outside `changeHistoryLimit`.
129
+ *
130
+ * @returns the allocated id (e.g. `"0007"`)
131
+ */
132
+ async function captureChange(projectPath, change) {
133
+ // Never trust the counter alone: if it was reset (see {@link maxChangeSeq})
134
+ // `counter + 1` would collide with an existing record and overwrite it.
135
+ const [counter, existingMax] = await Promise.all([
136
+ (0, wormaJson_1.readChangeSeq)(projectPath),
137
+ maxChangeSeq(projectPath),
138
+ ]);
139
+ const seq = Math.max(counter, existingMax) + 1;
140
+ const id = padId(seq);
141
+ const record = {
142
+ ...change,
143
+ id,
144
+ createdAt: change.createdAt ?? Date.now(),
145
+ };
146
+ const dir = changesDirPath(projectPath);
147
+ await promises_1.default.mkdir(dir, { recursive: true });
148
+ await promises_1.default.writeFile(recordFile(projectPath, id), JSON.stringify(record));
149
+ // Persist the bumped counter; `writeCacheIndex` preserves existing entries.
150
+ await (0, wormaJson_1.writeCacheIndex)(projectPath, [], { changeSeq: seq });
151
+ await pruneChanges(projectPath);
152
+ return id;
153
+ }
154
+ /**
155
+ * Remove records outside the configured history window, keeping the newest
156
+ * `changeHistoryLimit` records. `changeHistoryLimit <= 0` means "keep everything".
157
+ *
158
+ * Ordering is by `createdAt` (not by id): ids can be out of chronological order
159
+ * when `index.json#changeSeq` was reset at some point, and pruning by id
160
+ * arithmetic would then delete the newest records instead of the oldest ones.
161
+ */
162
+ async function pruneChanges(projectPath) {
163
+ const limit = (0, config_1.getGlobalConfig)().changeHistoryLimit;
164
+ if (typeof limit !== 'number' || limit <= 0)
165
+ return;
166
+ // `listChanges` returns the records newest first.
167
+ const summaries = await listChanges(projectPath);
168
+ const stale = summaries.slice(limit);
169
+ await Promise.all(stale.map(s => promises_1.default.unlink(recordFile(projectPath, s.id)).catch(() => { })));
170
+ }
171
+ /**
172
+ * List recorded changes, newest first.
173
+ *
174
+ * Sorted by `createdAt` (id as tie-breaker) rather than by file name: an id is
175
+ * only chronological as long as `index.json#changeSeq` never resets, and a
176
+ * reset would otherwise make a brand-new record show up last.
177
+ */
178
+ async function listChanges(projectPath) {
179
+ const dir = changesDirPath(projectPath);
180
+ let files = [];
181
+ try {
182
+ files = await promises_1.default.readdir(dir);
183
+ }
184
+ catch {
185
+ return [];
186
+ }
187
+ const ids = files
188
+ .filter(name => name.endsWith('.json'))
189
+ .map(name => name.replace(/\.json$/, ''));
190
+ const summaries = [];
191
+ for (const id of ids) {
192
+ const record = await readRecord(projectPath, id);
193
+ if (record)
194
+ summaries.push(toSummary(record));
195
+ }
196
+ summaries.sort((a, b) => (b.createdAt - a.createdAt) || b.id.localeCompare(a.id));
197
+ return summaries;
198
+ }
199
+ /**
200
+ * Read a single change record.
201
+ *
202
+ * @param projectPath absolute path of the project root
203
+ * @param id `"0007"` or the alias `"latest"` (newest record)
204
+ */
205
+ async function getChange(projectPath, id) {
206
+ let resolvedId = id;
207
+ if (!id || id === exports.LATEST_CHANGE_ID) {
208
+ // Resolve the newest record by scanning the changes directory rather than
209
+ // trusting `index.json#changeSeq`: the counter can be stale or missing
210
+ // (pruned records, manual edits, or a generation that rewrote index.json
211
+ // without carrying the counter) while the record files themselves remain
212
+ // the authoritative source.
213
+ const latestId = await resolveLatestId(projectPath);
214
+ if (!latestId)
215
+ return undefined;
216
+ resolvedId = latestId;
217
+ }
218
+ const record = await readRecord(projectPath, resolvedId);
219
+ return record ?? undefined;
220
+ }
221
+ /**
222
+ * Return the newest change-record id.
223
+ *
224
+ * Resolved by `createdAt` (see {@link listChanges}) so `latest` keeps pointing
225
+ * at the most recent generation even when ids are not chronological.
226
+ */
227
+ async function resolveLatestId(projectRoot) {
228
+ const [newest] = await listChanges(projectRoot);
229
+ return newest?.id;
230
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiDiffKey = apiDiffKey;
4
+ exports.diffApis = diffApis;
5
+ exports.hasApiChanges = hasApiChanges;
6
+ const wormaJson_1 = require("../functions/wormaJson");
7
+ /**
8
+ * Fields compared when two APIs share the same `method` + `path` key.
9
+ * `name` / `tag` are included so that renames and tag moves are reported
10
+ * instead of being silently ignored.
11
+ */
12
+ const COMPARED_FIELDS = [
13
+ 'name',
14
+ 'tag',
15
+ 'response',
16
+ 'requestBody',
17
+ 'queryParameters',
18
+ 'pathParameters',
19
+ ];
20
+ /**
21
+ * Stable matching key for an API.
22
+ *
23
+ * `method` + `path` is used instead of `name` because it survives function
24
+ * renames: a renamed API is reported as *modified* rather than
25
+ * removed + added.
26
+ */
27
+ function apiDiffKey(api) {
28
+ return `${String(api.method ?? '').toLowerCase()} ${api.path ?? ''}`;
29
+ }
30
+ function toChange(api) {
31
+ return {
32
+ method: api.method,
33
+ path: api.path,
34
+ name: api.name,
35
+ tag: api.tag,
36
+ };
37
+ }
38
+ /**
39
+ * Diff two API lists at API level.
40
+ *
41
+ * @param oldApis API list as of the previous generation (from cache)
42
+ * @param newApis API list parsed from the current spec
43
+ */
44
+ function diffApis(oldApis = [], newApis = []) {
45
+ const oldMap = new Map();
46
+ for (const api of oldApis) {
47
+ oldMap.set(apiDiffKey(api), api);
48
+ }
49
+ const newMap = new Map();
50
+ for (const api of newApis) {
51
+ newMap.set(apiDiffKey(api), api);
52
+ }
53
+ const added = [];
54
+ const removed = [];
55
+ const modified = [];
56
+ for (const [key, api] of newMap) {
57
+ if (!oldMap.has(key))
58
+ added.push(toChange(api));
59
+ }
60
+ for (const [key, api] of oldMap) {
61
+ if (!newMap.has(key))
62
+ removed.push(toChange(api));
63
+ }
64
+ for (const [key, api] of newMap) {
65
+ const previous = oldMap.get(key);
66
+ if (!previous)
67
+ continue;
68
+ const changedFields = COMPARED_FIELDS.filter(field => (0, wormaJson_1.stableStringify)(previous[field]) !== (0, wormaJson_1.stableStringify)(api[field]));
69
+ if (changedFields.length > 0) {
70
+ modified.push({ ...toChange(api), changedFields: [...changedFields] });
71
+ }
72
+ }
73
+ const byKey = (a, b) => apiDiffKey(a).localeCompare(apiDiffKey(b));
74
+ added.sort(byKey);
75
+ removed.sort(byKey);
76
+ modified.sort(byKey);
77
+ return { added, removed, modified };
78
+ }
79
+ /** Whether a diff result contains any change at all. */
80
+ function hasApiChanges(diff) {
81
+ return diff.added.length > 0 || diff.removed.length > 0 || diff.modified.length > 0;
82
+ }