@ankhorage/devtools 1.12.1 → 1.13.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.
@@ -42,6 +42,9 @@ types, ZORA elements, events, recipes, and version provenance. Never copy owner
42
42
  schemas, token inventories, color algorithms, action types, or manifest implementations into this
43
43
  skill.
44
44
 
45
+ The inspection composes metadata-only descriptors from every installed `@ankhorage/zora-*` plugin;
46
+ use those plugin elements exactly like ZORA core elements and keep their package provenance.
47
+
45
48
  Compile chosen values with the same helper before composing screens. Inspect both computed modes,
46
49
  including their resolved Surface themes and all owner diagnostics. Never hand-calculate a value the
47
50
  owner exposes.
@@ -18,6 +18,9 @@ navigator types, ZORA elements, or events. Resolve explicit user input before pr
18
18
  existing brief values, category recommendations, and global defaults. Record the origin of every
19
19
  resolved decision.
20
20
 
21
+ Installed `@ankhorage/zora-*` packages contribute their metadata-only plugin descriptors to this
22
+ same composed element catalog; do not maintain a second plugin list in the skill.
23
+
21
24
  ## 2. Ask in dependency order
22
25
 
23
26
  Advance through this sequence. Skip only a value already supplied or reliably discovered.
@@ -58,7 +58,7 @@ interface ZoraThemeApi extends Record<string, unknown> {
58
58
  }
59
59
 
