@hanv89/azure-arch-skill 0.8.0 → 0.9.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.
- package/dist/adapters/_shared.js +81 -8
- package/package.json +1 -1
package/dist/adapters/_shared.js
CHANGED
|
@@ -408,6 +408,10 @@ async function withFatalReturn(fn) {
|
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
410
|
exports.withFatalReturn = withFatalReturn;
|
|
411
|
+
// Persisted at install time so uninstall can iterate the file list without
|
|
412
|
+
// re-fetching the manifest over the network. Hidden filename so it doesn't
|
|
413
|
+
// clutter the user-visible skill folder.
|
|
414
|
+
const PERSISTED_MANIFEST_BASENAME = ".azure-arch-skill-manifest.json";
|
|
411
415
|
function makeFolderInstallAdapter(cfg) {
|
|
412
416
|
const defaultTarget = () => path.join(cfg.rootDir(), "skills", exports.SKILL_NAME);
|
|
413
417
|
const defaultSkillsRoot = () => path.join(cfg.rootDir(), "skills");
|
|
@@ -458,6 +462,9 @@ function makeFolderInstallAdapter(cfg) {
|
|
|
458
462
|
const body = await fetchText(`${base}/${src}`);
|
|
459
463
|
await fs.writeFile(path.join(target, dest), body, "utf8");
|
|
460
464
|
}
|
|
465
|
+
// Persist the manifest so uninstall can iterate the file list without
|
|
466
|
+
// re-fetching from the network.
|
|
467
|
+
await fs.writeFile(path.join(target, PERSISTED_MANIFEST_BASENAME), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
461
468
|
process.stdout.write(`installed ${exports.SKILL_NAME} to ${target}\n`);
|
|
462
469
|
return 0;
|
|
463
470
|
});
|
|
@@ -474,23 +481,89 @@ function makeFolderInstallAdapter(cfg) {
|
|
|
474
481
|
if (!ours) {
|
|
475
482
|
throw new Error(`refusing to remove ${target} - not an azure-architecture-diagram skill folder (no matching SKILL.md). Move/rename the directory or remove it manually if intentional.`);
|
|
476
483
|
}
|
|
477
|
-
|
|
478
|
-
|
|
484
|
+
// Manifest-scoped removal: read the persisted manifest at install time
|
|
485
|
+
// and remove only its files + the manifest itself. Leaves any
|
|
486
|
+
// user-authored content under the same folder in place (with a note).
|
|
487
|
+
// Fallback: bundles installed before 0.9.0 have no persisted manifest;
|
|
488
|
+
// legacy whole-folder rm preserves the pre-0.9.0 behaviour.
|
|
489
|
+
const persistedPath = path.join(target, PERSISTED_MANIFEST_BASENAME);
|
|
490
|
+
const manifestBody = await fs.readFile(persistedPath, "utf8").catch(() => null);
|
|
491
|
+
if (!manifestBody) {
|
|
492
|
+
// Legacy uninstall: rm -rf whole folder.
|
|
493
|
+
try {
|
|
494
|
+
await fs.rm(target, { recursive: true, force: false });
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
const stillExists = await fs.stat(target).then(() => true).catch(() => false);
|
|
498
|
+
if (stillExists) {
|
|
499
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
500
|
+
throw new Error(`uninstall partially failed at ${target}: ${msg}; manual cleanup may be required`);
|
|
501
|
+
}
|
|
502
|
+
throw err;
|
|
503
|
+
}
|
|
479
504
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
505
|
+
else {
|
|
506
|
+
let persistedManifest;
|
|
507
|
+
try {
|
|
508
|
+
persistedManifest = JSON.parse(manifestBody);
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
483
511
|
const msg = err instanceof Error ? err.message : String(err);
|
|
484
|
-
throw new Error(`
|
|
512
|
+
throw new Error(`persisted manifest at ${persistedPath} is not valid JSON: ${msg}. Remove the file manually then retry.`);
|
|
513
|
+
}
|
|
514
|
+
for (const f of persistedManifest.files) {
|
|
515
|
+
await fs.unlink(path.join(target, f.dest)).catch(() => null);
|
|
516
|
+
}
|
|
517
|
+
await fs.unlink(persistedPath).catch(() => null);
|
|
518
|
+
// Recursively prune empty directories under target. Stop at target
|
|
519
|
+
// itself — only rmdir target if no user-authored files remain.
|
|
520
|
+
const pruneEmptyDirs = async (dir) => {
|
|
521
|
+
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
522
|
+
for (const e of entries) {
|
|
523
|
+
if (e.isDirectory()) {
|
|
524
|
+
await pruneEmptyDirs(path.join(dir, e.name));
|
|
525
|
+
const subEntries = await fs.readdir(path.join(dir, e.name)).catch(() => []);
|
|
526
|
+
if (subEntries.length === 0) {
|
|
527
|
+
await fs.rmdir(path.join(dir, e.name)).catch(() => null);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
await pruneEmptyDirs(target);
|
|
533
|
+
const remaining = await fs.readdir(target).catch(() => []);
|
|
534
|
+
if (remaining.length === 0) {
|
|
535
|
+
await fs.rmdir(target).catch(() => null);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
process.stdout.write(`note: ${target} contains files outside the skill manifest; left in place. Remove manually if intentional.\n`);
|
|
485
539
|
}
|
|
486
|
-
throw err;
|
|
487
540
|
}
|
|
488
541
|
process.stdout.write(`uninstalled ${exports.SKILL_NAME} from ${target}\n`);
|
|
489
542
|
return 0;
|
|
490
543
|
});
|
|
491
544
|
}
|
|
492
545
|
async function update(opts) {
|
|
493
|
-
return
|
|
546
|
+
return withFatalReturn(async () => {
|
|
547
|
+
const target = await resolve(opts.target ?? defaultTarget());
|
|
548
|
+
const base = baseUrl(opts.version);
|
|
549
|
+
const manifest = await fetchManifest(base);
|
|
550
|
+
if (manifest.name !== exports.SKILL_NAME) {
|
|
551
|
+
throw new Error(`manifest name mismatch: expected '${exports.SKILL_NAME}', got '${manifest.name}'. CLI and bundle are out of sync.`);
|
|
552
|
+
}
|
|
553
|
+
// Already-at-version short-circuit: read the on-disk SKILL.md
|
|
554
|
+
// frontmatter version and compare to manifest. Equal -> no-op.
|
|
555
|
+
const installedSkillMdPath = path.join(target, "SKILL.md");
|
|
556
|
+
const installedBody = await fs.readFile(installedSkillMdPath, "utf8").catch(() => null);
|
|
557
|
+
if (installedBody) {
|
|
558
|
+
const fmInstalled = parseFrontmatter(installedBody);
|
|
559
|
+
if (fmInstalled.version && fmInstalled.version === manifest.version) {
|
|
560
|
+
process.stdout.write(`${exports.SKILL_NAME} already at version ${manifest.version} (no-op)\n`);
|
|
561
|
+
return 0;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Otherwise proceed with overwriting install (the existing path).
|
|
565
|
+
return install({ ...opts, overwrite: true });
|
|
566
|
+
});
|
|
494
567
|
}
|
|
495
568
|
async function list(opts) {
|
|
496
569
|
return withFatalReturn(async () => {
|
package/package.json
CHANGED