@ankhorage/contracts 1.18.3 → 1.19.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ankhorage/contracts
2
2
 
3
+ ## 1.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b618979: Add `@ankhorage/contracts/cli` metadata contracts for Ankh package discovery.
8
+
3
9
  ## 1.18.3
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -22,6 +22,7 @@ Shared public contracts for Ankhorage packages and standalone provider packages.
22
22
  ```ts
23
23
  import type { AppManifest } from '@ankhorage/contracts';
24
24
  import type { AuthAdapter } from '@ankhorage/contracts/auth';
25
+ import type { AnkhCommandProviderManifest, AnkhPackageMetadata } from '@ankhorage/contracts/cli';
25
26
  import type { DbAdapter } from '@ankhorage/contracts/db';
26
27
  import type { StorageAdapter } from '@ankhorage/contracts/storage';
27
28
  ```
@@ -71,6 +72,50 @@ export function createSupabaseAuthAdapter(): AuthAdapter {
71
72
  }
72
73
  ```
73
74
 
75
+ ## CLI discovery contracts
76
+
77
+ `@ankhorage/contracts/cli` contains metadata-only discovery contracts for Ankh
78
+ packages and command providers.
79
+
80
+ ```ts
81
+ import type { AnkhCommandProviderManifest, AnkhPackageMetadata } from '@ankhorage/contracts/cli';
82
+ ```
83
+
84
+ Provider packages expose `package.json.ankh` metadata using a package-relative
85
+ provider module path:
86
+
87
+ ```json
88
+ {
89
+ "ankh": {
90
+ "category": "infra",
91
+ "provider": "./dist/ankh.provider.js",
92
+ "capabilities": ["infra.up", "infra.status"]
93
+ }
94
+ }
95
+ ```
96
+
97
+ Metadata-only packages use `null` for `provider`:
98
+
99
+ ```json
100
+ {
101
+ "ankh": {
102
+ "category": "contracts",
103
+ "provider": null,
104
+ "capabilities": ["contracts.cli"]
105
+ }
106
+ }
107
+ ```
108
+
109
+ Command descriptor paths are relative to the provider category:
110
+
111
+ - `category: "infra"` with `path: ["up"]` maps to `ankh infra up`
112
+ - `category: "dev"` with `path: ["android", "scan"]` maps to `ankh dev android scan`
113
+
114
+ This subpath contains contracts only. It must not depend on `commander`,
115
+ `@ankhorage/ankh`, provider implementations, or runtime CLI execution logic.
116
+ Concrete `package.json.ankh` adoption for this package is deferred to
117
+ `contracts#84`.
118
+
74
119
  ## Profile contract
75
120
 
76
121
  Auth providers own identity records. App-facing profile data should be modeled separately, usually in an app table such as `profiles`.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ export type AnkhCommandCategory = string;
