@ankhorage/devtools 1.9.5 → 1.10.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.
@@ -2,7 +2,7 @@ import { lstat, mkdir, readdir, readFile, rmdir, unlink, writeFile } from 'node:
2
2
  import { dirname, join, sep } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { assertManagedSkillPath, createManifestContents, isNodeError, MANIFEST_PATH, readManagedSkillsManifest, resolveManagedPath, SKILLS_ROOT, } from './manifest.js';
5
- const CANONICAL_SKILL_NAMES = ['ankhorage-coding-rules', 'ankhorage-project-structure'];
5
+ import { selectManagedSkillNames } from './selection.js';
6
6
  export async function inspectManagedSkills(targetDirectory, devtoolsVersion) {
7
7
  return (await createManagedSkillPlan(targetDirectory, devtoolsVersion)).statuses;
8
8
  }
@@ -56,9 +56,9 @@ async function assertNoSymlinkSegments(targetDirectory, relativePath) {
56
56
  }
57
57
  }
58
58
  }
59
- async function collectCanonicalSkillFiles() {
59
+ async function collectCanonicalSkillFiles(skillNames) {
60
60
  const contentsByPath = new Map();
61
- for (const skillName of CANONICAL_SKILL_NAMES) {
61
+ for (const skillName of skillNames) {
62
62
  const sourceDirectory = fileURLToPath(new URL(`./assets/${skillName}/`, import.meta.url));
63
63
  const files = await readDirectoryFiles(sourceDirectory);
64
64
  assertSkillName(skillName, files);
@@ -68,9 +68,9 @@ async function collectCanonicalSkillFiles() {
68
68
  }
69
69
  return contentsByPath;
70
70
  }
71
- async function collectObsoletePaths(targetDirectory, desiredPaths, previousManifest) {
71
+ async function collectObsoletePaths(targetDirectory, desiredPaths, previousManifest, skillNames) {
72
72
  const obsoletePaths = new Set();
73
- for (const skillName of CANONICAL_SKILL_NAMES) {
73
+ for (const skillName of skillNames) {
74
74
  const skillRoot = `${SKILLS_ROOT}/${skillName}`;
75
75
  for (const existingPath of await readTargetDirectoryFiles(targetDirectory, skillRoot)) {
76
76
  if (!desiredPaths.has(existingPath)) {
@@ -80,7 +80,7 @@ async function collectObsoletePaths(targetDirectory, desiredPaths, previousManif
80
80
  }
81
81
  if (previousManifest !== null) {
82
82
  for (const [skillName, entry] of Object.entries(previousManifest.skills)) {
83
- if (CANONICAL_SKILL_NAMES.includes(skillName)) {
83
+ if (skillNames.includes(skillName)) {
84
84
  continue;
85
85
  }
86
86
  for (const managedPath of Object.keys(entry.files)) {
@@ -94,14 +94,15 @@ async function collectObsoletePaths(targetDirectory, desiredPaths, previousManif
94
94
  }
95
95
  async function createManagedSkillPlan(targetDirectory, devtoolsVersion) {
96
96
  const previousManifest = await readManagedSkillsManifest(targetDirectory);
97
- const contentsByPath = await collectCanonicalSkillFiles();
97
+ const skillNames = await selectManagedSkillNames(targetDirectory);
98
+ const contentsByPath = await collectCanonicalSkillFiles(skillNames);
98
99
  const desiredPaths = new Set(contentsByPath.keys());
99
- const manifestContents = createManifestContents(contentsByPath, devtoolsVersion, CANONICAL_SKILL_NAMES);
100
+ const manifestContents = createManifestContents(contentsByPath, devtoolsVersion, skillNames);
100
101
  const statuses = [];
101
102
  for (const [relativePath, contents] of [...contentsByPath.entries()].sort()) {
102
103
  statuses.push(await inspectDesiredFile(targetDirectory, relativePath, contents));
103
104
  }
104
- for (const relativePath of await collectObsoletePaths(targetDirectory, desiredPaths, previousManifest)) {
105
+ for (const relativePath of await collectObsoletePaths(targetDirectory, desiredPaths, previousManifest, skillNames)) {
105
106
  statuses.push({ relativePath, state: 'obsolete' });
106
107
  }
107
108
  statuses.push(await inspectDesiredFile(targetDirectory, MANIFEST_PATH, Buffer.from(manifestContents)));
@@ -0,0 +1,7 @@
1
+ export declare const BASELINE_SKILL_NAMES: readonly ["ankhorage-coding-rules", "ankhorage-project-structure"];
2
+ export declare const PROFILE_SKILL_NAMES: readonly ["zora-designer"];
3
+ export type ManagedSkillName = (typeof BASELINE_SKILL_NAMES)[number] | (typeof PROFILE_SKILL_NAMES)[number];
4
+ /*** Select baseline and profile-specific managed skills from current repository traits. */
5
+ export declare function selectManagedSkillNames(targetDirectory: string): Promise<readonly ManagedSkillName[]>;
6
+ /*** Identify ZORA owners and generated-app authoring repositories without explicit skill lists. */
7
+ export declare function isZoraDesignerContext(packageManifest: Readonly<Record<string, unknown>>): boolean;
@@ -0,0 +1,82 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ export const BASELINE_SKILL_NAMES = [
4
+ 'ankhorage-coding-rules',
5
+ 'ankhorage-project-structure',
6
+ ];
7
+ export const PROFILE_SKILL_NAMES = ['zora-designer'];
8
+ const ZORA_DESIGNER_OWNER_PACKAGES = new Set([
9
+ '@ankhorage/studio',
10
+ '@ankhorage/templates',
11
+ '@ankhorage/zora',
12
+ ]);
13
+ const ZORA_DESIGNER_APP_DEPENDENCIES = new Set([
14
+ '@ankhorage/expo-runtime',
15
+ '@ankhorage/runtime',
16
+ '@ankhorage/studio',
17
+ '@ankhorage/templates',
18
+ ]);
19
+ /*** Select baseline and profile-specific managed skills from current repository traits. */
20
+ export async function selectManagedSkillNames(targetDirectory) {
21
+ const packageManifest = await readPackageManifest(targetDirectory);
22
+ return isZoraDesignerContext(packageManifest)
23
+ ? [...BASELINE_SKILL_NAMES, ...PROFILE_SKILL_NAMES]
24
+ : BASELINE_SKILL_NAMES;
25
+ }
26
+ /*** Identify ZORA owners and generated-app authoring repositories without explicit skill lists. */
27
+ export function isZoraDesignerContext(packageManifest) {
28
+ const packageName = readNonEmptyString(packageManifest.name);
29
+ if (packageName !== null && ZORA_DESIGNER_OWNER_PACKAGES.has(packageName)) {
30
+ return true;
31
+ }
32
+ const dependencies = collectDependencyNames(packageManifest);
33
+ return (dependencies.has('@ankhorage/zora') &&
34
+ [...ZORA_DESIGNER_APP_DEPENDENCIES].some((dependency) => dependencies.has(dependency)));
35
+ }
36
+ /*** Read package metadata used for profile selection, treating a missing manifest as an empty one. */
37
+ async function readPackageManifest(targetDirectory) {
38
+ try {
39
+ const contents = await readFile(resolve(targetDirectory, 'package.json'), 'utf8');
40
+ const parsed = JSON.parse(contents);
41
+ if (!isRecord(parsed)) {
42
+ throw new Error('package.json must contain a JSON object for managed-skill selection.');
43
+ }
44
+ return parsed;
45
+ }
46
+ catch (error) {
47
+ if (isNodeError(error) && error.code === 'ENOENT') {
48
+ return {};
49
+ }
50
+ throw error;
51
+ }
52
+ }
53
+ /*** Combine all dependency sections that describe the repository's current package context. */
54
+ function collectDependencyNames(packageManifest) {
55
+ const names = new Set();
56
+ const dependencySections = [
57
+ packageManifest.dependencies,
58
+ packageManifest.devDependencies,
59
+ packageManifest.optionalDependencies,
60
+ packageManifest.peerDependencies,
61
+ ];
62
+ for (const dependencies of dependencySections) {
63
+ if (isRecord(dependencies)) {
64
+ for (const dependency of Object.keys(dependencies)) {
65
+ names.add(dependency);
66
+ }
67
+ }
68
+ }
69
+ return names;
70
+ }
71
+ /*** Narrow filesystem errors that expose a Node error code. */
72
+ function isNodeError(error) {
73
+ return error instanceof Error && 'code' in error;
74
+ }
75
+ /*** Narrow unknown package metadata to a record. */
76
+ function isRecord(value) {
77
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
78
+ }
79
+ /*** Return a trimmed package name when one is declared. */
80
+ function readNonEmptyString(value) {
81
+ return typeof value === 'string' && value.trim() !== '' ? value.trim() : null;
82
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.9.5",
3
+ "version": "1.10.0",
4
4
  "description": "Shared development tools and repository standards for Ankhorage",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ankhorage/devtools#readme",