@cli-schema/spec 0.1.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/README.md +149 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/intent.d.ts +3 -0
- package/dist/intent.d.ts.map +1 -0
- package/dist/intent.js +14 -0
- package/dist/intent.js.map +1 -0
- package/dist/meta-schema.json +213 -0
- package/dist/types.d.ts +122 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +23 -0
- package/dist/validate.js.map +1 -0
- package/package.json +33 -0
- package/src/index.ts +8 -0
- package/src/intent.ts +29 -0
- package/src/meta-schema.json +213 -0
- package/src/types.ts +184 -0
- package/src/validate.ts +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# @cli-schema/spec
|
|
2
|
+
|
|
3
|
+
TypeScript types, the vendored meta-schema, and a validator for the
|
|
4
|
+
[CLI Schema specification](https://github.com/cli-schema/cli-schema) v1 — the open standard for
|
|
5
|
+
describing command-line interfaces in a language-agnostic, machine-readable way.
|
|
6
|
+
|
|
7
|
+
This is the only package in the [`cli-schema-js`](https://github.com/cli-schema/cli-schema-js)
|
|
8
|
+
monorepo with no CLI-framework dependency. It defines the *shape* of a CLI Schema document;
|
|
9
|
+
[`@cli-schema/commander`](https://www.npmjs.com/package/@cli-schema/commander) is what actually
|
|
10
|
+
produces one from a real CLI.
|
|
11
|
+
|
|
12
|
+
## Quickstart
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @cli-schema/spec
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { validate, SCHEMA_VERSION, type CliSchema } from '@cli-schema/spec'
|
|
20
|
+
|
|
21
|
+
const doc: CliSchema = {
|
|
22
|
+
schemaVersion: SCHEMA_VERSION,
|
|
23
|
+
name: 'mytool',
|
|
24
|
+
version: '1.0.0',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const result = validate(doc)
|
|
28
|
+
if (!result.valid) throw new Error(JSON.stringify(result.errors, null, 2))
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That's the minimum: a document only needs `schemaVersion`, `name`, and `version` to be valid —
|
|
32
|
+
everything else (commands, parameters, environment, intent, ...) is optional enrichment.
|
|
33
|
+
|
|
34
|
+
## Purpose
|
|
35
|
+
|
|
36
|
+
CLI help text is written for humans, completion scripts are hand-authored per shell, and AI
|
|
37
|
+
agents guess flags. CLI Schema fixes this by defining one JSON document format that describes a
|
|
38
|
+
program's commands, parameters, intent, auth requirements, environment dependencies, and output
|
|
39
|
+
capabilities — so agents, IDEs, shell-completion generators, and docs tooling can all read the
|
|
40
|
+
same document instead of re-deriving it.
|
|
41
|
+
|
|
42
|
+
`@cli-schema/spec` is the compile-time and runtime contract for that format: the types you build
|
|
43
|
+
a document against, and the validator you check it with before trusting it.
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **Full v1 type coverage** — every object in the spec has a corresponding TypeScript interface:
|
|
48
|
+
`CliSchema` (root), `Command`, `Namespace`, `Parameter`, `Constraint`, `Intent`, `Output`,
|
|
49
|
+
`Environment`, `EnvVar`, `ConfigFile`, `DefaultHandler`, `Shortcut`, `Deprecation`.
|
|
50
|
+
- **The normative meta-schema, vendored** — `schema/cli-schema.meta-schema.json` from the spec
|
|
51
|
+
repo, re-exported at `@cli-schema/spec/meta-schema.json` for tools that want the raw JSON
|
|
52
|
+
Schema directly (e.g. to validate in a non-JS pipeline, or feed to a codegen tool).
|
|
53
|
+
- **A ready-to-use validator** — `validate()` / `assertValid()`, backed by
|
|
54
|
+
[ajv](https://ajv.js.org)'s 2020-12 dialect support. No need to wire up ajv yourself.
|
|
55
|
+
- **`inferIntentFromHttp`** — a small pure helper for CLIs that wrap HTTP APIs: derives a
|
|
56
|
+
reasonable {@link Intent} from an HTTP verb (`GET`/`HEAD` → safe & idempotent, `PUT` →
|
|
57
|
+
idempotent, `DELETE` → destructive & idempotent, `POST`/`PATCH` → unset, since their semantics
|
|
58
|
+
are endpoint-specific).
|
|
59
|
+
- **Vendor extensions typed** — every object that permits `x-` prefixed fields (spec §14) is
|
|
60
|
+
typed to accept them (`[extension: \`x-${string}\`]: unknown`), so `x-my-tool: {...}` type-checks
|
|
61
|
+
without `as any`.
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
### Building a document by hand
|
|
66
|
+
|
|
67
|
+
The types double as documentation of the format — reach for the [spec
|
|
68
|
+
itself](https://github.com/cli-schema/cli-schema/blob/main/spec/v1/README.md) for full field
|
|
69
|
+
semantics, but a well-populated document looks like:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import type { CliSchema } from '@cli-schema/spec'
|
|
73
|
+
|
|
74
|
+
const doc: CliSchema = {
|
|
75
|
+
schemaVersion: 1,
|
|
76
|
+
name: 'gh',
|
|
77
|
+
version: '2.45.0',
|
|
78
|
+
description: 'GitHub CLI — bring GitHub to your terminal',
|
|
79
|
+
requiresAuth: true,
|
|
80
|
+
authCommands: ['auth login', 'auth logout'],
|
|
81
|
+
reservedMetaCommands: ['__schema'],
|
|
82
|
+
commands: [
|
|
83
|
+
{
|
|
84
|
+
name: 'status',
|
|
85
|
+
summary: 'Print information about relevant issues, pull requests, and notifications',
|
|
86
|
+
intent: { destructive: false, idempotent: true, requiresAuth: true },
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
namespaces: [
|
|
90
|
+
{
|
|
91
|
+
segment: 'repo',
|
|
92
|
+
commands: [
|
|
93
|
+
{
|
|
94
|
+
name: 'delete',
|
|
95
|
+
path: ['repo'],
|
|
96
|
+
parameters: [
|
|
97
|
+
{ role: 'positional', name: 'repository', type: 'string', required: true },
|
|
98
|
+
{ role: 'confirmationSkip', name: 'yes', type: 'boolean', required: false },
|
|
99
|
+
],
|
|
100
|
+
intent: { destructive: true, idempotent: true, scope: 'global', requiresConfirmation: true },
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
In practice you'll usually generate this from a live CLI rather than write it by hand — see
|
|
109
|
+
[`@cli-schema/commander`](https://www.npmjs.com/package/@cli-schema/commander).
|
|
110
|
+
|
|
111
|
+
### Validating an untrusted document
|
|
112
|
+
|
|
113
|
+
Per spec §15, consumers should treat schema documents as untrusted input:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { validate, assertValid } from '@cli-schema/spec'
|
|
117
|
+
|
|
118
|
+
const { valid, errors } = validate(untrustedDoc)
|
|
119
|
+
// errors is an ajv ErrorObject[] — empty when valid
|
|
120
|
+
|
|
121
|
+
assertValid(untrustedDoc) // throws with a formatted message if invalid
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Reading the raw meta-schema
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import metaSchema from '@cli-schema/spec/meta-schema.json' with { type: 'json' }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Useful if you want to validate documents with your own ajv instance, a different validator
|
|
131
|
+
entirely, or a non-JS tool.
|
|
132
|
+
|
|
133
|
+
### Deriving intent from an HTTP verb
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { inferIntentFromHttp } from '@cli-schema/spec'
|
|
137
|
+
|
|
138
|
+
inferIntentFromHttp('DELETE') // { destructive: true, idempotent: true, scope: 'global' }
|
|
139
|
+
inferIntentFromHttp('POST') // undefined — annotate explicitly, semantics vary per endpoint
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Related packages
|
|
143
|
+
|
|
144
|
+
- [`@cli-schema/zod`](https://www.npmjs.com/package/@cli-schema/zod) — derive `Parameter`s from Zod schemas
|
|
145
|
+
- [`@cli-schema/commander`](https://www.npmjs.com/package/@cli-schema/commander) — generate a full `CliSchema` from a live Commander tree
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
|
package/dist/intent.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAWxC,wBAAgB,mBAAmB,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAYvE"}
|
package/dist/intent.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function inferIntentFromHttp(method) {
|
|
2
|
+
switch (method.toUpperCase()) {
|
|
3
|
+
case 'GET':
|
|
4
|
+
case 'HEAD':
|
|
5
|
+
return { destructive: false, idempotent: true, scope: 'global' };
|
|
6
|
+
case 'PUT':
|
|
7
|
+
return { destructive: false, idempotent: true, scope: 'global' };
|
|
8
|
+
case 'DELETE':
|
|
9
|
+
return { destructive: true, idempotent: true, scope: 'global' };
|
|
10
|
+
default:
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=intent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent.js","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"AAgBA,MAAM,UAAU,mBAAmB,CAAE,MAAc;IACjD,QAAQ,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;QAC7B,KAAK,KAAK,CAAC;QACX,KAAK,MAAM;YACT,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;QAClE,KAAK,KAAK;YACR,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;QAClE,KAAK,QAAQ;YACX,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;QACjE;YACE,OAAO,SAAS,CAAA;IACpB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://cli-schema.github.io/cli-schema/schema/cli-schema.meta-schema.json",
|
|
4
|
+
"title": "CLI Schema",
|
|
5
|
+
"description": "Meta-schema for CLI Schema documents (specification v1)",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"$defs": {
|
|
8
|
+
"deprecation": {
|
|
9
|
+
"oneOf": [
|
|
10
|
+
{ "type": "boolean", "const": true },
|
|
11
|
+
{
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"message": { "type": "string" },
|
|
15
|
+
"since": { "type": "string" },
|
|
16
|
+
"removedIn": { "type": "string" }
|
|
17
|
+
},
|
|
18
|
+
"additionalProperties": false
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"constraint": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"required": ["kind"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"kind": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": [
|
|
29
|
+
"range", "timeSpanRange", "length", "count", "regex",
|
|
30
|
+
"allowed", "denied", "email", "url", "uriScheme",
|
|
31
|
+
"fileExtensions", "existing", "nonExisting", "rejectSymbolicLinks"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"min": { "type": "string" },
|
|
35
|
+
"max": { "type": "string" },
|
|
36
|
+
"pattern": { "type": "string" },
|
|
37
|
+
"values": { "type": "array", "items": { "type": "string" } }
|
|
38
|
+
},
|
|
39
|
+
"additionalProperties": false
|
|
40
|
+
},
|
|
41
|
+
"parameter": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": ["role", "name", "type", "required"],
|
|
44
|
+
"properties": {
|
|
45
|
+
"role": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"enum": ["flag", "positional", "confirmationSkip", "dryRun"]
|
|
48
|
+
},
|
|
49
|
+
"name": { "type": "string" },
|
|
50
|
+
"shortName": { "type": "string", "maxLength": 1 },
|
|
51
|
+
"type": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"enum": ["string", "integer", "number", "boolean", "array", "enum"]
|
|
54
|
+
},
|
|
55
|
+
"required": { "type": "boolean" },
|
|
56
|
+
"summary": { "type": "string" },
|
|
57
|
+
"defaultValue": { "type": "string" },
|
|
58
|
+
"repeatable": { "type": "boolean", "default": false },
|
|
59
|
+
"separator": { "type": "string" },
|
|
60
|
+
"aliases": { "type": "array", "items": { "type": "string" } },
|
|
61
|
+
"enumValues": { "type": "array", "items": { "type": "string" } },
|
|
62
|
+
"elementType": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"enum": ["string", "integer", "number", "boolean"]
|
|
65
|
+
},
|
|
66
|
+
"hidden": { "type": "boolean", "default": false },
|
|
67
|
+
"variadic": { "type": "boolean", "default": false },
|
|
68
|
+
"deprecated": { "$ref": "#/$defs/deprecation" },
|
|
69
|
+
"validations": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"items": { "$ref": "#/$defs/constraint" }
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"patternProperties": { "^x-": {} },
|
|
75
|
+
"additionalProperties": false
|
|
76
|
+
},
|
|
77
|
+
"intent": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {
|
|
80
|
+
"destructive": { "type": "boolean" },
|
|
81
|
+
"idempotent": { "type": "boolean" },
|
|
82
|
+
"scope": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"enum": ["file", "directory", "global"]
|
|
85
|
+
},
|
|
86
|
+
"requiresConfirmation": { "type": "boolean" },
|
|
87
|
+
"requiresAuth": { "type": "boolean" }
|
|
88
|
+
},
|
|
89
|
+
"additionalProperties": false
|
|
90
|
+
},
|
|
91
|
+
"output": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"properties": {
|
|
94
|
+
"formats": { "type": "array", "items": { "type": "string" } },
|
|
95
|
+
"formatFlag": { "type": "string" }
|
|
96
|
+
},
|
|
97
|
+
"additionalProperties": false
|
|
98
|
+
},
|
|
99
|
+
"envVar": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"required": ["name"],
|
|
102
|
+
"properties": {
|
|
103
|
+
"name": { "type": "string" },
|
|
104
|
+
"required": { "type": "boolean", "default": false },
|
|
105
|
+
"description": { "type": "string" },
|
|
106
|
+
"defaultValue": { "type": "string" }
|
|
107
|
+
},
|
|
108
|
+
"additionalProperties": false
|
|
109
|
+
},
|
|
110
|
+
"configFile": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"required": ["path"],
|
|
113
|
+
"properties": {
|
|
114
|
+
"path": { "type": "string" },
|
|
115
|
+
"description": { "type": "string" },
|
|
116
|
+
"required": { "type": "boolean", "default": false }
|
|
117
|
+
},
|
|
118
|
+
"additionalProperties": false
|
|
119
|
+
},
|
|
120
|
+
"environment": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"properties": {
|
|
123
|
+
"variables": { "type": "array", "items": { "$ref": "#/$defs/envVar" } },
|
|
124
|
+
"configFiles": { "type": "array", "items": { "$ref": "#/$defs/configFile" } }
|
|
125
|
+
},
|
|
126
|
+
"additionalProperties": false
|
|
127
|
+
},
|
|
128
|
+
"defaultHandler": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"required": ["kind"],
|
|
131
|
+
"properties": {
|
|
132
|
+
"kind": { "type": "string", "enum": ["root", "namespace"] },
|
|
133
|
+
"summary": { "type": "string" },
|
|
134
|
+
"notes": { "type": "string" },
|
|
135
|
+
"usage": { "type": "string" },
|
|
136
|
+
"examples": { "type": "array", "items": { "type": "string" } },
|
|
137
|
+
"parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
|
|
138
|
+
"hidden": { "type": "boolean", "default": false }
|
|
139
|
+
},
|
|
140
|
+
"patternProperties": { "^x-": {} },
|
|
141
|
+
"additionalProperties": false
|
|
142
|
+
},
|
|
143
|
+
"command": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"required": ["name"],
|
|
146
|
+
"properties": {
|
|
147
|
+
"path": { "type": "array", "items": { "type": "string" } },
|
|
148
|
+
"name": { "type": "string" },
|
|
149
|
+
"summary": { "type": "string" },
|
|
150
|
+
"notes": { "type": "string" },
|
|
151
|
+
"usage": { "type": "string" },
|
|
152
|
+
"examples": { "type": "array", "items": { "type": "string" } },
|
|
153
|
+
"parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
|
|
154
|
+
"aliases": { "type": "array", "items": { "type": "string" } },
|
|
155
|
+
"hidden": { "type": "boolean", "default": false },
|
|
156
|
+
"tags": { "type": "array", "items": { "type": "string" } },
|
|
157
|
+
"deprecated": { "$ref": "#/$defs/deprecation" },
|
|
158
|
+
"intent": { "$ref": "#/$defs/intent" },
|
|
159
|
+
"output": { "$ref": "#/$defs/output" },
|
|
160
|
+
"streaming": { "type": "boolean", "default": false },
|
|
161
|
+
"longRunning": { "type": "boolean", "default": false }
|
|
162
|
+
},
|
|
163
|
+
"patternProperties": { "^x-": {} },
|
|
164
|
+
"additionalProperties": false
|
|
165
|
+
},
|
|
166
|
+
"namespace": {
|
|
167
|
+
"type": "object",
|
|
168
|
+
"required": ["segment"],
|
|
169
|
+
"properties": {
|
|
170
|
+
"segment": { "type": "string" },
|
|
171
|
+
"summary": { "type": "string" },
|
|
172
|
+
"notes": { "type": "string" },
|
|
173
|
+
"options": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
|
|
174
|
+
"defaultCommand": { "$ref": "#/$defs/defaultHandler" },
|
|
175
|
+
"commands": { "type": "array", "items": { "$ref": "#/$defs/command" } },
|
|
176
|
+
"namespaces": { "type": "array", "items": { "$ref": "#/$defs/namespace" } }
|
|
177
|
+
},
|
|
178
|
+
"patternProperties": { "^x-": {} },
|
|
179
|
+
"additionalProperties": false
|
|
180
|
+
},
|
|
181
|
+
"shortcut": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"required": ["from", "to"],
|
|
184
|
+
"properties": {
|
|
185
|
+
"from": { "type": "string" },
|
|
186
|
+
"to": { "type": "array", "items": { "type": "string" }, "minItems": 1 }
|
|
187
|
+
},
|
|
188
|
+
"additionalProperties": false
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
"required": ["schemaVersion", "name", "version"],
|
|
192
|
+
"properties": {
|
|
193
|
+
"schemaVersion": {
|
|
194
|
+
"type": "integer",
|
|
195
|
+
"const": 1
|
|
196
|
+
},
|
|
197
|
+
"name": { "type": "string" },
|
|
198
|
+
"version": { "type": "string" },
|
|
199
|
+
"description": { "type": "string" },
|
|
200
|
+
"tags": { "type": "array", "items": { "type": "string" } },
|
|
201
|
+
"requiresAuth": { "type": "boolean" },
|
|
202
|
+
"authCommands": { "type": "array", "items": { "type": "string" } },
|
|
203
|
+
"environment": { "$ref": "#/$defs/environment" },
|
|
204
|
+
"reservedMetaCommands": { "type": "array", "items": { "type": "string" } },
|
|
205
|
+
"globalOptions": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
|
|
206
|
+
"rootDefault": { "$ref": "#/$defs/defaultHandler" },
|
|
207
|
+
"commands": { "type": "array", "items": { "$ref": "#/$defs/command" } },
|
|
208
|
+
"namespaces": { "type": "array", "items": { "$ref": "#/$defs/namespace" } },
|
|
209
|
+
"shortcuts": { "type": "array", "items": { "$ref": "#/$defs/shortcut" } }
|
|
210
|
+
},
|
|
211
|
+
"patternProperties": { "^x-": {} },
|
|
212
|
+
"additionalProperties": false
|
|
213
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export declare const SCHEMA_VERSION: 1;
|
|
2
|
+
export type ParameterType = 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'enum';
|
|
3
|
+
export type ParameterElementType = 'string' | 'integer' | 'number' | 'boolean';
|
|
4
|
+
export type ParameterRole = 'flag' | 'positional' | 'confirmationSkip' | 'dryRun';
|
|
5
|
+
export type ConstraintKind = 'range' | 'timeSpanRange' | 'length' | 'count' | 'regex' | 'allowed' | 'denied' | 'email' | 'url' | 'uriScheme' | 'fileExtensions' | 'existing' | 'nonExisting' | 'rejectSymbolicLinks';
|
|
6
|
+
export interface Constraint {
|
|
7
|
+
kind: ConstraintKind;
|
|
8
|
+
min?: string;
|
|
9
|
+
max?: string;
|
|
10
|
+
pattern?: string;
|
|
11
|
+
values?: string[];
|
|
12
|
+
}
|
|
13
|
+
export type Deprecation = true | {
|
|
14
|
+
message?: string;
|
|
15
|
+
since?: string;
|
|
16
|
+
removedIn?: string;
|
|
17
|
+
};
|
|
18
|
+
export interface Parameter {
|
|
19
|
+
role: ParameterRole;
|
|
20
|
+
name: string;
|
|
21
|
+
type: ParameterType;
|
|
22
|
+
required: boolean;
|
|
23
|
+
shortName?: string;
|
|
24
|
+
summary?: string;
|
|
25
|
+
defaultValue?: string;
|
|
26
|
+
repeatable?: boolean;
|
|
27
|
+
separator?: string;
|
|
28
|
+
aliases?: string[];
|
|
29
|
+
enumValues?: string[];
|
|
30
|
+
elementType?: ParameterElementType;
|
|
31
|
+
hidden?: boolean;
|
|
32
|
+
variadic?: boolean;
|
|
33
|
+
deprecated?: Deprecation;
|
|
34
|
+
validations?: Constraint[];
|
|
35
|
+
[extension: `x-${string}`]: unknown;
|
|
36
|
+
}
|
|
37
|
+
export interface Intent {
|
|
38
|
+
destructive?: boolean;
|
|
39
|
+
idempotent?: boolean;
|
|
40
|
+
scope?: 'file' | 'directory' | 'global';
|
|
41
|
+
requiresConfirmation?: boolean;
|
|
42
|
+
requiresAuth?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface Output {
|
|
45
|
+
formats?: string[];
|
|
46
|
+
formatFlag?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface DefaultHandler {
|
|
49
|
+
kind: 'root' | 'namespace';
|
|
50
|
+
summary?: string;
|
|
51
|
+
notes?: string;
|
|
52
|
+
usage?: string;
|
|
53
|
+
examples?: string[];
|
|
54
|
+
parameters?: Parameter[];
|
|
55
|
+
hidden?: boolean;
|
|
56
|
+
[extension: `x-${string}`]: unknown;
|
|
57
|
+
}
|
|
58
|
+
export interface Command {
|
|
59
|
+
name: string;
|
|
60
|
+
path?: string[];
|
|
61
|
+
summary?: string;
|
|
62
|
+
notes?: string;
|
|
63
|
+
usage?: string;
|
|
64
|
+
examples?: string[];
|
|
65
|
+
parameters?: Parameter[];
|
|
66
|
+
aliases?: string[];
|
|
67
|
+
hidden?: boolean;
|
|
68
|
+
tags?: string[];
|
|
69
|
+
deprecated?: Deprecation;
|
|
70
|
+
intent?: Intent;
|
|
71
|
+
output?: Output;
|
|
72
|
+
streaming?: boolean;
|
|
73
|
+
longRunning?: boolean;
|
|
74
|
+
[extension: `x-${string}`]: unknown;
|
|
75
|
+
}
|
|
76
|
+
export interface Namespace {
|
|
77
|
+
segment: string;
|
|
78
|
+
summary?: string;
|
|
79
|
+
notes?: string;
|
|
80
|
+
options?: Parameter[];
|
|
81
|
+
defaultCommand?: DefaultHandler;
|
|
82
|
+
commands?: Command[];
|
|
83
|
+
namespaces?: Namespace[];
|
|
84
|
+
[extension: `x-${string}`]: unknown;
|
|
85
|
+
}
|
|
86
|
+
export interface EnvVar {
|
|
87
|
+
name: string;
|
|
88
|
+
required?: boolean;
|
|
89
|
+
description?: string;
|
|
90
|
+
defaultValue?: string;
|
|
91
|
+
}
|
|
92
|
+
export interface ConfigFile {
|
|
93
|
+
path: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
required?: boolean;
|
|
96
|
+
}
|
|
97
|
+
export interface Environment {
|
|
98
|
+
variables?: EnvVar[];
|
|
99
|
+
configFiles?: ConfigFile[];
|
|
100
|
+
}
|
|
101
|
+
export interface Shortcut {
|
|
102
|
+
from: string;
|
|
103
|
+
to: string[];
|
|
104
|
+
}
|
|
105
|
+
export interface CliSchema {
|
|
106
|
+
schemaVersion: typeof SCHEMA_VERSION;
|
|
107
|
+
name: string;
|
|
108
|
+
version: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
tags?: string[];
|
|
111
|
+
requiresAuth?: boolean;
|
|
112
|
+
authCommands?: string[];
|
|
113
|
+
environment?: Environment;
|
|
114
|
+
reservedMetaCommands?: string[];
|
|
115
|
+
globalOptions?: Parameter[];
|
|
116
|
+
rootDefault?: DefaultHandler;
|
|
117
|
+
commands?: Command[];
|
|
118
|
+
namespaces?: Namespace[];
|
|
119
|
+
shortcuts?: Shortcut[];
|
|
120
|
+
[extension: `x-${string}`]: unknown;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAU,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ErrorObject } from 'ajv';
|
|
2
|
+
export interface ValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: ErrorObject[];
|
|
5
|
+
}
|
|
6
|
+
export declare function validate(doc: unknown): ValidationResult;
|
|
7
|
+
export declare function assertValid(doc: unknown): void;
|
|
8
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AAGtC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,WAAW,EAAE,CAAA;CACtB;AAkBD,wBAAgB,QAAQ,CAAE,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAIxD;AAGD,wBAAgB,WAAW,CAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAM/C"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
2
|
+
import metaSchema from './meta-schema.json' with { type: 'json' };
|
|
3
|
+
let compiled;
|
|
4
|
+
function getValidator() {
|
|
5
|
+
if (compiled == null) {
|
|
6
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
7
|
+
compiled = ajv.compile(metaSchema);
|
|
8
|
+
}
|
|
9
|
+
return compiled;
|
|
10
|
+
}
|
|
11
|
+
export function validate(doc) {
|
|
12
|
+
const validator = getValidator();
|
|
13
|
+
const valid = validator(doc);
|
|
14
|
+
return { valid, errors: valid ? [] : [...(validator.errors ?? [])] };
|
|
15
|
+
}
|
|
16
|
+
export function assertValid(doc) {
|
|
17
|
+
const result = validate(doc);
|
|
18
|
+
if (!result.valid) {
|
|
19
|
+
const details = result.errors.map((e) => ` - ${e.instancePath || '/'}: ${e.message ?? 'invalid'}`).join('\n');
|
|
20
|
+
throw new Error(`Invalid CLI Schema document:\n${details}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAE1C,OAAO,UAAU,MAAM,oBAAoB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAOjE,IAAI,QAAoD,CAAA;AAExD,SAAS,YAAY;IACnB,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;QAC3D,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IACpC,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAQD,MAAM,UAAU,QAAQ,CAAE,GAAY;IACpC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAY,CAAA;IACvC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,EAAE,CAAA;AACtE,CAAC;AAGD,MAAM,UAAU,WAAW,CAAE,GAAY;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,YAAY,IAAI,GAAG,KAAK,CAAC,CAAC,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9G,MAAM,IAAI,KAAK,CAAC,iCAAiC,OAAO,EAAE,CAAC,CAAA;IAC7D,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cli-schema/spec",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript types, meta-schema, and validator for the CLI Schema specification (https://github.com/cli-schema/cli-schema)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./meta-schema.json": "./dist/meta-schema.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -b",
|
|
23
|
+
"test": "node --import tsx/esm --test test/**/*.test.ts"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"ajv": "^8.17.1"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/cli-schema/cli-schema-js.git",
|
|
31
|
+
"directory": "packages/spec"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.ts
ADDED
package/src/intent.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright cli-schema contributors
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Intent } from './types.ts'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Derives an {@link Intent} from an HTTP method, for CLIs that wrap HTTP APIs.
|
|
10
|
+
*
|
|
11
|
+
* GET and HEAD are safe and idempotent.
|
|
12
|
+
* PUT is idempotent (upsert semantics).
|
|
13
|
+
* DELETE is destructive and idempotent.
|
|
14
|
+
* POST and PATCH are left `undefined` — their semantics depend on the specific endpoint
|
|
15
|
+
* and should be annotated explicitly by the caller.
|
|
16
|
+
*/
|
|
17
|
+
export function inferIntentFromHttp (method: string): Intent | undefined {
|
|
18
|
+
switch (method.toUpperCase()) {
|
|
19
|
+
case 'GET':
|
|
20
|
+
case 'HEAD':
|
|
21
|
+
return { destructive: false, idempotent: true, scope: 'global' }
|
|
22
|
+
case 'PUT':
|
|
23
|
+
return { destructive: false, idempotent: true, scope: 'global' }
|
|
24
|
+
case 'DELETE':
|
|
25
|
+
return { destructive: true, idempotent: true, scope: 'global' }
|
|
26
|
+
default:
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://cli-schema.github.io/cli-schema/schema/cli-schema.meta-schema.json",
|
|
4
|
+
"title": "CLI Schema",
|
|
5
|
+
"description": "Meta-schema for CLI Schema documents (specification v1)",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"$defs": {
|
|
8
|
+
"deprecation": {
|
|
9
|
+
"oneOf": [
|
|
10
|
+
{ "type": "boolean", "const": true },
|
|
11
|
+
{
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"message": { "type": "string" },
|
|
15
|
+
"since": { "type": "string" },
|
|
16
|
+
"removedIn": { "type": "string" }
|
|
17
|
+
},
|
|
18
|
+
"additionalProperties": false
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"constraint": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"required": ["kind"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"kind": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": [
|
|
29
|
+
"range", "timeSpanRange", "length", "count", "regex",
|
|
30
|
+
"allowed", "denied", "email", "url", "uriScheme",
|
|
31
|
+
"fileExtensions", "existing", "nonExisting", "rejectSymbolicLinks"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"min": { "type": "string" },
|
|
35
|
+
"max": { "type": "string" },
|
|
36
|
+
"pattern": { "type": "string" },
|
|
37
|
+
"values": { "type": "array", "items": { "type": "string" } }
|
|
38
|
+
},
|
|
39
|
+
"additionalProperties": false
|
|
40
|
+
},
|
|
41
|
+
"parameter": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": ["role", "name", "type", "required"],
|
|
44
|
+
"properties": {
|
|
45
|
+
"role": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"enum": ["flag", "positional", "confirmationSkip", "dryRun"]
|
|
48
|
+
},
|
|
49
|
+
"name": { "type": "string" },
|
|
50
|
+
"shortName": { "type": "string", "maxLength": 1 },
|
|
51
|
+
"type": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"enum": ["string", "integer", "number", "boolean", "array", "enum"]
|
|
54
|
+
},
|
|
55
|
+
"required": { "type": "boolean" },
|
|
56
|
+
"summary": { "type": "string" },
|
|
57
|
+
"defaultValue": { "type": "string" },
|
|
58
|
+
"repeatable": { "type": "boolean", "default": false },
|
|
59
|
+
"separator": { "type": "string" },
|
|
60
|
+
"aliases": { "type": "array", "items": { "type": "string" } },
|
|
61
|
+
"enumValues": { "type": "array", "items": { "type": "string" } },
|
|
62
|
+
"elementType": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"enum": ["string", "integer", "number", "boolean"]
|
|
65
|
+
},
|
|
66
|
+
"hidden": { "type": "boolean", "default": false },
|
|
67
|
+
"variadic": { "type": "boolean", "default": false },
|
|
68
|
+
"deprecated": { "$ref": "#/$defs/deprecation" },
|
|
69
|
+
"validations": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"items": { "$ref": "#/$defs/constraint" }
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"patternProperties": { "^x-": {} },
|
|
75
|
+
"additionalProperties": false
|
|
76
|
+
},
|
|
77
|
+
"intent": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {
|
|
80
|
+
"destructive": { "type": "boolean" },
|
|
81
|
+
"idempotent": { "type": "boolean" },
|
|
82
|
+
"scope": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"enum": ["file", "directory", "global"]
|
|
85
|
+
},
|
|
86
|
+
"requiresConfirmation": { "type": "boolean" },
|
|
87
|
+
"requiresAuth": { "type": "boolean" }
|
|
88
|
+
},
|
|
89
|
+
"additionalProperties": false
|
|
90
|
+
},
|
|
91
|
+
"output": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"properties": {
|
|
94
|
+
"formats": { "type": "array", "items": { "type": "string" } },
|
|
95
|
+
"formatFlag": { "type": "string" }
|
|
96
|
+
},
|
|
97
|
+
"additionalProperties": false
|
|
98
|
+
},
|
|
99
|
+
"envVar": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"required": ["name"],
|
|
102
|
+
"properties": {
|
|
103
|
+
"name": { "type": "string" },
|
|
104
|
+
"required": { "type": "boolean", "default": false },
|
|
105
|
+
"description": { "type": "string" },
|
|
106
|
+
"defaultValue": { "type": "string" }
|
|
107
|
+
},
|
|
108
|
+
"additionalProperties": false
|
|
109
|
+
},
|
|
110
|
+
"configFile": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"required": ["path"],
|
|
113
|
+
"properties": {
|
|
114
|
+
"path": { "type": "string" },
|
|
115
|
+
"description": { "type": "string" },
|
|
116
|
+
"required": { "type": "boolean", "default": false }
|
|
117
|
+
},
|
|
118
|
+
"additionalProperties": false
|
|
119
|
+
},
|
|
120
|
+
"environment": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"properties": {
|
|
123
|
+
"variables": { "type": "array", "items": { "$ref": "#/$defs/envVar" } },
|
|
124
|
+
"configFiles": { "type": "array", "items": { "$ref": "#/$defs/configFile" } }
|
|
125
|
+
},
|
|
126
|
+
"additionalProperties": false
|
|
127
|
+
},
|
|
128
|
+
"defaultHandler": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"required": ["kind"],
|
|
131
|
+
"properties": {
|
|
132
|
+
"kind": { "type": "string", "enum": ["root", "namespace"] },
|
|
133
|
+
"summary": { "type": "string" },
|
|
134
|
+
"notes": { "type": "string" },
|
|
135
|
+
"usage": { "type": "string" },
|
|
136
|
+
"examples": { "type": "array", "items": { "type": "string" } },
|
|
137
|
+
"parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
|
|
138
|
+
"hidden": { "type": "boolean", "default": false }
|
|
139
|
+
},
|
|
140
|
+
"patternProperties": { "^x-": {} },
|
|
141
|
+
"additionalProperties": false
|
|
142
|
+
},
|
|
143
|
+
"command": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"required": ["name"],
|
|
146
|
+
"properties": {
|
|
147
|
+
"path": { "type": "array", "items": { "type": "string" } },
|
|
148
|
+
"name": { "type": "string" },
|
|
149
|
+
"summary": { "type": "string" },
|
|
150
|
+
"notes": { "type": "string" },
|
|
151
|
+
"usage": { "type": "string" },
|
|
152
|
+
"examples": { "type": "array", "items": { "type": "string" } },
|
|
153
|
+
"parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
|
|
154
|
+
"aliases": { "type": "array", "items": { "type": "string" } },
|
|
155
|
+
"hidden": { "type": "boolean", "default": false },
|
|
156
|
+
"tags": { "type": "array", "items": { "type": "string" } },
|
|
157
|
+
"deprecated": { "$ref": "#/$defs/deprecation" },
|
|
158
|
+
"intent": { "$ref": "#/$defs/intent" },
|
|
159
|
+
"output": { "$ref": "#/$defs/output" },
|
|
160
|
+
"streaming": { "type": "boolean", "default": false },
|
|
161
|
+
"longRunning": { "type": "boolean", "default": false }
|
|
162
|
+
},
|
|
163
|
+
"patternProperties": { "^x-": {} },
|
|
164
|
+
"additionalProperties": false
|
|
165
|
+
},
|
|
166
|
+
"namespace": {
|
|
167
|
+
"type": "object",
|
|
168
|
+
"required": ["segment"],
|
|
169
|
+
"properties": {
|
|
170
|
+
"segment": { "type": "string" },
|
|
171
|
+
"summary": { "type": "string" },
|
|
172
|
+
"notes": { "type": "string" },
|
|
173
|
+
"options": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
|
|
174
|
+
"defaultCommand": { "$ref": "#/$defs/defaultHandler" },
|
|
175
|
+
"commands": { "type": "array", "items": { "$ref": "#/$defs/command" } },
|
|
176
|
+
"namespaces": { "type": "array", "items": { "$ref": "#/$defs/namespace" } }
|
|
177
|
+
},
|
|
178
|
+
"patternProperties": { "^x-": {} },
|
|
179
|
+
"additionalProperties": false
|
|
180
|
+
},
|
|
181
|
+
"shortcut": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"required": ["from", "to"],
|
|
184
|
+
"properties": {
|
|
185
|
+
"from": { "type": "string" },
|
|
186
|
+
"to": { "type": "array", "items": { "type": "string" }, "minItems": 1 }
|
|
187
|
+
},
|
|
188
|
+
"additionalProperties": false
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
"required": ["schemaVersion", "name", "version"],
|
|
192
|
+
"properties": {
|
|
193
|
+
"schemaVersion": {
|
|
194
|
+
"type": "integer",
|
|
195
|
+
"const": 1
|
|
196
|
+
},
|
|
197
|
+
"name": { "type": "string" },
|
|
198
|
+
"version": { "type": "string" },
|
|
199
|
+
"description": { "type": "string" },
|
|
200
|
+
"tags": { "type": "array", "items": { "type": "string" } },
|
|
201
|
+
"requiresAuth": { "type": "boolean" },
|
|
202
|
+
"authCommands": { "type": "array", "items": { "type": "string" } },
|
|
203
|
+
"environment": { "$ref": "#/$defs/environment" },
|
|
204
|
+
"reservedMetaCommands": { "type": "array", "items": { "type": "string" } },
|
|
205
|
+
"globalOptions": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
|
|
206
|
+
"rootDefault": { "$ref": "#/$defs/defaultHandler" },
|
|
207
|
+
"commands": { "type": "array", "items": { "$ref": "#/$defs/command" } },
|
|
208
|
+
"namespaces": { "type": "array", "items": { "$ref": "#/$defs/namespace" } },
|
|
209
|
+
"shortcuts": { "type": "array", "items": { "$ref": "#/$defs/shortcut" } }
|
|
210
|
+
},
|
|
211
|
+
"patternProperties": { "^x-": {} },
|
|
212
|
+
"additionalProperties": false
|
|
213
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright cli-schema contributors
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TypeScript types for CLI Schema specification v1
|
|
8
|
+
* (https://github.com/cli-schema/cli-schema/blob/main/spec/v1/README.md).
|
|
9
|
+
*
|
|
10
|
+
* Field-for-field mirror of the meta-schema (`meta-schema.json`, vendored from the spec
|
|
11
|
+
* repo). Keep both in sync — the meta-schema is the normative source of truth; these types
|
|
12
|
+
* exist purely for compile-time ergonomics.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Always `1` for specification v1. */
|
|
16
|
+
export const SCHEMA_VERSION = 1 as const
|
|
17
|
+
|
|
18
|
+
/** JSON Schema primitive type names used by {@link Parameter.type} and {@link Parameter.elementType}. */
|
|
19
|
+
export type ParameterType = 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'enum'
|
|
20
|
+
|
|
21
|
+
/** Element type names for array-typed parameters (scalars only — see spec §6). */
|
|
22
|
+
export type ParameterElementType = 'string' | 'integer' | 'number' | 'boolean'
|
|
23
|
+
|
|
24
|
+
/** See spec §6.1 Roles. */
|
|
25
|
+
export type ParameterRole = 'flag' | 'positional' | 'confirmationSkip' | 'dryRun'
|
|
26
|
+
|
|
27
|
+
/** See spec §6.2 Constraints. */
|
|
28
|
+
export type ConstraintKind =
|
|
29
|
+
| 'range'
|
|
30
|
+
| 'timeSpanRange'
|
|
31
|
+
| 'length'
|
|
32
|
+
| 'count'
|
|
33
|
+
| 'regex'
|
|
34
|
+
| 'allowed'
|
|
35
|
+
| 'denied'
|
|
36
|
+
| 'email'
|
|
37
|
+
| 'url'
|
|
38
|
+
| 'uriScheme'
|
|
39
|
+
| 'fileExtensions'
|
|
40
|
+
| 'existing'
|
|
41
|
+
| 'nonExisting'
|
|
42
|
+
| 'rejectSymbolicLinks'
|
|
43
|
+
|
|
44
|
+
/** A validation rule applied to a parameter's value (spec §6.2). */
|
|
45
|
+
export interface Constraint {
|
|
46
|
+
kind: ConstraintKind
|
|
47
|
+
min?: string
|
|
48
|
+
max?: string
|
|
49
|
+
pattern?: string
|
|
50
|
+
values?: string[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Structured deprecation metadata (spec §11.1). `true` signals deprecation without version detail. */
|
|
54
|
+
export type Deprecation = true | {
|
|
55
|
+
message?: string
|
|
56
|
+
since?: string
|
|
57
|
+
removedIn?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A single flag or positional argument (spec §6 Parameter Object). */
|
|
61
|
+
export interface Parameter {
|
|
62
|
+
role: ParameterRole
|
|
63
|
+
name: string
|
|
64
|
+
type: ParameterType
|
|
65
|
+
required: boolean
|
|
66
|
+
shortName?: string
|
|
67
|
+
summary?: string
|
|
68
|
+
defaultValue?: string
|
|
69
|
+
repeatable?: boolean
|
|
70
|
+
separator?: string
|
|
71
|
+
aliases?: string[]
|
|
72
|
+
enumValues?: string[]
|
|
73
|
+
elementType?: ParameterElementType
|
|
74
|
+
hidden?: boolean
|
|
75
|
+
variadic?: boolean
|
|
76
|
+
deprecated?: Deprecation
|
|
77
|
+
validations?: Constraint[]
|
|
78
|
+
[extension: `x-${string}`]: unknown
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A command's side-effect profile, for agent risk reasoning (spec §11 Intent Object). */
|
|
82
|
+
export interface Intent {
|
|
83
|
+
destructive?: boolean
|
|
84
|
+
idempotent?: boolean
|
|
85
|
+
scope?: 'file' | 'directory' | 'global'
|
|
86
|
+
requiresConfirmation?: boolean
|
|
87
|
+
requiresAuth?: boolean
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Machine-readable output formats a command supports (spec §12 Output Object). */
|
|
91
|
+
export interface Output {
|
|
92
|
+
formats?: string[]
|
|
93
|
+
formatFlag?: string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** A handler invoked when a program or namespace is called with no subcommand (spec §10). */
|
|
97
|
+
export interface DefaultHandler {
|
|
98
|
+
kind: 'root' | 'namespace'
|
|
99
|
+
summary?: string
|
|
100
|
+
notes?: string
|
|
101
|
+
usage?: string
|
|
102
|
+
examples?: string[]
|
|
103
|
+
parameters?: Parameter[]
|
|
104
|
+
hidden?: boolean
|
|
105
|
+
[extension: `x-${string}`]: unknown
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** A single executable command (spec §7 Command Object). */
|
|
109
|
+
export interface Command {
|
|
110
|
+
name: string
|
|
111
|
+
path?: string[]
|
|
112
|
+
summary?: string
|
|
113
|
+
notes?: string
|
|
114
|
+
usage?: string
|
|
115
|
+
examples?: string[]
|
|
116
|
+
parameters?: Parameter[]
|
|
117
|
+
aliases?: string[]
|
|
118
|
+
hidden?: boolean
|
|
119
|
+
tags?: string[]
|
|
120
|
+
deprecated?: Deprecation
|
|
121
|
+
intent?: Intent
|
|
122
|
+
output?: Output
|
|
123
|
+
streaming?: boolean
|
|
124
|
+
longRunning?: boolean
|
|
125
|
+
[extension: `x-${string}`]: unknown
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A named grouping of commands; namespaces may be nested arbitrarily (spec §8). */
|
|
129
|
+
export interface Namespace {
|
|
130
|
+
segment: string
|
|
131
|
+
summary?: string
|
|
132
|
+
notes?: string
|
|
133
|
+
options?: Parameter[]
|
|
134
|
+
defaultCommand?: DefaultHandler
|
|
135
|
+
commands?: Command[]
|
|
136
|
+
namespaces?: Namespace[]
|
|
137
|
+
[extension: `x-${string}`]: unknown
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** A single environment variable dependency (spec §9.1). */
|
|
141
|
+
export interface EnvVar {
|
|
142
|
+
name: string
|
|
143
|
+
required?: boolean
|
|
144
|
+
description?: string
|
|
145
|
+
defaultValue?: string
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** A single configuration file dependency (spec §9.2). */
|
|
149
|
+
export interface ConfigFile {
|
|
150
|
+
path: string
|
|
151
|
+
description?: string
|
|
152
|
+
required?: boolean
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** External context the program depends on (spec §9 Environment Object). */
|
|
156
|
+
export interface Environment {
|
|
157
|
+
variables?: EnvVar[]
|
|
158
|
+
configFiles?: ConfigFile[]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** A root-level alias that transparently resolves to a deeper namespace or command path (spec §5.1). */
|
|
162
|
+
export interface Shortcut {
|
|
163
|
+
from: string
|
|
164
|
+
to: string[]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The root of a CLI Schema document (spec §5 Root Object). */
|
|
168
|
+
export interface CliSchema {
|
|
169
|
+
schemaVersion: typeof SCHEMA_VERSION
|
|
170
|
+
name: string
|
|
171
|
+
version: string
|
|
172
|
+
description?: string
|
|
173
|
+
tags?: string[]
|
|
174
|
+
requiresAuth?: boolean
|
|
175
|
+
authCommands?: string[]
|
|
176
|
+
environment?: Environment
|
|
177
|
+
reservedMetaCommands?: string[]
|
|
178
|
+
globalOptions?: Parameter[]
|
|
179
|
+
rootDefault?: DefaultHandler
|
|
180
|
+
commands?: Command[]
|
|
181
|
+
namespaces?: Namespace[]
|
|
182
|
+
shortcuts?: Shortcut[]
|
|
183
|
+
[extension: `x-${string}`]: unknown
|
|
184
|
+
}
|
package/src/validate.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright cli-schema contributors
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Ajv2020 } from 'ajv/dist/2020.js'
|
|
7
|
+
import type { ErrorObject } from 'ajv'
|
|
8
|
+
import metaSchema from './meta-schema.json' with { type: 'json' }
|
|
9
|
+
|
|
10
|
+
export interface ValidationResult {
|
|
11
|
+
valid: boolean
|
|
12
|
+
errors: ErrorObject[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let compiled: ReturnType<Ajv2020['compile']> | undefined
|
|
16
|
+
|
|
17
|
+
function getValidator (): ReturnType<Ajv2020['compile']> {
|
|
18
|
+
if (compiled == null) {
|
|
19
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false })
|
|
20
|
+
compiled = ajv.compile(metaSchema)
|
|
21
|
+
}
|
|
22
|
+
return compiled
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validates a candidate document against the CLI Schema v1 meta-schema.
|
|
27
|
+
*
|
|
28
|
+
* Per spec §15 (Security Considerations), consumers should treat schema documents as
|
|
29
|
+
* untrusted input and validate them before acting on their contents.
|
|
30
|
+
*/
|
|
31
|
+
export function validate (doc: unknown): ValidationResult {
|
|
32
|
+
const validator = getValidator()
|
|
33
|
+
const valid = validator(doc) as boolean
|
|
34
|
+
return { valid, errors: valid ? [] : [...(validator.errors ?? [])] }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Throws with a formatted message if `doc` does not conform to the meta-schema. */
|
|
38
|
+
export function assertValid (doc: unknown): void {
|
|
39
|
+
const result = validate(doc)
|
|
40
|
+
if (!result.valid) {
|
|
41
|
+
const details = result.errors.map((e) => ` - ${e.instancePath || '/'}: ${e.message ?? 'invalid'}`).join('\n')
|
|
42
|
+
throw new Error(`Invalid CLI Schema document:\n${details}`)
|
|
43
|
+
}
|
|
44
|
+
}
|