@texuli/spec 0.1.0-next.0 → 0.1.0-next.3
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 +162 -92
- package/dist/catalog.d.ts +1 -1
- package/dist/codegen/index.d.ts +1 -0
- package/dist/codegen/index.js +1 -0
- package/{generated → dist/generated}/typescript/blueprint.d.ts +43 -1
- package/{generated → dist/generated}/typescript/blueprint.js +1 -1
- package/{generated → dist/generated}/typescript/catalog-schema.js +1 -1
- package/{generated → dist/generated}/typescript/diagnostics.d.ts +1 -1
- package/{generated → dist/generated}/typescript/diagnostics.js +1 -1
- package/{generated → dist/generated}/typescript/manifest.js +1 -1
- package/{generated → dist/generated}/typescript/vocabulary.d.ts +32 -3
- package/{generated → dist/generated}/typescript/vocabulary.js +4 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +29 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/versioning.d.ts +39 -0
- package/dist/versioning.js +87 -0
- package/package.json +18 -8
- /package/{generated → dist/generated}/typescript/catalog-schema.d.ts +0 -0
- /package/{generated → dist/generated}/typescript/manifest.d.ts +0 -0
package/README.md
CHANGED
|
@@ -1,98 +1,168 @@
|
|
|
1
1
|
# @texuli/spec
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
| `fixtures/invalid/` | Blueprints that must fail with exact diagnostic codes, one or more per code |
|
|
23
|
-
| `manifest.json` | The spec version, the dialect and a SHA-256 per normative file |
|
|
24
|
-
| `generated/typescript/`, `generated/kotlin/` | The projections. Never edit them |
|
|
25
|
-
| `generated-manifest.json` | A SHA-256 per generated file |
|
|
26
|
-
|
|
27
|
-
## The dialect
|
|
28
|
-
|
|
29
|
-
Schemas use a closed subset of JSON Schema 2020-12 (`DIALECT_KEYWORDS`):
|
|
30
|
-
`type`, `properties`, `required`, `additionalProperties`, `items`, `enum`,
|
|
31
|
-
`const`, string, number and array bounds, `pattern`, and local `$ref` /
|
|
32
|
-
`$defs`. Objects must be closed (`additionalProperties: false`) or explicit
|
|
33
|
-
maps. Anything else fails `checkSchemaDocument`, and generation refuses it.
|
|
34
|
-
|
|
35
|
-
## Exports
|
|
36
|
-
|
|
37
|
-
- `@texuli/spec`, the runtime half:
|
|
38
|
-
- the generated types (`BlueprintEnvelope`, `SpecCatalog`, `SpecValidation` …);
|
|
39
|
-
- `SPEC_COMPONENT_IDS` and `SpecComponentProps`;
|
|
40
|
-
- `validateInstance` (the dialect validator);
|
|
41
|
-
- `validateBlueprint` (the target-neutral blueprint check: envelope
|
|
42
|
-
schema, closed ids and props, prop kinds and enums, required props, no
|
|
43
|
-
platform-extension props from data, the portable profile failing closed,
|
|
44
|
-
and an optional catalog binding);
|
|
45
|
-
- `canonicalJson` and a synchronous `sha256`.
|
|
46
|
-
- `@texuli/spec/codegen`, build time: `buildSpec`, `projectCatalog`,
|
|
47
|
-
`checkConformance`, `formatJson`.
|
|
48
|
-
|
|
49
|
-
URL policy, tree limits, grammar and rendering stay in the runtime validator,
|
|
50
|
-
`@texuli/catalog/validate`.
|
|
51
|
-
|
|
52
|
-
## Projections
|
|
53
|
-
|
|
54
|
-
- **Web extensions.** Props of kind `slot`, `event` or `unknown`, and the
|
|
55
|
-
`class`, `className`, `style` and chart `renderer` props, are marked
|
|
56
|
-
`extension: "web"`. They are left out of `SpecComponentProps`, and data
|
|
57
|
-
that sets them is refused.
|
|
58
|
-
- **Kotlin** (`com.texuli.spec`, Kotlin 1.9+ for `entries` and
|
|
59
|
-
`data object`): data classes, closed enums and static decoders over a
|
|
60
|
-
generated `JsonValue` model, with no library dependency. Decoders check
|
|
61
|
-
JSON types, required and unknown fields, enums and constants. Patterns
|
|
62
|
-
and limits are the shared validator's job.
|
|
63
|
-
- **Stamp.** Every generated file carries the spec version and a digest of
|
|
64
|
-
the normative files. There are no timestamps, and output is
|
|
65
|
-
byte-identical for the same inputs in any order.
|
|
66
|
-
|
|
67
|
-
## Regenerate
|
|
3
|
+
The blueprint contract of [Texuli](https://texuli.com), an MIT-licensed
|
|
4
|
+
Svelte 5 component library by Dailybot, Inc. (*Small pieces. Infinite
|
|
5
|
+
possibilities.*). A **blueprint** is a JSON document, usually written by an
|
|
6
|
+
AI agent, that describes an interface as a tree of components from Texuli's
|
|
7
|
+
closed catalog. `@texuli/spec` is the platform-neutral source of truth for
|
|
8
|
+
that format: the generated TypeScript types, the closed component
|
|
9
|
+
vocabulary, a target-neutral blueprint validator and deterministic hashing.
|
|
10
|
+
|
|
11
|
+
Use it when you generate, store, diff or check blueprints, or build a
|
|
12
|
+
renderer for them. The same contract also generates the Kotlin models used
|
|
13
|
+
by Texuli's Kotlin Multiplatform twin. The package is framework-free: no
|
|
14
|
+
DOM, no Svelte, no Node APIs and no runtime dependencies.
|
|
15
|
+
|
|
16
|
+
> **Status: preview.** Texuli is in development. The packages are published
|
|
17
|
+
> to npm as prereleases under the `next` dist-tag, and there is no stable
|
|
18
|
+
> release yet, so install with `@next`. APIs may still change between
|
|
19
|
+
> previews.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
68
22
|
|
|
69
23
|
```bash
|
|
70
|
-
|
|
71
|
-
pnpm exec tsx packages/spec/scripts/generate.ts # write manifests and projections
|
|
72
|
-
pnpm exec tsx packages/spec/scripts/generate.ts --check # fail on drift
|
|
24
|
+
npm install @texuli/spec@next
|
|
73
25
|
```
|
|
74
26
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add @texuli/spec@next
|
|
29
|
+
# or
|
|
30
|
+
yarn add @texuli/spec@next
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
No peer dependencies and no CSS. The package is ESM-only and ships
|
|
34
|
+
TypeScript declarations. The normative JSON documents (the blueprint
|
|
35
|
+
schema, the component catalog and the fixtures) live in the
|
|
36
|
+
[repository](https://github.com/DailybotHQ/texuli/tree/main/packages/spec);
|
|
37
|
+
they are not part of the npm package.
|
|
38
|
+
|
|
39
|
+
## Quick start
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import {
|
|
43
|
+
canonicalJson,
|
|
44
|
+
type Schema,
|
|
45
|
+
SPEC_COMPONENT_IDS,
|
|
46
|
+
SPEC_VERSION,
|
|
47
|
+
sha256,
|
|
48
|
+
validateInstance,
|
|
49
|
+
} from '@texuli/spec';
|
|
50
|
+
|
|
51
|
+
SPEC_VERSION; // the spec version, e.g. '0.2.0'
|
|
52
|
+
SPEC_COMPONENT_IDS.includes('alert'); // true: the closed component vocabulary
|
|
53
|
+
|
|
54
|
+
// The dialect validator: a closed subset of JSON Schema 2020-12.
|
|
55
|
+
const schema: Schema = {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: { title: { type: 'string', maxLength: 80 } },
|
|
58
|
+
required: ['title'],
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
};
|
|
61
|
+
validateInstance(schema, { title: 'Weekly report' }); // []
|
|
62
|
+
validateInstance(schema, { title: 42, extra: true });
|
|
63
|
+
// [{ code: 'type', path: '$.title', … }, { code: 'additional-property', path: '$.extra', … }]
|
|
64
|
+
|
|
65
|
+
// Deterministic serialization and hashing, identical in every runtime.
|
|
66
|
+
const digest = sha256(canonicalJson({ b: 1, a: [true, null] }));
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
To check a whole blueprint, pass the blueprint schema and catalog documents
|
|
70
|
+
from the repository's `packages/spec/schemas/blueprint.schema.json` and
|
|
71
|
+
`packages/spec/catalog/components.json`:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { type Schema, type SpecCatalog, validateBlueprint } from '@texuli/spec';
|
|
75
|
+
|
|
76
|
+
const result = validateBlueprint(blueprint, {
|
|
77
|
+
blueprintSchema: blueprintSchemaJson as Schema,
|
|
78
|
+
catalog: componentsJson as SpecCatalog,
|
|
79
|
+
});
|
|
80
|
+
if (!result.ok) {
|
|
81
|
+
for (const d of result.diagnostics) console.warn(d.code, d.path, d.message);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
A node with an id outside the catalog fails with `unknown-id`; other codes
|
|
86
|
+
include `schema`, `unknown-prop`, `prop-type`, `enum-value`,
|
|
87
|
+
`missing-required`, `extension-prop`, `catalog-mismatch`,
|
|
88
|
+
`unsupported-profile` and `unknown-slot`.
|
|
89
|
+
|
|
90
|
+
## What's inside
|
|
91
|
+
|
|
92
|
+
| Subpath | Purpose | Headline exports |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `@texuli/spec` | Runtime: types, vocabulary, validation, hashing | `BlueprintEnvelope`, `SpecCatalog`, `SpecValidation` and the other generated types; `SPEC_COMPONENT_IDS`, `SpecComponentProps`, `SPEC_CATALOG_HASH`, `SPEC_VERSION`; `validateBlueprint`, `validateTree`, `validateInstance`, `checkSchemaDocument`; `canonicalJson`, `sha256` |
|
|
95
|
+
| `@texuli/spec/codegen` | Build time: generation and conformance | `buildSpec`, `projectCatalog`, `checkConformance`, `formatJson`, `nextSpecVersion`, `checkVersionHistory` |
|
|
96
|
+
|
|
97
|
+
## Key guarantees
|
|
98
|
+
|
|
99
|
+
- **Closed vocabulary.** Component ids, prop names, prop kinds and enum
|
|
100
|
+
values are closed; anything outside the catalog is refused.
|
|
101
|
+
- **No platform props from data.** Web-only props (slots, events, `class`,
|
|
102
|
+
`className`, `style` and the chart `renderer`) are marked as web
|
|
103
|
+
extensions, left out of `SpecComponentProps`, and refused when a
|
|
104
|
+
blueprint sets them.
|
|
105
|
+
- **Fails closed.** The portable profile is refused until a capability is
|
|
106
|
+
audited portable, and a blueprint bound to another catalog hash is
|
|
107
|
+
refused when you pass `catalogHash`.
|
|
108
|
+
- **Deterministic.** Canonical JSON sorts keys at every depth, and every
|
|
109
|
+
generated file carries the spec version and digest with no timestamps.
|
|
110
|
+
- **Structure only.** This validator checks structure. URL policy, tree
|
|
111
|
+
limits and rendering live in the runtime validator of
|
|
112
|
+
[`@texuli/catalog`](https://www.npmjs.com/package/@texuli/catalog).
|
|
113
|
+
|
|
114
|
+
## Versioning
|
|
115
|
+
|
|
116
|
+
The spec has its own semantic version (`SPEC_VERSION`), separate from the
|
|
117
|
+
npm package version. The **spec digest** is the SHA-256 of the manifest
|
|
118
|
+
that pins every normative file (schemas, catalog and fixtures); any
|
|
119
|
+
normative change moves the digest and requires a new spec version.
|
|
120
|
+
|
|
121
|
+
| Change class | Examples | Before 1.0.0 | From 1.0.0 |
|
|
122
|
+
| --- | --- | --- | --- |
|
|
123
|
+
| patch | Descriptions, diagnostic wording, a fixture for behavior that already holds | patch | patch |
|
|
124
|
+
| minor | Optional fields, new catalog ids, new optional props, new diagnostic codes for data that was already refused | minor | minor |
|
|
125
|
+
| major | Removing or renaming ids, props or fields; changing a prop type; making something required | minor | major |
|
|
126
|
+
|
|
127
|
+
For TypeScript consumers, a patch leaves the generated types and
|
|
128
|
+
`SPEC_COMPONENT_IDS` unchanged; code that compiled against a minor release
|
|
129
|
+
keeps compiling, though new optional fields and ids can appear in data it
|
|
130
|
+
reads. Native (Kotlin) builds compile the generated models from the same
|
|
131
|
+
spec, and their decoders are closed: a blueprint that uses a field from a
|
|
132
|
+
newer spec fails to decode on an older native build with a `schema`
|
|
133
|
+
diagnostic instead of passing silently. A blueprint binds to an exact
|
|
134
|
+
catalog hash today; an envelope field for a compatible spec range, and
|
|
135
|
+
migrations between versions, are planned and not built.
|
|
136
|
+
|
|
137
|
+
The full rules (the digest, `versions.json` pins, regeneration and the
|
|
138
|
+
conformance harness) are in
|
|
139
|
+
[contract.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/contract.md).
|
|
140
|
+
|
|
141
|
+
## Documentation
|
|
142
|
+
|
|
143
|
+
- Website: [texuli.com](https://texuli.com), the
|
|
144
|
+
[agents overview](https://texuli.com/agents-page/) and
|
|
145
|
+
[web and native](https://texuli.com/web-and-native/)
|
|
146
|
+
- Schemas, catalog and fixtures:
|
|
147
|
+
[packages/spec](https://github.com/DailybotHQ/texuli/tree/main/packages/spec)
|
|
148
|
+
- The contract in depth (the JSON Schema dialect, projections, conformance
|
|
149
|
+
and versioning): [contract.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/contract.md)
|
|
150
|
+
- The Kotlin Multiplatform twin that consumes the generated models:
|
|
151
|
+
[native/README.md](https://github.com/DailybotHQ/texuli/blob/main/native/README.md)
|
|
152
|
+
- Architecture: [ARCHITECTURE.md](https://github.com/DailybotHQ/texuli/blob/main/docs/ARCHITECTURE.md)
|
|
153
|
+
|
|
154
|
+
## Related packages
|
|
155
|
+
|
|
156
|
+
- [`@texuli/catalog`](https://www.npmjs.com/package/@texuli/catalog): the agent catalog and the renderer with no HTML sinks.
|
|
157
|
+
- [`@texuli/player`](https://www.npmjs.com/package/@texuli/player): the Svelte paint runtime for validated blueprints.
|
|
158
|
+
- [`texuli`](https://www.npmjs.com/package/texuli): the styled Svelte 5 components blueprints are built from.
|
|
159
|
+
|
|
160
|
+
## Contributing
|
|
161
|
+
|
|
162
|
+
Source, issues and contribution guidelines live at
|
|
163
|
+
[github.com/DailybotHQ/texuli](https://github.com/DailybotHQ/texuli). Report
|
|
164
|
+
bugs at [github.com/DailybotHQ/texuli/issues](https://github.com/DailybotHQ/texuli/issues).
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT © Dailybot, Inc.
|
package/dist/catalog.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* projection from a built catalog. The projection is the bootstrap step of
|
|
4
4
|
* spec/Compose design §2.3; the parity check reruns it to catch drift.
|
|
5
5
|
*/
|
|
6
|
-
import type { SpecCatalog, SpecComponent, SpecProp } from '
|
|
6
|
+
import type { SpecCatalog, SpecComponent, SpecProp } from './generated/typescript/catalog-schema.js';
|
|
7
7
|
export type { SpecCatalog, SpecComponent, SpecProp };
|
|
8
8
|
export declare const CAPABILITIES: readonly ["portable-core", "web-only", "native-only", "unknown"];
|
|
9
9
|
export type Capability = SpecComponent['capability'];
|
package/dist/codegen/index.d.ts
CHANGED
|
@@ -9,5 +9,6 @@ export { prettyJson } from '../canonical.js';
|
|
|
9
9
|
export { type BuiltCatalogLike, projectCatalog } from '../catalog.js';
|
|
10
10
|
export { type ConformanceIssue, checkConformance } from '../conformance.js';
|
|
11
11
|
export { formatJson, LINE_WIDTH } from '../format.js';
|
|
12
|
+
export { checkVersionHistory, nextSpecVersion, type SpecChangeClass, type SpecVersionHistory, type SpecVersionPin, type VersionIssue, } from '../versioning.js';
|
|
12
13
|
export { KOTLIN_PACKAGE, kotlinString } from './kotlin.js';
|
|
13
14
|
export type { SchemaUnit, Stamp } from './shared.js';
|
package/dist/codegen/index.js
CHANGED
|
@@ -9,4 +9,5 @@ export { prettyJson } from "../canonical.js";
|
|
|
9
9
|
export { projectCatalog } from "../catalog.js";
|
|
10
10
|
export { checkConformance } from "../conformance.js";
|
|
11
11
|
export { formatJson, LINE_WIDTH } from "../format.js";
|
|
12
|
+
export { checkVersionHistory, nextSpecVersion, } from "../versioning.js";
|
|
12
13
|
export { KOTLIN_PACKAGE, kotlinString } from "./kotlin.js";
|
|
@@ -47,13 +47,55 @@ export interface BlueprintEditOp {
|
|
|
47
47
|
tree?: BlueprintNode;
|
|
48
48
|
}
|
|
49
49
|
export type BlueprintNodePath = readonly number[];
|
|
50
|
-
/** One catalog node: id, props and
|
|
50
|
+
/** One catalog node: id, props, children, and an optional named slot and condition. */
|
|
51
51
|
export interface BlueprintNode {
|
|
52
52
|
/** A catalog component id. */
|
|
53
53
|
id: string;
|
|
54
54
|
/** Props checked against the component's catalog entry. */
|
|
55
55
|
props?: Readonly<Record<string, unknown>>;
|
|
56
56
|
children?: readonly BlueprintNode[];
|
|
57
|
+
/**
|
|
58
|
+
* The parent's named slot this node fills (audit G04): a slot prop of the parent's catalog entry.
|
|
59
|
+
* Omitted or "children" is the default slot.
|
|
60
|
+
*/
|
|
61
|
+
slot?: string;
|
|
62
|
+
when?: BlueprintCondition;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Shows the node when true (audit G07): the closed Condition AST of the blocks formula grammar.
|
|
66
|
+
* False or unknown hides the node. Structural shape only; which operands each op needs, and the
|
|
67
|
+
* size limits, are checked by the runtime formula parser.
|
|
68
|
+
*/
|
|
69
|
+
export interface BlueprintCondition {
|
|
70
|
+
op: 'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'neq' | 'and' | 'or' | 'not';
|
|
71
|
+
left?: BlueprintNumberFormula;
|
|
72
|
+
right?: BlueprintNumberFormula;
|
|
73
|
+
/** The conditions of and / or. */
|
|
74
|
+
args?: readonly BlueprintCondition[];
|
|
75
|
+
value?: BlueprintCondition;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A number expression of the blocks formula grammar. Structural shape only; the runtime formula
|
|
79
|
+
* parser checks which operands each op needs.
|
|
80
|
+
*/
|
|
81
|
+
export interface BlueprintNumberFormula {
|
|
82
|
+
op: 'num' | 'var' | 'add' | 'sub' | 'mul' | 'div' | 'pow' | 'min' | 'max' | 'round' | 'clamp' | 'if';
|
|
83
|
+
/** A number for num; a number formula for round and clamp. */
|
|
84
|
+
value?: unknown;
|
|
85
|
+
/** The input a var reads. */
|
|
86
|
+
name?: string;
|
|
87
|
+
left?: BlueprintNumberFormula;
|
|
88
|
+
right?: BlueprintNumberFormula;
|
|
89
|
+
base?: BlueprintNumberFormula;
|
|
90
|
+
exponent?: BlueprintNumberFormula;
|
|
91
|
+
/** The operands of min / max. */
|
|
92
|
+
args?: readonly BlueprintNumberFormula[];
|
|
93
|
+
digits?: number;
|
|
94
|
+
min?: BlueprintNumberFormula;
|
|
95
|
+
max?: BlueprintNumberFormula;
|
|
96
|
+
test?: BlueprintCondition;
|
|
97
|
+
ifTrue?: BlueprintNumberFormula;
|
|
98
|
+
ifFalse?: BlueprintNumberFormula;
|
|
57
99
|
}
|
|
58
100
|
/**
|
|
59
101
|
* Envelope version 1 around one catalog tree (ADR-003, audit G13). Mirrors what
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated by @texuli/spec from schemas/blueprint.schema.json. Do not edit: run the spec generator.
|
|
2
|
-
// Spec 0.
|
|
2
|
+
// Spec 0.2.0, digest d9eccaba1826e7d87648a4a33787d873245592dda843607a10b2c68a77f1806f
|
|
3
3
|
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated by @texuli/spec from schemas/catalog.schema.json. Do not edit: run the spec generator.
|
|
2
|
-
// Spec 0.
|
|
2
|
+
// Spec 0.2.0, digest d9eccaba1826e7d87648a4a33787d873245592dda843607a10b2c68a77f1806f
|
|
3
3
|
export {};
|
|
@@ -4,7 +4,7 @@ export interface SpecDiagnostic {
|
|
|
4
4
|
message: string;
|
|
5
5
|
id?: string;
|
|
6
6
|
}
|
|
7
|
-
export type SpecDiagnosticCode = 'schema' | 'unknown-id' | 'unknown-prop' | 'prop-type' | 'enum-value' | 'missing-required' | 'extension-prop' | 'catalog-mismatch' | 'unsupported-profile';
|
|
7
|
+
export type SpecDiagnosticCode = 'schema' | 'unknown-id' | 'unknown-prop' | 'prop-type' | 'enum-value' | 'missing-required' | 'extension-prop' | 'catalog-mismatch' | 'unsupported-profile' | 'unknown-slot';
|
|
8
8
|
/** The stable result shape of validating a blueprint against the spec. */
|
|
9
9
|
export interface SpecValidation {
|
|
10
10
|
ok: boolean;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated by @texuli/spec from schemas/diagnostics.schema.json. Do not edit: run the spec generator.
|
|
2
|
-
// Spec 0.
|
|
2
|
+
// Spec 0.2.0, digest d9eccaba1826e7d87648a4a33787d873245592dda843607a10b2c68a77f1806f
|
|
3
3
|
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated by @texuli/spec from schemas/manifest.schema.json. Do not edit: run the spec generator.
|
|
2
|
-
// Spec 0.
|
|
2
|
+
// Spec 0.2.0, digest d9eccaba1826e7d87648a4a33787d873245592dda843607a10b2c68a77f1806f
|
|
3
3
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The built catalog this vocabulary was projected from. */
|
|
2
|
-
export declare const SPEC_CATALOG_HASH = "
|
|
2
|
+
export declare const SPEC_CATALOG_HASH = "b073e30b4c8b1a30b3ef3ccb372e552d2d88e584616b74591c6c32923b471376";
|
|
3
3
|
/** Every closed component id, sorted. */
|
|
4
|
-
export declare const SPEC_COMPONENT_IDS: readonly ["alert", "alert-dialog", "analytics-grid", "app-nav", "badge", "banner", "blocks-code", "blocks-data", "blocks-diagram", "blocks-embed", "blocks-interactive", "blocks-map", "blocks-media", "blocks-structure", "blocks-text", "breadcrumb", "button", "button-group", "callout", "card", "charts-area", "charts-bar", "charts-bubble", "charts-bullet", "charts-canvas-renderer", "charts-cartesian", "charts-combo", "charts-donut", "charts-funnel", "charts-gauge", "charts-geo-map", "charts-geo-point-map", "charts-heatmap", "charts-line", "charts-network", "charts-pie", "charts-progress-ring", "charts-sankey", "charts-scatter", "charts-sparkline", "charts-stacked-bar", "charts-sunburst", "charts-treemap", "checkbox", "chip", "code", "combobox", "command-palette", "content-layout", "context-menu", "context-menu-trigger", "description", "detail-panel", "dialog", "drawer", "dropdown-menu", "dropdown-menu-content", "dropdown-menu-item", "dropdown-menu-trigger", "empty-state", "error-state", "field", "field-error", "form", "hover-card", "hover-card-content", "icon-button", "input", "input-group", "kbd", "kpi", "label", "layout-auth-split", "layout-blog", "layout-card", "layout-chrome", "layout-dashboard-grid", "layout-deck", "layout-doc", "layout-docs", "layout-drawer", "layout-dual-sidebar", "layout-editorial", "layout-grid", "layout-host", "layout-landing", "layout-marketing", "layout-page", "layout-pricing", "layout-settings", "layout-sidebar", "layout-top-nav", "layout-wizard", "media-composition-canvas", "media-composition-svg", "media-image", "menubar", "menubar-trigger", "multi-select", "nav-icon", "navigation-menu", "pagination", "popover", "popover-content", "radio", "radio-group", "responsive-modal", "screen-outlet", "select", "select-content", "select-group", "select-item", "select-label", "select-portal", "select-separator", "select-trigger", "select-value", "select-viewport", "sheet", "sidebar", "split-view", "stat", "stepper", "switch", "table", "tag", "text-counter", "textarea", "timeline", "toaster", "tooltip"];
|
|
4
|
+
export declare const SPEC_COMPONENT_IDS: readonly ["alert", "alert-dialog", "analytics-grid", "app-nav", "badge", "banner", "blocks-code", "blocks-data", "blocks-diagram", "blocks-embed", "blocks-interactive", "blocks-map", "blocks-media", "blocks-structure", "blocks-text", "breadcrumb", "button", "button-group", "callout", "card", "charts-area", "charts-bar", "charts-bubble", "charts-bullet", "charts-canvas-renderer", "charts-cartesian", "charts-combo", "charts-donut", "charts-funnel", "charts-gauge", "charts-geo-map", "charts-geo-point-map", "charts-heatmap", "charts-line", "charts-network", "charts-pie", "charts-progress-ring", "charts-sankey", "charts-scatter", "charts-sparkline", "charts-stacked-bar", "charts-sunburst", "charts-treemap", "checkbox", "chip", "code", "combobox", "command-palette", "content-layout", "context-menu", "context-menu-trigger", "deck-slide", "description", "detail-panel", "dialog", "drawer", "dropdown-menu", "dropdown-menu-content", "dropdown-menu-item", "dropdown-menu-trigger", "empty-state", "error-state", "field", "field-error", "form", "hover-card", "hover-card-content", "icon-button", "inline-text", "input", "input-group", "kbd", "kpi", "label", "layout-auth-split", "layout-blog", "layout-card", "layout-chrome", "layout-dashboard-grid", "layout-deck", "layout-doc", "layout-docs", "layout-drawer", "layout-dual-sidebar", "layout-editorial", "layout-grid", "layout-host", "layout-landing", "layout-marketing", "layout-page", "layout-pricing", "layout-settings", "layout-sidebar", "layout-top-nav", "layout-wizard", "media-composition-canvas", "media-composition-svg", "media-image", "menubar", "menubar-trigger", "multi-select", "nav-icon", "navigation-menu", "pagination", "popover", "popover-content", "radio", "radio-group", "responsive-modal", "screen-outlet", "select", "select-content", "select-group", "select-item", "select-label", "select-portal", "select-separator", "select-trigger", "select-value", "select-viewport", "sheet", "sidebar", "split-view", "stat", "stepper", "switch", "table", "tag", "text-counter", "textarea", "timeline", "toaster", "tooltip"];
|
|
5
5
|
export type SpecComponentId = (typeof SPEC_COMPONENT_IDS)[number];
|
|
6
6
|
/** The props a blueprint may set on each component (platform extensions left out). */
|
|
7
7
|
export interface SpecComponentProps {
|
|
@@ -44,6 +44,7 @@ export interface SpecComponentProps {
|
|
|
44
44
|
variant?: 'default' | 'inset';
|
|
45
45
|
};
|
|
46
46
|
badge: {
|
|
47
|
+
label?: string;
|
|
47
48
|
variant?: 'default' | 'secondary' | 'outline' | 'success' | 'warning' | 'destructive' | 'info' | 'neutral';
|
|
48
49
|
};
|
|
49
50
|
banner: {
|
|
@@ -112,6 +113,7 @@ export interface SpecComponentProps {
|
|
|
112
113
|
disabled?: boolean;
|
|
113
114
|
href?: string;
|
|
114
115
|
hrefTrust?: 'developer' | 'untrusted';
|
|
116
|
+
label?: string;
|
|
115
117
|
loading?: boolean;
|
|
116
118
|
loadingLabel?: string;
|
|
117
119
|
size?: 'sm' | 'md' | 'lg' | 'icon';
|
|
@@ -147,6 +149,7 @@ export interface SpecComponentProps {
|
|
|
147
149
|
mode?: 'light' | 'dark';
|
|
148
150
|
series: readonly unknown[];
|
|
149
151
|
title: string;
|
|
152
|
+
visibleDomain?: string;
|
|
150
153
|
};
|
|
151
154
|
'charts-bar': {
|
|
152
155
|
annotations?: string;
|
|
@@ -156,6 +159,7 @@ export interface SpecComponentProps {
|
|
|
156
159
|
mode?: 'light' | 'dark';
|
|
157
160
|
series: readonly unknown[];
|
|
158
161
|
title: string;
|
|
162
|
+
visibleDomain?: string;
|
|
159
163
|
};
|
|
160
164
|
'charts-bubble': {
|
|
161
165
|
annotations?: string;
|
|
@@ -170,6 +174,7 @@ export interface SpecComponentProps {
|
|
|
170
174
|
};
|
|
171
175
|
'charts-bullet': {
|
|
172
176
|
description?: string;
|
|
177
|
+
format?: Readonly<Record<string, unknown>>;
|
|
173
178
|
label: string;
|
|
174
179
|
labels?: Readonly<Record<string, unknown>>;
|
|
175
180
|
max?: number;
|
|
@@ -192,6 +197,7 @@ export interface SpecComponentProps {
|
|
|
192
197
|
mode?: 'light' | 'dark';
|
|
193
198
|
series: readonly unknown[];
|
|
194
199
|
title: string;
|
|
200
|
+
visibleDomain?: string;
|
|
195
201
|
};
|
|
196
202
|
'charts-combo': {
|
|
197
203
|
annotations?: string;
|
|
@@ -201,6 +207,7 @@ export interface SpecComponentProps {
|
|
|
201
207
|
mode?: 'light' | 'dark';
|
|
202
208
|
series: readonly unknown[];
|
|
203
209
|
title: string;
|
|
210
|
+
visibleDomain?: string;
|
|
204
211
|
};
|
|
205
212
|
'charts-donut': {
|
|
206
213
|
data: readonly unknown[];
|
|
@@ -223,6 +230,7 @@ export interface SpecComponentProps {
|
|
|
223
230
|
'charts-gauge': {
|
|
224
231
|
bandLabels?: readonly unknown[];
|
|
225
232
|
description?: string;
|
|
233
|
+
format?: Readonly<Record<string, unknown>>;
|
|
226
234
|
label: string;
|
|
227
235
|
labels?: Readonly<Record<string, unknown>>;
|
|
228
236
|
max?: number;
|
|
@@ -268,6 +276,7 @@ export interface SpecComponentProps {
|
|
|
268
276
|
mode?: 'light' | 'dark';
|
|
269
277
|
series: readonly unknown[];
|
|
270
278
|
title: string;
|
|
279
|
+
visibleDomain?: string;
|
|
271
280
|
};
|
|
272
281
|
'charts-network': {
|
|
273
282
|
description?: string;
|
|
@@ -289,6 +298,7 @@ export interface SpecComponentProps {
|
|
|
289
298
|
};
|
|
290
299
|
'charts-progress-ring': {
|
|
291
300
|
description?: string;
|
|
301
|
+
format?: Readonly<Record<string, unknown>>;
|
|
292
302
|
label: string;
|
|
293
303
|
labels?: Readonly<Record<string, unknown>>;
|
|
294
304
|
max?: number;
|
|
@@ -318,6 +328,7 @@ export interface SpecComponentProps {
|
|
|
318
328
|
};
|
|
319
329
|
'charts-sparkline': {
|
|
320
330
|
description?: string;
|
|
331
|
+
format?: Readonly<Record<string, unknown>>;
|
|
321
332
|
height?: number;
|
|
322
333
|
label: string;
|
|
323
334
|
labels?: Readonly<Record<string, unknown>>;
|
|
@@ -334,6 +345,7 @@ export interface SpecComponentProps {
|
|
|
334
345
|
mode?: 'light' | 'dark';
|
|
335
346
|
series: readonly unknown[];
|
|
336
347
|
title: string;
|
|
348
|
+
visibleDomain?: string;
|
|
337
349
|
};
|
|
338
350
|
'charts-sunburst': {
|
|
339
351
|
description: string;
|
|
@@ -422,6 +434,13 @@ export interface SpecComponentProps {
|
|
|
422
434
|
'context-menu-trigger': {
|
|
423
435
|
disabled?: boolean;
|
|
424
436
|
};
|
|
437
|
+
'deck-slide': {
|
|
438
|
+
master?: 'title' | 'section' | 'content' | 'two-column' | 'media-left' | 'media-right' | 'quote' | 'closing';
|
|
439
|
+
notes?: string;
|
|
440
|
+
slideId: string;
|
|
441
|
+
title?: string;
|
|
442
|
+
transition?: 'none' | 'fade' | 'fade-up';
|
|
443
|
+
};
|
|
425
444
|
description: Record<string, never>;
|
|
426
445
|
'detail-panel': {
|
|
427
446
|
closeOnEscape?: boolean;
|
|
@@ -508,6 +527,9 @@ export interface SpecComponentProps {
|
|
|
508
527
|
loading?: boolean;
|
|
509
528
|
variant?: 'default' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link';
|
|
510
529
|
};
|
|
530
|
+
'inline-text': {
|
|
531
|
+
text: string;
|
|
532
|
+
};
|
|
511
533
|
input: {
|
|
512
534
|
clearLabel?: string;
|
|
513
535
|
clearable?: boolean;
|
|
@@ -525,7 +547,9 @@ export interface SpecComponentProps {
|
|
|
525
547
|
readonly?: boolean;
|
|
526
548
|
value?: string;
|
|
527
549
|
};
|
|
528
|
-
kbd:
|
|
550
|
+
kbd: {
|
|
551
|
+
label?: string;
|
|
552
|
+
};
|
|
529
553
|
kpi: {
|
|
530
554
|
comparisonLabel?: string;
|
|
531
555
|
currency?: string;
|
|
@@ -576,6 +600,11 @@ export interface SpecComponentProps {
|
|
|
576
600
|
storageKey?: string;
|
|
577
601
|
};
|
|
578
602
|
'layout-deck': {
|
|
603
|
+
aspect?: '16:9' | '4:3' | '1:1';
|
|
604
|
+
description?: string;
|
|
605
|
+
locale?: string;
|
|
606
|
+
masters?: readonly unknown[];
|
|
607
|
+
settings?: Readonly<Record<string, unknown>>;
|
|
579
608
|
slides?: readonly unknown[];
|
|
580
609
|
title?: string;
|
|
581
610
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by @texuli/spec from catalog/components.json. Do not edit: run the spec generator.
|
|
2
|
-
// Spec 0.
|
|
2
|
+
// Spec 0.2.0, digest d9eccaba1826e7d87648a4a33787d873245592dda843607a10b2c68a77f1806f
|
|
3
3
|
/** The built catalog this vocabulary was projected from. */
|
|
4
|
-
export const SPEC_CATALOG_HASH = '
|
|
4
|
+
export const SPEC_CATALOG_HASH = 'b073e30b4c8b1a30b3ef3ccb372e552d2d88e584616b74591c6c32923b471376';
|
|
5
5
|
/** Every closed component id, sorted. */
|
|
6
6
|
export const SPEC_COMPONENT_IDS = [
|
|
7
7
|
'alert',
|
|
@@ -55,6 +55,7 @@ export const SPEC_COMPONENT_IDS = [
|
|
|
55
55
|
'content-layout',
|
|
56
56
|
'context-menu',
|
|
57
57
|
'context-menu-trigger',
|
|
58
|
+
'deck-slide',
|
|
58
59
|
'description',
|
|
59
60
|
'detail-panel',
|
|
60
61
|
'dialog',
|
|
@@ -71,6 +72,7 @@ export const SPEC_COMPONENT_IDS = [
|
|
|
71
72
|
'hover-card',
|
|
72
73
|
'hover-card-content',
|
|
73
74
|
'icon-button',
|
|
75
|
+
'inline-text',
|
|
74
76
|
'input',
|
|
75
77
|
'input-group',
|
|
76
78
|
'kbd',
|
package/dist/index.d.ts
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* hashing. Build-time generation and the conformance harness live in
|
|
6
6
|
* `@texuli/spec/codegen`. Framework-free: no DOM, no Svelte and no Node APIs.
|
|
7
7
|
*/
|
|
8
|
-
export type * from '
|
|
9
|
-
export type * from '
|
|
10
|
-
export type * from '
|
|
11
|
-
export type * from '
|
|
12
|
-
export { SPEC_CATALOG_HASH, SPEC_COMPONENT_IDS, type SpecComponentId, type SpecComponentProps, } from '
|
|
8
|
+
export type * from './generated/typescript/blueprint.js';
|
|
9
|
+
export type * from './generated/typescript/catalog-schema.js';
|
|
10
|
+
export type * from './generated/typescript/diagnostics.js';
|
|
11
|
+
export type * from './generated/typescript/manifest.js';
|
|
12
|
+
export { SPEC_CATALOG_HASH, SPEC_COMPONENT_IDS, type SpecComponentId, type SpecComponentProps, } from './generated/typescript/vocabulary.js';
|
|
13
13
|
export { canonicalJson, sha256 } from './canonical.js';
|
|
14
14
|
export { CAPABILITIES, type Capability, type SpecPropKind, WEB_ONLY_KINDS, WEB_ONLY_PROP_NAMES, } from './catalog.js';
|
|
15
15
|
export { checkSchemaDocument, DIALECT_KEYWORDS, DIALECT_TYPES, DIALECT_URI, type DialectType, type Schema, type SchemaIssue, validateInstance, } from './dialect.js';
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* hashing. Build-time generation and the conformance harness live in
|
|
6
6
|
* `@texuli/spec/codegen`. Framework-free: no DOM, no Svelte and no Node APIs.
|
|
7
7
|
*/
|
|
8
|
-
export { SPEC_CATALOG_HASH, SPEC_COMPONENT_IDS, } from "
|
|
8
|
+
export { SPEC_CATALOG_HASH, SPEC_COMPONENT_IDS, } from "./generated/typescript/vocabulary.js";
|
|
9
9
|
export { canonicalJson, sha256 } from "./canonical.js";
|
|
10
10
|
export { CAPABILITIES, WEB_ONLY_KINDS, WEB_ONLY_PROP_NAMES, } from "./catalog.js";
|
|
11
11
|
export { checkSchemaDocument, DIALECT_KEYWORDS, DIALECT_TYPES, DIALECT_URI, validateInstance, } from "./dialect.js";
|
package/dist/validate.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SpecDiagnostic, SpecDiagnosticCode, SpecValidation } from '
|
|
1
|
+
import type { SpecDiagnostic, SpecDiagnosticCode, SpecValidation } from './generated/typescript/diagnostics.js';
|
|
2
2
|
import type { SpecCatalog } from './catalog.js';
|
|
3
3
|
import { type Schema } from './dialect.js';
|
|
4
4
|
export type { SpecDiagnostic, SpecDiagnosticCode, SpecValidation };
|
|
@@ -10,7 +10,9 @@ export interface SpecDocuments {
|
|
|
10
10
|
* Validate a blueprint envelope against the spec alone: the envelope schema,
|
|
11
11
|
* then every node against the catalog vocabulary (closed ids, closed prop
|
|
12
12
|
* names, prop kinds and enum values, required props, no platform-extension
|
|
13
|
-
* props from data
|
|
13
|
+
* props from data, a `slot` that names a slot prop of the parent) and the
|
|
14
|
+
* declared profile. A `when` condition is checked for shape only; the
|
|
15
|
+
* runtime formula parser checks its operands. With `catalogHash`, an envelope
|
|
14
16
|
* bound to another catalog is refused. This is the shared, target-neutral
|
|
15
17
|
* contract; URL policy, limits, grammar and rendering stay in the runtime
|
|
16
18
|
* validators (`@texuli/catalog/validate`).
|
package/dist/validate.js
CHANGED
|
@@ -4,7 +4,9 @@ const MAX_TREE_DEPTH = 64;
|
|
|
4
4
|
* Validate a blueprint envelope against the spec alone: the envelope schema,
|
|
5
5
|
* then every node against the catalog vocabulary (closed ids, closed prop
|
|
6
6
|
* names, prop kinds and enum values, required props, no platform-extension
|
|
7
|
-
* props from data
|
|
7
|
+
* props from data, a `slot` that names a slot prop of the parent) and the
|
|
8
|
+
* declared profile. A `when` condition is checked for shape only; the
|
|
9
|
+
* runtime formula parser checks its operands. With `catalogHash`, an envelope
|
|
8
10
|
* bound to another catalog is refused. This is the shared, target-neutral
|
|
9
11
|
* contract; URL policy, limits, grammar and rendering stay in the runtime
|
|
10
12
|
* validators (`@texuli/catalog/validate`).
|
|
@@ -30,9 +32,10 @@ export function validateBlueprint(input, documents, options = {}) {
|
|
|
30
32
|
export function validateTree(tree, catalog, options = {}) {
|
|
31
33
|
const byId = new Map(catalog.components.map((c) => [c.id, c]));
|
|
32
34
|
const out = [];
|
|
33
|
-
const visit = (node, path, depth) => {
|
|
35
|
+
const visit = (node, path, depth, parent) => {
|
|
34
36
|
if (!isRecord(node) || typeof node.id !== 'string' || depth > MAX_TREE_DEPTH)
|
|
35
37
|
return;
|
|
38
|
+
checkSlot(node.slot, parent, node.id, path, out);
|
|
36
39
|
const component = byId.get(node.id);
|
|
37
40
|
if (!component) {
|
|
38
41
|
out.push({
|
|
@@ -55,13 +58,35 @@ export function validateTree(tree, catalog, options = {}) {
|
|
|
55
58
|
}
|
|
56
59
|
if (Array.isArray(node.children)) {
|
|
57
60
|
node.children.forEach((child, index) => {
|
|
58
|
-
visit(child, `${path}.children[${index}]`, depth + 1);
|
|
61
|
+
visit(child, `${path}.children[${index}]`, depth + 1, component);
|
|
59
62
|
});
|
|
60
63
|
}
|
|
61
64
|
};
|
|
62
|
-
visit(tree, options.path ?? '$', 0);
|
|
65
|
+
visit(tree, options.path ?? '$', 0, null);
|
|
63
66
|
return out;
|
|
64
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* A node's `slot` (audit G04) must name a slot prop of its parent; omitted
|
|
70
|
+
* or `children` is the default slot. A root fills no slot. An unknown
|
|
71
|
+
* parent was already reported, so its children's slots are not judged.
|
|
72
|
+
*/
|
|
73
|
+
function checkSlot(slot, parent, id, path, out) {
|
|
74
|
+
if (slot === undefined || slot === 'children' || parent === undefined)
|
|
75
|
+
return;
|
|
76
|
+
const named = parent !== null &&
|
|
77
|
+
typeof slot === 'string' &&
|
|
78
|
+
parent.props.some((prop) => prop.kind === 'slot' && prop.name === slot);
|
|
79
|
+
if (named)
|
|
80
|
+
return;
|
|
81
|
+
out.push({
|
|
82
|
+
code: 'unknown-slot',
|
|
83
|
+
path: `${path}.slot`,
|
|
84
|
+
id,
|
|
85
|
+
message: parent === null
|
|
86
|
+
? 'A root node fills no slot.'
|
|
87
|
+
: `"${String(slot)}" is not a slot of "${parent.id}".`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
65
90
|
function checkProps(props, component, path, out) {
|
|
66
91
|
const given = isRecord(props) ? props : {};
|
|
67
92
|
const id = component.id;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The semantic version of the spec documents in this package. */
|
|
2
|
-
export declare const SPEC_VERSION = "0.
|
|
2
|
+
export declare const SPEC_VERSION = "0.2.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The semantic version of the spec documents in this package. */
|
|
2
|
-
export const SPEC_VERSION = '0.
|
|
2
|
+
export const SPEC_VERSION = '0.2.0';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The spec's version history (`versions.json`): every released spec version
|
|
3
|
+
* pinned to the digest of its normative files, so the digest and the semantic
|
|
4
|
+
* version cannot drift apart. A normative change (schema, catalog projection,
|
|
5
|
+
* capability claim or fixture) changes the digest; it needs a new version
|
|
6
|
+
* and a new pin, with the change class the policy assigns to it.
|
|
7
|
+
*/
|
|
8
|
+
/** What a release changes, by the policy in the package README. */
|
|
9
|
+
export type SpecChangeClass = 'patch' | 'minor' | 'major';
|
|
10
|
+
export interface SpecVersionPin {
|
|
11
|
+
specVersion: string;
|
|
12
|
+
/** The spec digest of that release, or `null` for a release pinned retroactively without one. */
|
|
13
|
+
specDigest: string | null;
|
|
14
|
+
change: SpecChangeClass | 'initial';
|
|
15
|
+
summary: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SpecVersionHistory {
|
|
18
|
+
releases: readonly SpecVersionPin[];
|
|
19
|
+
}
|
|
20
|
+
export interface VersionIssue {
|
|
21
|
+
version: string;
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The version that must follow `previous` for a change of `change` class.
|
|
26
|
+
* Before 1.0.0 any change to accepted data (minor or major class) bumps the
|
|
27
|
+
* minor number and a patch-class change bumps the patch; from 1.0.0 each
|
|
28
|
+
* class bumps its own number.
|
|
29
|
+
*/
|
|
30
|
+
export declare function nextSpecVersion(previous: string, change: SpecChangeClass): string;
|
|
31
|
+
/**
|
|
32
|
+
* Check the history and that the newest release is the spec as built now:
|
|
33
|
+
* the same version and the same digest. Returns every problem; empty means
|
|
34
|
+
* the version and the digest agree.
|
|
35
|
+
*/
|
|
36
|
+
export declare function checkVersionHistory(history: SpecVersionHistory, current: {
|
|
37
|
+
specVersion: string;
|
|
38
|
+
specDigest: string;
|
|
39
|
+
}): VersionIssue[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The spec's version history (`versions.json`): every released spec version
|
|
3
|
+
* pinned to the digest of its normative files, so the digest and the semantic
|
|
4
|
+
* version cannot drift apart. A normative change (schema, catalog projection,
|
|
5
|
+
* capability claim or fixture) changes the digest; it needs a new version
|
|
6
|
+
* and a new pin, with the change class the policy assigns to it.
|
|
7
|
+
*/
|
|
8
|
+
const SEMVER = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
9
|
+
const DIGEST = /^[0-9a-f]{64}$/;
|
|
10
|
+
function parts(version) {
|
|
11
|
+
const match = SEMVER.exec(version);
|
|
12
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The version that must follow `previous` for a change of `change` class.
|
|
16
|
+
* Before 1.0.0 any change to accepted data (minor or major class) bumps the
|
|
17
|
+
* minor number and a patch-class change bumps the patch; from 1.0.0 each
|
|
18
|
+
* class bumps its own number.
|
|
19
|
+
*/
|
|
20
|
+
export function nextSpecVersion(previous, change) {
|
|
21
|
+
const p = parts(previous);
|
|
22
|
+
if (!p)
|
|
23
|
+
throw new Error(`${previous}: not a semantic version`);
|
|
24
|
+
const [major, minor, patch] = p;
|
|
25
|
+
if (change === 'patch')
|
|
26
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
27
|
+
if (major === 0 || change === 'minor')
|
|
28
|
+
return `${major}.${minor + 1}.0`;
|
|
29
|
+
return `${major + 1}.0.0`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check the history and that the newest release is the spec as built now:
|
|
33
|
+
* the same version and the same digest. Returns every problem; empty means
|
|
34
|
+
* the version and the digest agree.
|
|
35
|
+
*/
|
|
36
|
+
export function checkVersionHistory(history, current) {
|
|
37
|
+
const issues = [];
|
|
38
|
+
const releases = history.releases;
|
|
39
|
+
if (releases.length === 0) {
|
|
40
|
+
return [{ version: current.specVersion, message: 'versions.json lists no release.' }];
|
|
41
|
+
}
|
|
42
|
+
releases.forEach((release, index) => {
|
|
43
|
+
const { specVersion: version } = release;
|
|
44
|
+
if (!parts(version))
|
|
45
|
+
issues.push({ version, message: 'not a semantic version' });
|
|
46
|
+
if (release.specDigest !== null && !DIGEST.test(release.specDigest)) {
|
|
47
|
+
issues.push({ version, message: 'the digest is not a SHA-256 hex string' });
|
|
48
|
+
}
|
|
49
|
+
if (index === 0) {
|
|
50
|
+
if (release.change !== 'initial') {
|
|
51
|
+
issues.push({ version, message: 'the first release must have change "initial"' });
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const previous = releases[index - 1];
|
|
56
|
+
if (release.change === 'initial') {
|
|
57
|
+
issues.push({ version, message: 'only the first release is "initial"' });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (release.specDigest === null) {
|
|
61
|
+
issues.push({ version, message: 'only the first release may lack a digest' });
|
|
62
|
+
}
|
|
63
|
+
if (parts(previous.specVersion)) {
|
|
64
|
+
const expected = nextSpecVersion(previous.specVersion, release.change);
|
|
65
|
+
if (version !== expected) {
|
|
66
|
+
issues.push({
|
|
67
|
+
version,
|
|
68
|
+
message: `a ${release.change} change after ${previous.specVersion} is ${expected}`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const newest = releases[releases.length - 1];
|
|
74
|
+
if (newest.specVersion !== current.specVersion) {
|
|
75
|
+
issues.push({
|
|
76
|
+
version: current.specVersion,
|
|
77
|
+
message: `the newest release in versions.json is ${newest.specVersion}, the spec is ${current.specVersion}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else if (newest.specDigest !== current.specDigest) {
|
|
81
|
+
issues.push({
|
|
82
|
+
version: current.specVersion,
|
|
83
|
+
message: 'the normative files changed without a new version: classify the change, bump SPEC_VERSION, regenerate, and append a release pinning the new digest',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return issues;
|
|
87
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@texuli/spec",
|
|
3
|
-
"version": "0.1.0-next.
|
|
3
|
+
"version": "0.1.0-next.3",
|
|
4
4
|
"description": "The schema-first blueprint contract: JSON schemas, the catalog vocabulary, deterministic TypeScript and Kotlin projections, and the conformance harness.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"svelte",
|
|
7
|
+
"svelte-5",
|
|
8
|
+
"components",
|
|
9
|
+
"design-system",
|
|
10
|
+
"texuli"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/DailybotHQ/texuli#readme",
|
|
13
|
+
"bugs": "https://github.com/DailybotHQ/texuli/issues",
|
|
5
14
|
"license": "MIT",
|
|
15
|
+
"author": "Dailybot, Inc.",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/DailybotHQ/texuli.git",
|
|
19
|
+
"directory": "packages/spec"
|
|
20
|
+
},
|
|
6
21
|
"type": "module",
|
|
7
22
|
"sideEffects": [
|
|
8
23
|
"**/*.css"
|
|
@@ -18,11 +33,6 @@
|
|
|
18
33
|
}
|
|
19
34
|
},
|
|
20
35
|
"files": [
|
|
21
|
-
"dist"
|
|
22
|
-
|
|
23
|
-
],
|
|
24
|
-
"publishConfig": {
|
|
25
|
-
"access": "public",
|
|
26
|
-
"tag": "next"
|
|
27
|
-
}
|
|
36
|
+
"dist"
|
|
37
|
+
]
|
|
28
38
|
}
|
|
File without changes
|
|
File without changes
|