@hanv89/azure-arch-skill 0.2.1 → 0.3.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/claude-code.js +56 -13
- package/package.json +1 -1
|
@@ -37,13 +37,9 @@ const DEFAULT_BASE_RAW_URL = "https://raw.githubusercontent.com/hanv89/azure-ico
|
|
|
37
37
|
// existing installs become un-uninstallable until users upgrade the CLI
|
|
38
38
|
// (uninstall's allow-list refuses folders whose SKILL.md `name` differs).
|
|
39
39
|
const SKILL_NAME = "azure-architecture-diagram";
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
const
|
|
43
|
-
{ src: "dist/skill/SKILL.md", dest: "SKILL.md" },
|
|
44
|
-
{ src: "dist/skill/examples/01-context.puml", dest: "examples/01-context.puml" },
|
|
45
|
-
{ src: "dist/skill/examples/02-fabric-data-pipeline.puml", dest: "examples/02-fabric-data-pipeline.puml" },
|
|
46
|
-
];
|
|
40
|
+
// Fetched at install/update time from dist/skill/manifest.json. files[0] MUST
|
|
41
|
+
// be SKILL.md so the frontmatter precheck has a stable target.
|
|
42
|
+
const MANIFEST_PATH = "dist/skill/manifest.json";
|
|
47
43
|
const CANARY_ICON_PATH = "dist/Azure/Compute/AzureVirtualMachine.png";
|
|
48
44
|
const FETCH_TIMEOUT_MS = 30_000;
|
|
49
45
|
const USER_AGENT = `azure-arch-skill/${package_json_1.default.version}`;
|
|
@@ -185,6 +181,48 @@ async function headOk(url) {
|
|
|
185
181
|
const res = await fetchWithTimeout(url, { method: "HEAD" });
|
|
186
182
|
return res.ok;
|
|
187
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Fetch + parse the bundle manifest. Validates required fields and the
|
|
186
|
+
* SKILL.md-at-index-0 invariant. Throws with a clear error on any issue —
|
|
187
|
+
* callers should not silently fall back.
|
|
188
|
+
*/
|
|
189
|
+
async function fetchManifest(base) {
|
|
190
|
+
const url = `${base}/${MANIFEST_PATH}`;
|
|
191
|
+
const body = await fetchText(url);
|
|
192
|
+
let parsed;
|
|
193
|
+
try {
|
|
194
|
+
parsed = JSON.parse(body);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
throw new Error(`manifest ${url} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
198
|
+
}
|
|
199
|
+
if (!parsed || typeof parsed !== "object") {
|
|
200
|
+
throw new Error(`manifest ${url} did not parse to an object`);
|
|
201
|
+
}
|
|
202
|
+
const m = parsed;
|
|
203
|
+
for (const key of ["name", "version", "requires_icons"]) {
|
|
204
|
+
if (typeof m[key] !== "string" || !m[key]) {
|
|
205
|
+
throw new Error(`manifest ${url} missing required field: ${key}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (!Array.isArray(m.files) || m.files.length === 0) {
|
|
209
|
+
throw new Error(`manifest ${url} files[] missing or empty`);
|
|
210
|
+
}
|
|
211
|
+
for (const [i, f] of m.files.entries()) {
|
|
212
|
+
if (!f || typeof f !== "object") {
|
|
213
|
+
throw new Error(`manifest ${url} files[${i}] not an object`);
|
|
214
|
+
}
|
|
215
|
+
for (const key of ["src", "dest", "role"]) {
|
|
216
|
+
if (typeof f[key] !== "string") {
|
|
217
|
+
throw new Error(`manifest ${url} files[${i}].${key} missing`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (m.files[0].dest !== "SKILL.md" || m.files[0].role !== "skill") {
|
|
222
|
+
throw new Error(`manifest ${url} files[0] must be SKILL.md (role=skill); got dest=${m.files[0].dest} role=${m.files[0].role}`);
|
|
223
|
+
}
|
|
224
|
+
return m;
|
|
225
|
+
}
|
|
188
226
|
/**
|
|
189
227
|
* Minimal YAML frontmatter parser — supports only single-line scalar `key: value`
|
|
190
228
|
* pairs with optional `"` or `'` quoting. Multi-line scalars (`|`, `>`), nested
|
|
@@ -253,8 +291,13 @@ async function install(opts) {
|
|
|
253
291
|
if (!process.env.AZURE_ARCH_SKILL_TARGET_ROOT && path.basename(target) !== SKILL_NAME) {
|
|
254
292
|
throw new Error(`refusing to install at ${target} - target basename must be '${SKILL_NAME}' (default ~/.claude/skills/${SKILL_NAME}/). Set AZURE_ARCH_SKILL_TARGET_ROOT to install into a custom test root.`);
|
|
255
293
|
}
|
|
256
|
-
//
|
|
257
|
-
const
|
|
294
|
+
// Fetch the bundle manifest FIRST. Anything downstream relies on it.
|
|
295
|
+
const manifest = await fetchManifest(base);
|
|
296
|
+
if (manifest.name !== SKILL_NAME) {
|
|
297
|
+
throw new Error(`manifest name mismatch: expected '${SKILL_NAME}', got '${manifest.name}'. CLI and bundle are out of sync.`);
|
|
298
|
+
}
|
|
299
|
+
// Detect partial vs complete prior installs across manifest.files.
|
|
300
|
+
const presence = await Promise.all(manifest.files.map(async ({ dest }) => ({
|
|
258
301
|
dest,
|
|
259
302
|
exists: await fs.stat(path.join(target, dest)).then(() => true).catch(() => false),
|
|
260
303
|
})));
|
|
@@ -265,7 +308,7 @@ async function install(opts) {
|
|
|
265
308
|
? `${target} already contains an install. Run 'azure-arch-skill update --agent=claude-code' to refresh.`
|
|
266
309
|
: `${target} contains a partial install (${presence.filter(p => !p.exists).map(p => p.dest).join(", ")} missing). Run 'azure-arch-skill update --agent=claude-code' to repair.`);
|
|
267
310
|
}
|
|
268
|
-
const skillUrl = `${base}/${
|
|
311
|
+
const skillUrl = `${base}/${manifest.files[0].src}`;
|
|
269
312
|
const skillMd = await fetchText(skillUrl);
|
|
270
313
|
const fm = parseFrontmatter(skillMd);
|
|
271
314
|
if (!fm.requires_icons) {
|
|
@@ -277,11 +320,11 @@ async function install(opts) {
|
|
|
277
320
|
throw new Error(`icon-set unreachable - HEAD ${canaryUrl} failed (skill declares requires_icons=${fm.requires_icons}; this release verifies reachability only, strict semver match planned)`);
|
|
278
321
|
}
|
|
279
322
|
// Mkdir the parent of every bundle dest so future deeper-nested entries work.
|
|
280
|
-
for (const { dest } of
|
|
323
|
+
for (const { dest } of manifest.files) {
|
|
281
324
|
await fs.mkdir(path.dirname(path.join(target, dest)), { recursive: true });
|
|
282
325
|
}
|
|
283
|
-
await fs.writeFile(path.join(target,
|
|
284
|
-
for (const { src, dest } of
|
|
326
|
+
await fs.writeFile(path.join(target, manifest.files[0].dest), skillMd, "utf8");
|
|
327
|
+
for (const { src, dest } of manifest.files.slice(1)) {
|
|
285
328
|
const body = await fetchText(`${base}/${src}`);
|
|
286
329
|
await fs.writeFile(path.join(target, dest), body, "utf8");
|
|
287
330
|
}
|
package/package.json
CHANGED