2
+ export type AnkhProviderReference = `./${string}`;
3
+ export type AnkhCapabilityId = `${string}.${string}`;
4
+ export interface AnkhCommandDescriptor {
5
+ readonly path: readonly [string, ...string[]];
6
+ readonly summary: string;
7
+ readonly capability: AnkhCapabilityId;
8
+ readonly aliases?: readonly string[];
9
+ readonly examples?: readonly string[];
10
+ }
11
+ export interface AnkhCommandProviderManifest {
12
+ readonly id: string;
13
+ readonly category: AnkhCommandCategory;
14
+ readonly version: string;
15
+ readonly capabilities: readonly AnkhCapabilityId[];
16
+ readonly commands: readonly AnkhCommandDescriptor[];
17
+ }
18
+ export interface AnkhPackageMetadata {
19
+ readonly category: AnkhCommandCategory;
20
+ readonly provider: AnkhProviderReference | null;
21
+ readonly capabilities: readonly AnkhCapabilityId[];
22
+ }
23
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEzC,MAAM,MAAM,qBAAqB,GAAG,KAAK,MAAM,EAAE,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACrD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,YAAY,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACpD"}
package/dist/cli.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"","sourcesContent":["export type AnkhCommandCategory = string;\n\nexport type AnkhProviderReference = `./${string}`;\n\nexport type AnkhCapabilityId = `${string}.${string}`;\n\nexport interface AnkhCommandDescriptor {\n readonly path: readonly [string, ...string[]];\n readonly summary: string;\n readonly capability: AnkhCapabilityId;\n readonly aliases?: readonly string[];\n readonly examples?: readonly string[];\n}\n\nexport interface AnkhCommandProviderManifest {\n readonly id: string;\n readonly category: AnkhCommandCategory;\n readonly version: string;\n readonly capabilities: readonly AnkhCapabilityId[];\n readonly commands: readonly AnkhCommandDescriptor[];\n}\n\nexport interface AnkhPackageMetadata {\n readonly category: AnkhCommandCategory;\n readonly provider: AnkhProviderReference | null;\n readonly capabilities: readonly AnkhCapabilityId[];\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './auth';
2
2
  export * from './bindings';
3
+ export * from './cli';
3
4
  export * from './data';
4
5
  export * from './db';
5
6
  export * from './nutrition';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,MAAM,CAAC;AACrB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,MAAM,CAAC;AACrB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './auth';
2
2
  export * from './bindings';
3
+ export * from './cli';
3
4
  export * from './data';
4
5
  export * from './db';
5
6
  export * from './nutrition';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,MAAM,CAAC;AACrB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,MAAM,CAAC","sourcesContent":["export * from './auth';\nexport * from './bindings';\nexport * from './data';\nexport * from './db';\nexport * from './nutrition';\nexport * from './requirements';\nexport * from './state';\nexport * from './storage';\nexport * from './types';\nexport * from './ui';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,MAAM,CAAC;AACrB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,MAAM,CAAC","sourcesContent":["export * from './auth';\nexport * from './bindings';\nexport * from './cli';\nexport * from './data';\nexport * from './db';\nexport * from './nutrition';\nexport * from './requirements';\nexport * from './state';\nexport * from './storage';\nexport * from './types';\nexport * from './ui';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/contracts",
3
- "version": "1.18.3",
3
+ "version": "1.19.0",
4
4
  "main": "./dist/index.js",
5
5
  "dependencies": {
6
6
  "@ankhorage/color-theory": "^0.0.7"
@@ -25,6 +25,10 @@
25
25
  "types": "./dist/bindings.d.ts",
26
26
  "default": "./dist/bindings.js"
27
27
  },
28
+ "./cli": {
29
+ "types": "./dist/cli.d.ts",
30
+ "default": "./dist/cli.js"
31
+ },
28
32
  "./data": {
29
33
  "types": "./dist/data/index.d.ts",
30
34
  "default": "./dist/data/index.js"
@@ -0,0 +1,88 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import type {
4
+ AnkhCapabilityId,
5
+ AnkhCommandDescriptor,
6
+ AnkhCommandProviderManifest,
7
+ AnkhPackageMetadata,
8
+ } from './index';
9
+
10
+ describe('cli contracts', () => {
11
+ it('accepts provider package metadata and provider manifests', () => {
12
+ const packageMetadata = {
13
+ category: 'infra',
14
+ provider: './dist/ankh.provider.js',
15
+ capabilities: ['infra.up', 'infra.status'],
16
+ } as const satisfies AnkhPackageMetadata;
17
+
18
+ const upCommand = {
19
+ path: ['up'],
20
+ summary: 'Bring project infrastructure up',
21
+ capability: 'infra.up',
22
+ aliases: ['start'],
23
+ examples: ['ankh infra up shop'],
24
+ } as const satisfies AnkhCommandDescriptor;
25
+
26
+ const manifest = {
27
+ id: '@ankhorage/infra',
28
+ category: 'infra',
29
+ version: '1.0.0',
30
+ capabilities: ['infra.up', 'infra.status'],
31
+ commands: [upCommand],
32
+ } as const satisfies AnkhCommandProviderManifest;
33
+
34
+ expect(JSON.parse(JSON.stringify(packageMetadata))).toEqual(packageMetadata);
35
+ expect(JSON.parse(JSON.stringify(manifest))).toEqual(manifest);
36
+ expect(manifest.id).toBe('@ankhorage/infra');
37
+ expect(manifest.category).toBe('infra');
38
+ expect(manifest.commands[0].path).toEqual(['up']);
39
+ });
40
+
41
+ it('accepts metadata-only packages without a provider module', () => {
42
+ const packageMetadata = {
43
+ category: 'contracts',
44
+ provider: null,
45
+ capabilities: ['contracts.cli'],
46
+ } as const satisfies AnkhPackageMetadata;
47
+
48
+ expect(JSON.parse(JSON.stringify(packageMetadata))).toEqual(packageMetadata);
49
+ expect(packageMetadata.provider).toBeNull();
50
+ });
51
+
52
+ it('keeps command paths relative to the provider category', () => {
53
+ const androidScanCommand = {
54
+ path: ['android', 'scan'],
55
+ summary: 'Scan an Android target',
56
+ capability: 'dev.android.scan',
57
+ examples: ['ankh dev android scan'],
58
+ } as const satisfies AnkhCommandDescriptor;
59
+
60
+ const manifest = {
61
+ id: '@ankhorage/dev',
62
+ category: 'dev',
63
+ version: '1.0.0',
64
+ capabilities: ['dev.android.scan'],
65
+ commands: [androidScanCommand],
66
+ } as const satisfies AnkhCommandProviderManifest;
67
+
68
+ expect(manifest.category).toBe('dev');
69
+ expect(manifest.commands[0].path).toEqual(['android', 'scan']);
70
+ expect(manifest.commands[0].path[0]).not.toBe(manifest.category);
71
+ });
72
+
73
+ it('accepts dot-separated capability ids for discovery metadata', () => {
74
+ const capabilities = [
75
+ 'infra.up',
76
+ 'templates.list',
77
+ 'board.web.import',
78
+ 'contracts.cli',
79
+ ] as const satisfies readonly AnkhCapabilityId[];
80
+
81
+ expect(capabilities).toEqual([
82
+ 'infra.up',
83
+ 'templates.list',
84
+ 'board.web.import',
85
+ 'contracts.cli',
86
+ ]);
87
+ });
88
+ });
package/src/cli.ts ADDED
@@ -0,0 +1,27 @@
1
+ export type AnkhCommandCategory = string;
2
+
3
+ export type AnkhProviderReference = `./${string}`;
4
+
5
+ export type AnkhCapabilityId = `${string}.${string}`;
6
+
7
+ export interface AnkhCommandDescriptor {
8
+ readonly path: readonly [string, ...string[]];
9
+ readonly summary: string;
10
+ readonly capability: AnkhCapabilityId;
11
+ readonly aliases?: readonly string[];
12
+ readonly examples?: readonly string[];
13
+ }
14
+
15
+ export interface AnkhCommandProviderManifest {
16
+ readonly id: string;
17
+ readonly category: AnkhCommandCategory;
18
+ readonly version: string;
19
+ readonly capabilities: readonly AnkhCapabilityId[];
20
+ readonly commands: readonly AnkhCommandDescriptor[];
21
+ }
22
+
23
+ export interface AnkhPackageMetadata {
24
+ readonly category: AnkhCommandCategory;
25
+ readonly provider: AnkhProviderReference | null;
26
+ readonly capabilities: readonly AnkhCapabilityId[];
27
+ }
@@ -56,6 +56,17 @@ async function collectTypeScriptFiles(directory: string): Promise<string[]> {
56
56
  }
57
57
 
58
58
  describe('contracts', () => {
59
+ it('exports the cli subpath for Ankh discovery contracts', async () => {
60
+ const packageJson = JSON.parse(await readFile(join(process.cwd(), 'package.json'), 'utf8')) as {
61
+ exports?: Record<string, { default?: string; types?: string }>;
62
+ };
63
+
64
+ expect(packageJson.exports?.['./cli']).toEqual({
65
+ types: './dist/cli.d.ts',
66
+ default: './dist/cli.js',
67
+ });
68
+ });
69
+
59
70
  it('exports stable platform constants', () => {
60
71
  expect(NAVIGATOR_TYPES).toEqual(['stack', 'tabs', 'drawer']);
61
72
  expect(APP_CATEGORIES).toEqual([
@@ -213,7 +224,7 @@ describe('contracts', () => {
213
224
 
214
225
  it('ThemeModeConfig.harmony accepts all ColorHarmony values', () => {
215
226
  for (const harmony of COLOR_HARMONIES) {
216
- const config: ThemeModeConfig = { primaryColor: '#ff0000', harmony };
227
+ const config = { primaryColor: '#ff0000', harmony } satisfies ThemeModeConfig;
217
228
  expect(config.harmony).toBe(harmony);
218
229
  }
219
230
  });
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './auth';
2
2
  export * from './bindings';
3
+ export * from './cli';
3
4
  export * from './data';
4
5
  export * from './db';
5
6
  export * from './nutrition';