60
60
  interface EventMetadata extends Record<string, unknown> {
61
- description: string;
61
+ description?: string;
62
62
  eventType: string;
63
63
  label: string;
64
64
  payloadFields?: unknown;
@@ -84,6 +84,10 @@ interface RecipeMetadata extends Record<string, unknown> {
84
84
  }
85
85
 
86
86
  interface ZoraMetadataApi extends Record<string, unknown> {
87
+ composeZoraPluginMetadata: (plugins: readonly Record<string, unknown>[]) => {
88
+ componentMeta: Record<string, ComponentMetadata | undefined>;
89
+ };
90
+ ZORA_CORE_PLUGIN_METADATA: Record<string, unknown>;
87
91
  ZORA_COMPONENT_META: Record<string, ComponentMetadata | undefined>;
88
92
  ZORA_THEME_RECIPE_META: Record<string, RecipeMetadata | undefined>;
89
93
  }
@@ -119,7 +123,7 @@ const OWNER_RELEASES = {
119
123
  colorTheory: { packageName: '@ankhorage/color-theory', minimumVersion: '0.3.0' },
120
124
  contracts: { packageName: '@ankhorage/contracts', minimumVersion: '10.1.0' },
121
125
  templates: { packageName: '@ankhorage/templates', minimumVersion: '9.3.0' },
122
- zora: { packageName: '@ankhorage/zora', minimumVersion: '4.2.0' },
126
+ zora: { packageName: '@ankhorage/zora', minimumVersion: '4.3.0' },
123
127
  };
124
128
 
125
129
  const OWNER_REQUIREMENTS = {
@@ -155,7 +159,12 @@ const OWNER_REQUIREMENTS = {
155
159
  zoraMetadata: {
156
160
  ...OWNER_RELEASES.zora,
157
161
  specifier: '@ankhorage/zora/metadata',
158
- exports: ['ZORA_COMPONENT_META', 'ZORA_THEME_RECIPE_META'],
162
+ exports: [
163
+ 'composeZoraPluginMetadata',
164
+ 'ZORA_COMPONENT_META',
165
+ 'ZORA_CORE_PLUGIN_METADATA',
166
+ 'ZORA_THEME_RECIPE_META',
167
+ ],
159
168
  },
160
169
  };
161
170
 
@@ -171,27 +180,81 @@ export async function loadOwnerApis(targetDirectory = process.cwd()) {
171
180
  const templates = await loadOwnerModule(targetDirectory, OWNER_REQUIREMENTS.templates);
172
181
  const zoraTheme = await loadOwnerModule(targetDirectory, OWNER_REQUIREMENTS.zoraTheme);
173
182
  const zoraMetadata = await loadOwnerModule(targetDirectory, OWNER_REQUIREMENTS.zoraMetadata);
183
+ const installedPluginMetadata = await loadInstalledZoraPluginMetadata(targetDirectory);
174
184
  assertColorTheoryApi(colorTheory.module);
175
185
  assertContractsApi(contracts.module);
176
186
  assertTemplatesApi(templates.module);
177
187
  assertZoraThemeApi(zoraTheme.module);
178
188
  assertZoraMetadataApi(zoraMetadata.module);
189
+ const composedZoraMetadata = zoraMetadata.module.composeZoraPluginMetadata([
190
+ zoraMetadata.module.ZORA_CORE_PLUGIN_METADATA,
191
+ ...installedPluginMetadata.map((entry) => entry.metadata),
192
+ ]);
193
+ assertRecord(composedZoraMetadata, 'composed ZORA plugin metadata');
194
+ assertRecord(composedZoraMetadata.componentMeta, 'composed ZORA component metadata');
179
195
 
180
196
  return {
181
197
  colorTheory: colorTheory.module,
182
198
  contracts: contracts.module,
183
199
  templates: templates.module,
184
200
  zoraTheme: zoraTheme.module,
185
- zoraMetadata: zoraMetadata.module,
201
+ zoraMetadata: {
202
+ ...zoraMetadata.module,
203
+ ZORA_COMPONENT_META: composedZoraMetadata.componentMeta,
204
+ },
186
205
  versions: {
187
206
  colorTheory: colorTheory.version,
188
207
  contracts: contracts.version,
189
208
  templates: templates.version,
190
209
  zora: zoraTheme.version,
210
+ plugins: Object.fromEntries(
211
+ installedPluginMetadata.map((entry) => [entry.packageName, entry.version]),
212
+ ),
191
213
  },
192
214
  };
193
215
  }
194
216
 
217
+ /*** Load metadata-only descriptors for every installed ZORA plugin declared by the target package. */
218
+ async function loadInstalledZoraPluginMetadata(
219
+ targetDirectory: string,
220
+ ): Promise<{ metadata: Record<string, unknown>; packageName: string; version: string }[]> {
221
+ const targetManifestPath = join(resolve(targetDirectory), 'package.json');
222
+ const targetManifest: unknown = JSON.parse(await readFile(targetManifestPath, 'utf8'));
223
+ assertRecord(targetManifest, 'Target package manifest');
224
+ const packageNames = new Set<string>();
225
+ if (typeof targetManifest.name === 'string') packageNames.add(targetManifest.name);
226
+ for (const field of ['dependencies', 'devDependencies', 'peerDependencies']) {
227
+ const dependencies = targetManifest[field];
228
+ if (!isRecord(dependencies)) continue;
229
+ for (const packageName of Object.keys(dependencies)) packageNames.add(packageName);
230
+ }
231
+
232
+ const loaded: { metadata: Record<string, unknown>; packageName: string; version: string }[] = [];
233
+ for (const packageName of [...packageNames].filter(isZoraPluginPackage).sort()) {
234
+ const minimumVersion =
235
+ packageName === '@ankhorage/zora-chess'
236
+ ? '0.2.0'
237
+ : packageName === '@ankhorage/zora-tabletop'
238
+ ? '0.1.0'
239
+ : '0.0.0';
240
+ const requirement: OwnerRequirement = {
241
+ packageName,
242
+ minimumVersion,
243
+ specifier: `${packageName}/metadata`,
244
+ exports: ['ZORA_PLUGIN_METADATA'],
245
+ };
246
+ const plugin = await loadOwnerModule(targetDirectory, requirement);
247
+ const metadata = plugin.module.ZORA_PLUGIN_METADATA;
248
+ assertRecord(metadata, `${packageName} ZORA plugin metadata`);
249
+ loaded.push({ metadata, packageName, version: plugin.version });
250
+ }
251
+ return loaded;
252
+ }
253
+
254
+ function isZoraPluginPackage(packageName: string): boolean {
255
+ return packageName.startsWith('@ankhorage/zora-') && packageName !== '@ankhorage/zora';
256
+ }
257
+
195
258
  /*** Load only Contracts for tooling that runs before another owner package has been built. */
196
259
  export async function loadContractsApi(targetDirectory = process.cwd()): Promise<ContractsApi> {
197
260
  const loaded = await loadOwnerModule(targetDirectory, OWNER_REQUIREMENTS.contracts);
@@ -661,6 +724,10 @@ function assertZoraThemeApi(value: Record<string, unknown>): asserts value is Zo
661
724
 
662
725
  /*** Narrow released ZORA metadata into only the fields the orchestration needs. */
663
726
  function assertZoraMetadataApi(value: Record<string, unknown>): asserts value is ZoraMetadataApi {
727
+ if (typeof value.composeZoraPluginMetadata !== 'function') {
728
+ throw new Error('composeZoraPluginMetadata must be a function.');
729
+ }
730
+ assertRecord(value.ZORA_CORE_PLUGIN_METADATA, 'ZORA_CORE_PLUGIN_METADATA');
664
731
  assertRecord(value.ZORA_COMPONENT_META, 'ZORA_COMPONENT_META');
665
732
  for (const [name, metadata] of Object.entries(value.ZORA_COMPONENT_META)) {
666
733
  assertComponentMetadata(metadata, name);
@@ -696,7 +763,9 @@ function assertEventMetadata(value: unknown, name: string): asserts value is Eve
696
763
  assertRecord(value, `ZORA event metadata ${name}`);
697
764
  assertNonEmptyString(value.eventType, `ZORA event metadata ${name}.eventType`);
698
765
  assertNonEmptyString(value.label, `ZORA event metadata ${name}.label`);
699
- assertNonEmptyString(value.description, `ZORA event metadata ${name}.description`);
766
+ if (value.description !== undefined) {
767
+ assertNonEmptyString(value.description, `ZORA event metadata ${name}.description`);
768
+ }
700
769
  }
701
770
 
702
771
  /*** Validate one theme recipe metadata entry and its supported fields. */
@@ -0,0 +1,6 @@
1
+ export declare function createThemeConfig(): Record<string, unknown>;
2
+ export declare const TEMPLATES_FIXTURE_SOURCE: string;
3
+ export declare const COLOR_THEORY_FIXTURE_SOURCE = "\nexport const COLOR_HARMONIES = ['monochromatic', 'complementary'];\nexport const COLOR_HARMONY_CATALOG = [\n { id: 'monochromatic', label: 'Monochromatic', description: 'One hue.' },\n { id: 'complementary', label: 'Complementary', description: 'Opposing hues.' },\n];\n";
4
+ export declare const CONTRACTS_FIXTURE_SOURCE = "\nexport const APP_CATEGORIES = ['business_productivity'];\nexport const NAVIGATOR_TYPES = ['stack', 'tabs', 'drawer'];\n";
5
+ export declare const ZORA_THEME_FIXTURE_SOURCE = "\nexport const compileZoraTheme = (themeConfig) => ({\n themeConfig,\n light: { surfaceTheme: { mode: 'light' }, diagnostics: [] },\n dark: { surfaceTheme: { mode: 'dark' }, diagnostics: [] },\n diagnostics: [],\n});\n";
6
+ export declare const ZORA_METADATA_FIXTURE_SOURCE = "\nexport const ZORA_COMPONENT_META = {\n View: { name: 'View', directManifestNode: true, allowedChildren: ['Text', 'Box'], props: {} },\n Box: { name: 'Box', directManifestNode: true, allowedChildren: [], props: {} },\n Text: {\n name: 'Text',\n directManifestNode: true,\n allowedChildren: [],\n props: { text: { type: 'string' } },\n events: {\n press: {\n eventType: 'text.press',\n label: 'Press',\n description: 'Text was pressed.',\n },\n },\n },\n MissingElement: {\n name: 'MissingElement',\n directManifestNode: true,\n allowedChildren: [],\n manifestPolicy: { kind: 'unresolved-element', availability: 'draft-only', releaseGate: 'blocked' },\n blueprint: { defaultProps: { requestedCapability: 'Unresolved', reason: 'No exact element.' } },\n props: {\n requestedCapability: { type: 'string' },\n reason: { type: 'string' },\n evidenceId: { type: 'string' },\n },\n },\n};\nexport const ZORA_CORE_PLUGIN_METADATA = {\n packageName: '@ankhorage/zora',\n componentMeta: ZORA_COMPONENT_META,\n extensionHosts: ['View', 'Box'],\n};\nexport const composeZoraPluginMetadata = (plugins) => {\n const componentMeta = {};\n const extensionHosts = new Set();\n for (const plugin of [...plugins].sort((left, right) => left.packageName.localeCompare(right.packageName))) {\n Object.assign(componentMeta, plugin.componentMeta);\n for (const host of plugin.extensionHosts ?? []) extensionHosts.add(host);\n }\n for (const plugin of plugins) {\n for (const placement of plugin.placements ?? []) {\n for (const parent of placement.parents) {\n if (!extensionHosts.has(parent)) throw new Error('Invalid extension host');\n componentMeta[parent] = {\n ...componentMeta[parent],\n allowedChildren: [...componentMeta[parent].allowedChildren, placement.child],\n };\n }\n }\n }\n return { componentMeta };\n};\nexport const ZORA_THEME_RECIPE_META = {\n Card: {\n name: 'Card',\n kind: 'component',\n fields: { variant: { type: 'choice', options: ['filled', 'outlined'] } },\n },\n};\n";
@@ -0,0 +1,144 @@
1
+ export function createThemeConfig() {
2
+ return {
3
+ id: 'evidence-theme',
4
+ name: 'Evidence Theme',
5
+ light: { primaryColor: '#2563EB', harmony: 'complementary' },
6
+ dark: { primaryColor: '#2563EB', harmony: 'complementary' },
7
+ };
8
+ }
9
+ export const TEMPLATES_FIXTURE_SOURCE = `
10
+ export const CATEGORY_PRESETS = {
11
+ business_productivity: {
12
+ category: 'business_productivity',
13
+ label: 'Business',
14
+ recommendedPrimaryColors: ['#2563EB'],
15
+ recommendedHarmonies: ['complementary'],
16
+ tonePairs: { light: 'jewel-on-neutral-light', dark: 'pastel-on-neutral-dark' },
17
+ density: 'compact',
18
+ },
19
+ };
20
+ export const TONE_PAIR_CATALOG = [];
21
+ export const resolveTonePair = () => null;
22
+ export const resolveCategoryDesignPreset = (category, theme = {}) => ({ category, theme });
23
+ const themeConfig = ${JSON.stringify(createThemeConfig())};
24
+ export const compileCategoryDesign = (category) => ({
25
+ category,
26
+ themeConfig,
27
+ diagnostics: [],
28
+ computedTheme: {
29
+ themeConfig,
30
+ light: { surfaceTheme: { mode: 'light' }, diagnostics: [] },
31
+ dark: { surfaceTheme: { mode: 'dark' }, diagnostics: [] },
32
+ diagnostics: [],
33
+ },
34
+ });
35
+ export const composeCategoryAppManifest = (input) => ({
36
+ manifest: {
37
+ metadata: {
38
+ name: input.name ?? 'Generated App',
39
+ slug: input.slug ?? 'generated-app',
40
+ version: input.version ?? '1.0.0',
41
+ category: input.category,
42
+ themeId: 'evidence-theme',
43
+ },
44
+ themes: [themeConfig],
45
+ activeThemeId: 'evidence-theme',
46
+ infra: { modules: input.modules ?? [] },
47
+ navigator: input.navigator,
48
+ screens: input.screens,
49
+ settings: { localization: { defaultLocale: 'en', locales: ['en'] } },
50
+ },
51
+ diagnostics: [],
52
+ status: input.authoringState === 'release' ? 'ready' : 'blocked',
53
+ authoringState: input.authoringState,
54
+ });
55
+ export const validateTemplateManifest = (manifest) => ({
56
+ manifest,
57
+ diagnostics: [],
58
+ status: 'ready',
59
+ authoringState: 'release',
60
+ });
61
+ export const assertTemplateManifestReady = (composition) => composition.manifest;
62
+ `;
63
+ export const COLOR_THEORY_FIXTURE_SOURCE = `
64
+ export const COLOR_HARMONIES = ['monochromatic', 'complementary'];
65
+ export const COLOR_HARMONY_CATALOG = [
66
+ { id: 'monochromatic', label: 'Monochromatic', description: 'One hue.' },
67
+ { id: 'complementary', label: 'Complementary', description: 'Opposing hues.' },
68
+ ];
69
+ `;
70
+ export const CONTRACTS_FIXTURE_SOURCE = `
71
+ export const APP_CATEGORIES = ['business_productivity'];
72
+ export const NAVIGATOR_TYPES = ['stack', 'tabs', 'drawer'];
73
+ `;
74
+ export const ZORA_THEME_FIXTURE_SOURCE = `
75
+ export const compileZoraTheme = (themeConfig) => ({
76
+ themeConfig,
77
+ light: { surfaceTheme: { mode: 'light' }, diagnostics: [] },
78
+ dark: { surfaceTheme: { mode: 'dark' }, diagnostics: [] },
79
+ diagnostics: [],
80
+ });
81
+ `;
82
+ export const ZORA_METADATA_FIXTURE_SOURCE = `
83
+ export const ZORA_COMPONENT_META = {
84
+ View: { name: 'View', directManifestNode: true, allowedChildren: ['Text', 'Box'], props: {} },
85
+ Box: { name: 'Box', directManifestNode: true, allowedChildren: [], props: {} },
86
+ Text: {
87
+ name: 'Text',
88
+ directManifestNode: true,
89
+ allowedChildren: [],
90
+ props: { text: { type: 'string' } },
91
+ events: {
92
+ press: {
93
+ eventType: 'text.press',
94
+ label: 'Press',
95
+ description: 'Text was pressed.',
96
+ },
97
+ },
98
+ },
99
+ MissingElement: {
100
+ name: 'MissingElement',
101
+ directManifestNode: true,
102
+ allowedChildren: [],
103
+ manifestPolicy: { kind: 'unresolved-element', availability: 'draft-only', releaseGate: 'blocked' },
104
+ blueprint: { defaultProps: { requestedCapability: 'Unresolved', reason: 'No exact element.' } },
105
+ props: {
106
+ requestedCapability: { type: 'string' },
107
+ reason: { type: 'string' },
108
+ evidenceId: { type: 'string' },
109
+ },
110
+ },
111
+ };
112
+ export const ZORA_CORE_PLUGIN_METADATA = {
113
+ packageName: '@ankhorage/zora',
114
+ componentMeta: ZORA_COMPONENT_META,
115
+ extensionHosts: ['View', 'Box'],
116
+ };
117
+ export const composeZoraPluginMetadata = (plugins) => {
118
+ const componentMeta = {};
119
+ const extensionHosts = new Set();
120
+ for (const plugin of [...plugins].sort((left, right) => left.packageName.localeCompare(right.packageName))) {
121
+ Object.assign(componentMeta, plugin.componentMeta);
122
+ for (const host of plugin.extensionHosts ?? []) extensionHosts.add(host);
123
+ }
124
+ for (const plugin of plugins) {
125
+ for (const placement of plugin.placements ?? []) {
126
+ for (const parent of placement.parents) {
127
+ if (!extensionHosts.has(parent)) throw new Error('Invalid extension host');
128
+ componentMeta[parent] = {
129
+ ...componentMeta[parent],
130
+ allowedChildren: [...componentMeta[parent].allowedChildren, placement.child],
131
+ };
132
+ }
133
+ }
134
+ }
135
+ return { componentMeta };
136
+ };
137
+ export const ZORA_THEME_RECIPE_META = {
138
+ Card: {
139
+ name: 'Card',
140
+ kind: 'component',
141
+ fields: { variant: { type: 'choice', options: ['filled', 'outlined'] } },
142
+ },
143
+ };
144
+ `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.12.1",
3
+ "version": "1.13.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",