wormajs 1.0.0-beta.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.
- package/dist/bin/actions.js +23 -3
- package/dist/bin/cli.js +1 -0
- package/dist/functions/changeReport.js +35 -0
- package/dist/helper/template/index.js +53 -1
- package/dist/index.js +2 -1
- package/dist/plugins/presets/aiDoc.js +27 -2
- package/package.json +1 -1
- package/typings/index.d.ts +13 -0
package/dist/bin/actions.js
CHANGED
|
@@ -243,15 +243,35 @@ function createTable(head) {
|
|
|
243
243
|
style: { head: [], border: [] },
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
|
-
/** `worma diff` / `worma diff <id>` / `worma diff latest` */
|
|
247
|
-
async function actionDiff(id, { list, project }) {
|
|
246
|
+
/** `worma diff` / `worma diff <id>` / `worma diff latest` / `worma diff --remove <id>` */
|
|
247
|
+
async function actionDiff(id, { list, remove, project }) {
|
|
248
248
|
const projectPath = project
|
|
249
249
|
? (node_path_1.default.isAbsolute(project) ? project : node_path_1.default.resolve(process.cwd(), project))
|
|
250
250
|
: process.cwd();
|
|
251
251
|
// Mirror `actionGen`: always resolve the cache from CWD so monorepo
|
|
252
252
|
// sub-packages share one unified cache root.
|
|
253
253
|
(0, config_1.setGlobalConfig)({ cacheRoot: process.cwd() });
|
|
254
|
-
const { countChanges, getChange, listChanges } = await Promise.resolve().then(() => __importStar(require('../functions/changeReport')));
|
|
254
|
+
const { countChanges, getChange, listChanges, removeChange } = await Promise.resolve().then(() => __importStar(require('../functions/changeReport')));
|
|
255
|
+
// `worma diff --remove 0007` (also `worma diff 0007 --remove` / `--remove latest`)
|
|
256
|
+
if (remove) {
|
|
257
|
+
// `--remove` accepts the id itself; fall back to the positional argument so
|
|
258
|
+
// both `--remove 0007` and `0007 --remove` work.
|
|
259
|
+
const targetId = typeof remove === 'string' ? remove : id;
|
|
260
|
+
if (!targetId) {
|
|
261
|
+
console.log(`\n ${theme_1.theme.warning('?')} Nothing to remove: pass a change id, e.g. ${theme_1.theme.label('worma diff --remove 0007')}.\n`);
|
|
262
|
+
process.exitCode = 1;
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const removedId = await removeChange(projectPath, targetId);
|
|
266
|
+
if (!removedId) {
|
|
267
|
+
console.log(`\n ${theme_1.theme.warning('?')} No change record found for "${targetId}".\n`);
|
|
268
|
+
process.exitCode = 1;
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
console.log(`\n ${theme_1.theme.success('✔')} Removed change record ${theme_1.theme.label(removedId)}.`);
|
|
272
|
+
console.log(` ${theme_1.theme.dim('Run `worma diff` to list the remaining records.')}\n`);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
255
275
|
if (!id || list) {
|
|
256
276
|
const summaries = await listChanges(projectPath);
|
|
257
277
|
if (summaries.length === 0) {
|
package/dist/bin/cli.js
CHANGED
|
@@ -27,6 +27,7 @@ program
|
|
|
27
27
|
.description('browse recorded API changes')
|
|
28
28
|
.argument('[id]', 'change id (e.g. 0007) or `latest`; omit to list all')
|
|
29
29
|
.option('-l, --list', 'list all recorded changes')
|
|
30
|
+
.option('-r, --remove [id]', 'delete a recorded change (e.g. `worma diff --remove 0007`)')
|
|
30
31
|
.option('-p, --project <path>', 'project directory')
|
|
31
32
|
.action(actions_1.actionDiff);
|
|
32
33
|
program.parse(process.argv);
|
|
@@ -9,6 +9,7 @@ exports.changesDirPath = changesDirPath;
|
|
|
9
9
|
exports.captureChange = captureChange;
|
|
10
10
|
exports.listChanges = listChanges;
|
|
11
11
|
exports.getChange = getChange;
|
|
12
|
+
exports.removeChange = removeChange;
|
|
12
13
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
13
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
15
|
const config_1 = require("../config");
|
|
@@ -218,6 +219,40 @@ async function getChange(projectPath, id) {
|
|
|
218
219
|
const record = await readRecord(projectPath, resolvedId);
|
|
219
220
|
return record ?? undefined;
|
|
220
221
|
}
|
|
222
|
+
/** Ids are always the zero-padded sequence written by {@link captureChange}. */
|
|
223
|
+
const CHANGE_ID_RE = /^\d+$/;
|
|
224
|
+
/**
|
|
225
|
+
* Delete a single recorded change.
|
|
226
|
+
*
|
|
227
|
+
* @param projectPath absolute path of the project root
|
|
228
|
+
* @param id `"0007"` or the alias `"latest"` (newest record)
|
|
229
|
+
*
|
|
230
|
+
* `index.json#changeSeq` is deliberately left untouched: it only ever allocates
|
|
231
|
+
* new* ids, so keeping it monotonic guarantees the deleted id is never handed
|
|
232
|
+
* out again for a different record.
|
|
233
|
+
*
|
|
234
|
+
* @returns the id that was deleted, or `undefined` when nothing matched
|
|
235
|
+
*/
|
|
236
|
+
async function removeChange(projectPath, id) {
|
|
237
|
+
let resolvedId = id;
|
|
238
|
+
if (!id || id === exports.LATEST_CHANGE_ID) {
|
|
239
|
+
const latestId = await resolveLatestId(projectPath);
|
|
240
|
+
if (!latestId)
|
|
241
|
+
return undefined;
|
|
242
|
+
resolvedId = latestId;
|
|
243
|
+
}
|
|
244
|
+
// Guards against deleting anything outside the changes directory
|
|
245
|
+
// (`--remove ../../some-file` would otherwise unlink an arbitrary file).
|
|
246
|
+
if (!CHANGE_ID_RE.test(resolvedId))
|
|
247
|
+
return undefined;
|
|
248
|
+
try {
|
|
249
|
+
await promises_1.default.unlink(recordFile(projectPath, resolvedId));
|
|
250
|
+
return resolvedId;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
221
256
|
/**
|
|
222
257
|
* Return the newest change-record id.
|
|
223
258
|
*
|
|
@@ -387,7 +387,20 @@ class TemplateHelper {
|
|
|
387
387
|
// --- Phase 1: Per-tag streaming (render → collect → batch write) ---
|
|
388
388
|
const nonDirTagTpls = tpls.filter(f => !f.insideTagDir && f.templateType === 'tag');
|
|
389
389
|
const dirTpls = tpls.filter(f => f.insideTagDir);
|
|
390
|
-
|
|
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
|
+
}
|
|
391
404
|
let tagFilesWritten = 0;
|
|
392
405
|
logger_1.logger.debug('Phase 1: Per-tag streaming', {
|
|
393
406
|
totalTags: tags.length,
|
|
@@ -461,6 +474,45 @@ class TemplateHelper {
|
|
|
461
474
|
logger_1.logger.debug('Generation summary', { totalOutputFiles: allFilePaths.length });
|
|
462
475
|
return { filePaths: allFilePaths };
|
|
463
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
|
+
}
|
|
464
516
|
/**
|
|
465
517
|
* Remove generated artifacts of tags that no longer exist in the spec.
|
|
466
518
|
*
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ 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.writeSourceSnapshot = exports.sourceDocumentHash = exports.readSourceSnapshot = exports.diffSourceDocument = exports.diffApis = exports.apiDiffKey = exports.listChanges = exports.LATEST_CHANGE_ID = exports.getChange = exports.countChanges = exports.defineConfig = exports.createConfig = exports.setGlobalConfig = exports.checkUpdates = 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
21
|
var checkUpdates_1 = require("./checkUpdates");
|
|
22
22
|
Object.defineProperty(exports, "checkUpdates", { enumerable: true, get: function () { return checkUpdates_1.checkUpdates; } });
|
|
23
23
|
var config_1 = require("./config");
|
|
@@ -31,6 +31,7 @@ Object.defineProperty(exports, "countChanges", { enumerable: true, get: function
|
|
|
31
31
|
Object.defineProperty(exports, "getChange", { enumerable: true, get: function () { return changeReport_1.getChange; } });
|
|
32
32
|
Object.defineProperty(exports, "LATEST_CHANGE_ID", { enumerable: true, get: function () { return changeReport_1.LATEST_CHANGE_ID; } });
|
|
33
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; } });
|
|
34
35
|
var diffApis_1 = require("./functions/diffApis");
|
|
35
36
|
Object.defineProperty(exports, "apiDiffKey", { enumerable: true, get: function () { return diffApis_1.apiDiffKey; } });
|
|
36
37
|
Object.defineProperty(exports, "diffApis", { enumerable: true, get: function () { return diffApis_1.diffApis; } });
|
|
@@ -73,8 +73,15 @@ function aiDoc(config) {
|
|
|
73
73
|
});
|
|
74
74
|
if (agentValue) {
|
|
75
75
|
const agentsToInstall = resolveInstallAgents(agentValue);
|
|
76
|
-
|
|
77
|
-
|
|
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
|
*
|
package/package.json
CHANGED
package/typings/index.d.ts
CHANGED
|
@@ -658,6 +658,19 @@ export declare function listChanges(projectPath: string): Promise<ChangeSummary[
|
|
|
658
658
|
* @param id `"0007"` or the alias `"latest"` (newest record)
|
|
659
659
|
*/
|
|
660
660
|
export declare function getChange(projectPath: string, id: string): Promise<Change | undefined>;
|
|
661
|
+
/**
|
|
662
|
+
* Delete a single recorded change.
|
|
663
|
+
*
|
|
664
|
+
* @param projectPath absolute path of the project root
|
|
665
|
+
* @param id `"0007"` or the alias `"latest"` (newest record)
|
|
666
|
+
*
|
|
667
|
+
* `index.json#changeSeq` is deliberately left untouched: it only ever allocates
|
|
668
|
+
* new* ids, so keeping it monotonic guarantees the deleted id is never handed
|
|
669
|
+
* out again for a different record.
|
|
670
|
+
*
|
|
671
|
+
* @returns the id that was deleted, or `undefined` when nothing matched
|
|
672
|
+
*/
|
|
673
|
+
export declare function removeChange(projectPath: string, id: string): Promise<string | undefined>;
|
|
661
674
|
/** A newly added or removed API (identified by `method` + `path`). */
|
|
662
675
|
export interface ApiChange {
|
|
663
676
|
method: string;
|