@yahoo/uds-create-config 5.42.3 → 5.43.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/dist/configs/build-options.d.ts +2 -2
- package/dist/configs/build-options.js +11 -11
- package/dist/configs/react-native-system.d.ts +8 -1
- package/dist/configs/system.js +4 -0
- package/dist/entities/native/NativeSettings.d.ts +8 -1
- package/dist/entities/system/LinkedSystem.d.ts +1 -1
- package/dist/entities/system/LinkedSystem.js +1 -1
- package/dist/entities/system/Settings.d.ts +20 -3
- package/dist/entities/system/Settings.js +46 -4
- package/dist/entities/system/shipped-modules.d.ts +1 -1
- package/dist/framework/Config.d.ts +1 -1
- package/dist/framework/Config.js +3 -2
- package/dist/framework/{registry-dir.d.ts → build-dirs.d.ts} +28 -15
- package/dist/framework/build-dirs.js +95 -0
- package/dist/framework/defineEntity.d.ts +2 -1
- package/dist/framework/defineEntity.js +2 -1
- package/dist/framework/registry.d.ts +8 -0
- package/dist/framework/registry.js +3 -2
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/migrations/20261020182408_linked_dir.d.ts +22 -0
- package/dist/migrations/20261020182408_linked_dir.js +99 -0
- package/dist/migrations/20261020183330_lint_settings.d.ts +11 -0
- package/dist/migrations/20261020183330_lint_settings.js +10 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/dist/framework/registry-dir.js +0 -69
|
@@ -3,8 +3,8 @@ import { z } from "zod";
|
|
|
3
3
|
//#region src/configs/build-options.d.ts
|
|
4
4
|
/** The section as a config that set nothing reads it, spelled once. */
|
|
5
5
|
declare const BUILD_DEFAULTS: {
|
|
6
|
-
readonly outDir: ".uds"; /**
|
|
7
|
-
readonly
|
|
6
|
+
readonly outDir: ".uds"; /** Linked systems' sources, beside the conventional components root — see `resolveLinkedDir`. */
|
|
7
|
+
readonly linkedDir: "src/uds/linked"; /** Where a system that declares no `inputDir` keeps its sources, for what sits beside them. */
|
|
8
8
|
readonly inputRoot: "src/uds"; /** The prefix a system's sources are imported by: `@/uds/components/card`. */
|
|
9
9
|
readonly sourceAlias: "@/uds"; /** The prefix its build output is imported by: `#uds/components/card`. */
|
|
10
10
|
readonly outputAlias: "#uds"; /** The React major the app runs, which decides how an emitted component takes its `ref`. */
|
|
@@ -27,8 +27,8 @@ const insideProject = (field) => z.string().min(1).refine((value) => !value.star
|
|
|
27
27
|
/** The section as a config that set nothing reads it, spelled once. */
|
|
28
28
|
const BUILD_DEFAULTS = {
|
|
29
29
|
outDir: ".uds",
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/** Linked systems' sources, beside the conventional components root — see `resolveLinkedDir`. */
|
|
31
|
+
linkedDir: "src/uds/linked",
|
|
32
32
|
/** Where a system that declares no `inputDir` keeps its sources, for what sits beside them. */
|
|
33
33
|
inputRoot: "src/uds",
|
|
34
34
|
/** The prefix a system's sources are imported by: `@/uds/components/card`. */
|
|
@@ -66,7 +66,7 @@ const buildFields = z.strictObject({
|
|
|
66
66
|
* Every source file under `inputDir` ships by default, with what it imports. A module no component
|
|
67
67
|
* imports, a toast queue's `createToast` or a motion wrapper like yos-v3's `SpringMotionConfig`,
|
|
68
68
|
* is emitted at its own path under `components/`, so this system's apps import it as
|
|
69
|
-
* `#uds/components/<file>` and a system that links this one as `@/uds/
|
|
69
|
+
* `#uds/components/<file>` and a system that links this one as `@/uds/linked/<slug>/<file>`.
|
|
70
70
|
* Tests, stories and type declarations never ship, and neither does a file only the config or a
|
|
71
71
|
* script imports. List a file here to keep it out as well, such as a helper whose npm
|
|
72
72
|
* dependencies a linking team shouldn't have to install. An excluded file nothing else imports is
|
|
@@ -84,11 +84,11 @@ const buildFields = z.strictObject({
|
|
|
84
84
|
* sources even though nothing imports it. Unlike a shipped module it is never emitted, even under
|
|
85
85
|
* {@link inputDir}: a script runs as written, against the authoring package, so it may read the
|
|
86
86
|
* config (`Token.get`, `Component.list`) where a shipped module may only read token values. A
|
|
87
|
-
* linking team runs it from the installed tree, `src/uds/
|
|
87
|
+
* linking team runs it from the installed tree, `src/uds/linked/<slug>/<path>`.
|
|
88
88
|
*/
|
|
89
89
|
scripts: z.array(z.string().min(1).refine((value) => !value.startsWith("/") && !/^[A-Za-z]:/.test(value) && !value.replace(/\\/g, "/").split("/").includes(".."), { message: "A path in `scripts` is relative to the config, inside the project." })).meta({
|
|
90
90
|
title: "Scripts",
|
|
91
|
-
description: "Files a team that links this system runs from its installed sources, such as a migration script that reads their old config and writes their new one, as paths relative to the config: `src/migrate/convert.ts`. Each is sealed, pushed and installed with the system's sources, at `src/uds/
|
|
91
|
+
description: "Files a team that links this system runs from its installed sources, such as a migration script that reads their old config and writes their new one, as paths relative to the config: `src/migrate/convert.ts`. Each is sealed, pushed and installed with the system's sources, at `src/uds/linked/<slug>/<path>` in a linking project, but never emitted, so it runs as written against the authoring package and may read the config. A file apps import at runtime ships without being listed."
|
|
92
92
|
}).optional(),
|
|
93
93
|
/** Where `uds build` writes. A CLI `--output-dir` still wins, because a one-off build into a
|
|
94
94
|
* scratch directory is not a property of the system. */
|
|
@@ -100,7 +100,7 @@ const buildFields = z.strictObject({
|
|
|
100
100
|
/** The prefix sources are imported by — see `resolveSourceAlias`. */
|
|
101
101
|
sourceAlias: z.string().min(1).refine((value) => !value.endsWith("/") && !value.includes("*"), { message: "`sourceAlias` is a prefix, written without a trailing `/` or `*`." }).meta({
|
|
102
102
|
title: "Source alias",
|
|
103
|
-
description: `The prefix this system's sources are imported by, for their types — \`${BUILD_DEFAULTS.sourceAlias}/components/card\`, and a linked system's as \`${BUILD_DEFAULTS.sourceAlias}/
|
|
103
|
+
description: `The prefix this system's sources are imported by, for their types — \`${BUILD_DEFAULTS.sourceAlias}/components/card\`, and a linked system's as \`${BUILD_DEFAULTS.sourceAlias}/linked/<slug>/…\`. \`uds install\` and \`uds build\` write the tsconfig paths it needs, and the loader rewrites it to the output alias at build time. Unset, \`${BUILD_DEFAULTS.sourceAlias}\`.`,
|
|
104
104
|
default: BUILD_DEFAULTS.sourceAlias
|
|
105
105
|
}).optional(),
|
|
106
106
|
/** The prefix build output is imported by — see `resolveOutputAlias`. */
|
|
@@ -109,11 +109,11 @@ const buildFields = z.strictObject({
|
|
|
109
109
|
description: `The prefix this system's build output is imported by — \`${BUILD_DEFAULTS.outputAlias}/components/card\` — which the tsconfig maps onto \`outDir\`. Unset, \`${BUILD_DEFAULTS.outputAlias}\`.`,
|
|
110
110
|
default: BUILD_DEFAULTS.outputAlias
|
|
111
111
|
}).optional(),
|
|
112
|
-
/** Where `uds
|
|
113
|
-
|
|
114
|
-
title: "
|
|
115
|
-
description: `Where \`uds install\` places the sources of linked systems, relative to the config. Unset, \`
|
|
116
|
-
default: BUILD_DEFAULTS.
|
|
112
|
+
/** Where `uds install` materializes linked-system sources — see `resolveLinkedDir`. */
|
|
113
|
+
linkedDir: z.string().min(1).meta({
|
|
114
|
+
title: "Linked directory",
|
|
115
|
+
description: `Where \`uds install\` places the sources of linked systems, relative to the config. Unset, \`linked/\` beside the system's components: \`${BUILD_DEFAULTS.linkedDir}\` unless \`inputDir\` names another root.`,
|
|
116
|
+
default: BUILD_DEFAULTS.linkedDir
|
|
117
117
|
}).optional(),
|
|
118
118
|
/**
|
|
119
119
|
* The React major the app this repo builds for runs — see `appBuildReact` in the CLI.
|
|
@@ -38,7 +38,7 @@ declare const ReactNativeSystemDefinition: ConfigClass<{
|
|
|
38
38
|
outDir: _$zod.ZodOptional<_$zod.ZodString>;
|
|
39
39
|
sourceAlias: _$zod.ZodOptional<_$zod.ZodString>;
|
|
40
40
|
outputAlias: _$zod.ZodOptional<_$zod.ZodString>;
|
|
41
|
-
|
|
41
|
+
linkedDir: _$zod.ZodOptional<_$zod.ZodString>;
|
|
42
42
|
react: _$zod.ZodOptional<_$zod.ZodUnion<readonly [_$zod.ZodLiteral<18>, _$zod.ZodLiteral<19>]>>;
|
|
43
43
|
}, _$zod_v4_core0.$strict>>;
|
|
44
44
|
css: _$zod.ZodOptional<_$zod.ZodObject<{
|
|
@@ -57,6 +57,13 @@ declare const ReactNativeSystemDefinition: ConfigClass<{
|
|
|
57
57
|
pagesDir: _$zod.ZodOptional<_$zod.ZodString>;
|
|
58
58
|
port: _$zod.ZodOptional<_$zod.ZodNumber>;
|
|
59
59
|
}, _$zod_v4_core0.$strict>>;
|
|
60
|
+
lint: _$zod.ZodOptional<_$zod.ZodObject<{
|
|
61
|
+
rules: _$zod.ZodOptional<_$zod.ZodRecord<_$zod.ZodString, _$zod.ZodEnum<{
|
|
62
|
+
error: "error";
|
|
63
|
+
off: "off";
|
|
64
|
+
warn: "warn";
|
|
65
|
+
}>>>;
|
|
66
|
+
}, _$zod_v4_core0.$strict>>;
|
|
60
67
|
}, "css" | "playground" | "canvas"> & {
|
|
61
68
|
canvas: _$zod.ZodOptional<_$zod.ZodObject<{
|
|
62
69
|
packageAliases: _$zod.ZodOptional<_$zod.ZodRecord<_$zod.ZodString, _$zod.ZodString>>;
|
package/dist/configs/system.js
CHANGED
|
@@ -24,6 +24,8 @@ import { motionCompatibilityMigration } from "../migrations/20261016164316_motio
|
|
|
24
24
|
import { nativeStyleArraysAndAutoMigration } from "../migrations/20261016201727_native_style_arrays_and_auto.js";
|
|
25
25
|
import { buildExcludeMigration } from "../migrations/20261019165323_build_exclude.js";
|
|
26
26
|
import { nativeLinearGradientsMigration } from "../migrations/20261020173914_native_linear_gradients.js";
|
|
27
|
+
import { linkedDirMigration } from "../migrations/20261020182408_linked_dir.js";
|
|
28
|
+
import { lintSettingsMigration } from "../migrations/20261020183330_lint_settings.js";
|
|
27
29
|
import { CanvasRole } from "../entities/system/CanvasRole.js";
|
|
28
30
|
import { GlobalStyle } from "../entities/system/GlobalStyle.js";
|
|
29
31
|
import { Guidance } from "../entities/system/Guidance.js";
|
|
@@ -179,5 +181,7 @@ if (!registeredSchemaMigrations().has(20261016164316)) registerSchemaMigrations(
|
|
|
179
181
|
if (!registeredSchemaMigrations().has(20261016201727)) registerSchemaMigrations(nativeStyleArraysAndAutoMigration);
|
|
180
182
|
if (!registeredSchemaMigrations().has(20261019165323)) registerSchemaMigrations(buildExcludeMigration);
|
|
181
183
|
if (!registeredSchemaMigrations().has(20261020173914)) registerSchemaMigrations(nativeLinearGradientsMigration);
|
|
184
|
+
if (!registeredSchemaMigrations().has(20261020182408)) registerSchemaMigrations(linkedDirMigration);
|
|
185
|
+
if (!registeredSchemaMigrations().has(20261020183330)) registerSchemaMigrations(lintSettingsMigration);
|
|
182
186
|
//#endregion
|
|
183
187
|
export { System };
|
|
@@ -29,7 +29,7 @@ declare const NativeSettings: EntityClass<z.ZodObject<Omit<{
|
|
|
29
29
|
outDir: z.ZodOptional<z.ZodString>;
|
|
30
30
|
sourceAlias: z.ZodOptional<z.ZodString>;
|
|
31
31
|
outputAlias: z.ZodOptional<z.ZodString>;
|
|
32
|
-
|
|
32
|
+
linkedDir: z.ZodOptional<z.ZodString>;
|
|
33
33
|
react: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<18>, z.ZodLiteral<19>]>>;
|
|
34
34
|
}, z.core.$strict>>;
|
|
35
35
|
css: z.ZodOptional<z.ZodObject<{
|
|
@@ -48,6 +48,13 @@ declare const NativeSettings: EntityClass<z.ZodObject<Omit<{
|
|
|
48
48
|
pagesDir: z.ZodOptional<z.ZodString>;
|
|
49
49
|
port: z.ZodOptional<z.ZodNumber>;
|
|
50
50
|
}, z.core.$strict>>;
|
|
51
|
+
lint: z.ZodOptional<z.ZodObject<{
|
|
52
|
+
rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
53
|
+
error: "error";
|
|
54
|
+
off: "off";
|
|
55
|
+
warn: "warn";
|
|
56
|
+
}>>>;
|
|
57
|
+
}, z.core.$strict>>;
|
|
51
58
|
}, "css" | "playground" | "canvas"> & {
|
|
52
59
|
canvas: z.ZodOptional<z.ZodObject<{
|
|
53
60
|
packageAliases: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
@@ -14,7 +14,7 @@ import { z } from "zod";
|
|
|
14
14
|
/**
|
|
15
15
|
* A link's slug, as a single path segment.
|
|
16
16
|
*
|
|
17
|
-
* The slug is more than a key: `uds install` writes one
|
|
17
|
+
* The slug is more than a key: `uds install` writes one folder per link and names it this, so
|
|
18
18
|
* a slug that isn't a path segment is one the install step can't use, and `../..` would be a traversal
|
|
19
19
|
* handed to a file write. Stated here on the entity so the rule has one home — the install refuses
|
|
20
20
|
* with it, an authoring UI can say so before submitting, and an AI tool creating a link is bound by it
|
|
@@ -50,7 +50,7 @@ const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/
|
|
|
50
50
|
/**
|
|
51
51
|
* A link's slug, as a single path segment.
|
|
52
52
|
*
|
|
53
|
-
* The slug is more than a key: `uds install` writes one
|
|
53
|
+
* The slug is more than a key: `uds install` writes one folder per link and names it this, so
|
|
54
54
|
* a slug that isn't a path segment is one the install step can't use, and `../..` would be a traversal
|
|
55
55
|
* handed to a file write. Stated here on the entity so the rule has one home — the install refuses
|
|
56
56
|
* with it, an authoring UI can say so before submitting, and an AI tool creating a link is bound by it
|
|
@@ -22,7 +22,7 @@ declare const BuildSectionDefinition: SubEntityClass<z.ZodObject<{
|
|
|
22
22
|
outDir: z.ZodOptional<z.ZodString>;
|
|
23
23
|
sourceAlias: z.ZodOptional<z.ZodString>;
|
|
24
24
|
outputAlias: z.ZodOptional<z.ZodString>;
|
|
25
|
-
|
|
25
|
+
linkedDir: z.ZodOptional<z.ZodString>;
|
|
26
26
|
react: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<18>, z.ZodLiteral<19>]>>;
|
|
27
27
|
}, z.core.$strict>>;
|
|
28
28
|
interface BuildSectionClass extends Named<typeof BuildSectionDefinition> {}
|
|
@@ -96,6 +96,16 @@ declare const PlaygroundSectionDefinition: SubEntityClass<z.ZodObject<{
|
|
|
96
96
|
}, z.core.$strict>>;
|
|
97
97
|
interface PlaygroundSectionClass extends Named<typeof PlaygroundSectionDefinition> {}
|
|
98
98
|
declare const PlaygroundSection: PlaygroundSectionClass;
|
|
99
|
+
/** The `lint` section — the authoring lint `uds lint` runs. */
|
|
100
|
+
declare const LintSectionDefinition: SubEntityClass<z.ZodObject<{
|
|
101
|
+
rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
102
|
+
error: "error";
|
|
103
|
+
off: "off";
|
|
104
|
+
warn: "warn";
|
|
105
|
+
}>>>;
|
|
106
|
+
}, z.core.$strict>>;
|
|
107
|
+
interface LintSectionClass extends Named<typeof LintSectionDefinition> {}
|
|
108
|
+
declare const LintSection: LintSectionClass;
|
|
99
109
|
/**
|
|
100
110
|
* {@link derivedInputDir} over paths alone, for a build that has walked its source graph but not yet
|
|
101
111
|
* written the `file` rows the computed `inputDir` reads. `components` are the config-relative paths
|
|
@@ -114,7 +124,7 @@ declare const SettingsDefinition: EntityClass<z.ZodObject<{
|
|
|
114
124
|
outDir: z.ZodOptional<z.ZodString>;
|
|
115
125
|
sourceAlias: z.ZodOptional<z.ZodString>;
|
|
116
126
|
outputAlias: z.ZodOptional<z.ZodString>;
|
|
117
|
-
|
|
127
|
+
linkedDir: z.ZodOptional<z.ZodString>;
|
|
118
128
|
react: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<18>, z.ZodLiteral<19>]>>;
|
|
119
129
|
}, z.core.$strict>>;
|
|
120
130
|
css: z.ZodOptional<z.ZodObject<{
|
|
@@ -133,6 +143,13 @@ declare const SettingsDefinition: EntityClass<z.ZodObject<{
|
|
|
133
143
|
pagesDir: z.ZodOptional<z.ZodString>;
|
|
134
144
|
port: z.ZodOptional<z.ZodNumber>;
|
|
135
145
|
}, z.core.$strict>>;
|
|
146
|
+
lint: z.ZodOptional<z.ZodObject<{
|
|
147
|
+
rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
148
|
+
error: "error";
|
|
149
|
+
off: "off";
|
|
150
|
+
warn: "warn";
|
|
151
|
+
}>>>;
|
|
152
|
+
}, z.core.$strict>>;
|
|
136
153
|
}, z.core.$strict>, z.ZodObject<Record<never, never>, z.core.$strip>, "settings", {}, {
|
|
137
154
|
/**
|
|
138
155
|
* The directory this system's component files are laid out beneath, always defined:
|
|
@@ -147,4 +164,4 @@ declare const SettingsDefinition: EntityClass<z.ZodObject<{
|
|
|
147
164
|
interface SettingsClass extends Named<typeof SettingsDefinition> {}
|
|
148
165
|
declare const Settings: SettingsClass;
|
|
149
166
|
//#endregion
|
|
150
|
-
export { BuildSection, BuildSectionClass, CSS_DEFAULTS, CssPrefix, CssSection, CssSectionClass, PLAYGROUND_DEFAULTS, PlaygroundSection, PlaygroundSectionClass, Settings, SettingsClass, SystemSection, SystemSectionClass, classPrefixOf, cssPrefixes, deriveInputDir, prefixed, varPrefixOf };
|
|
167
|
+
export { BuildSection, BuildSectionClass, CSS_DEFAULTS, CssPrefix, CssSection, CssSectionClass, LintSection, LintSectionClass, PLAYGROUND_DEFAULTS, PlaygroundSection, PlaygroundSectionClass, Settings, SettingsClass, SystemSection, SystemSectionClass, classPrefixOf, cssPrefixes, deriveInputDir, prefixed, varPrefixOf };
|
|
@@ -4,7 +4,7 @@ import { defineSubEntity } from "../../framework/defineSubEntity.js";
|
|
|
4
4
|
import { createSliceMemo } from "../../framework/memo.js";
|
|
5
5
|
import { parseRootFontSize } from "../../css/root-font-size.js";
|
|
6
6
|
import { buildFields } from "../../configs/build-options.js";
|
|
7
|
-
import { resolveInputDir } from "../../framework/
|
|
7
|
+
import { resolveInputDir } from "../../framework/build-dirs.js";
|
|
8
8
|
import { safelistSchema } from "./safelist.js";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
//#region src/entities/system/Settings.ts
|
|
@@ -168,6 +168,28 @@ const PlaygroundSection = defineSubEntity({
|
|
|
168
168
|
singleton: true,
|
|
169
169
|
fields: playgroundFields
|
|
170
170
|
});
|
|
171
|
+
const lintFields = z.strictObject({
|
|
172
|
+
/**
|
|
173
|
+
* How loud each authoring lint rule is, by rule or by group — `{ editability: 'off',
|
|
174
|
+
* 'inline-style-on-layer': 'error' }`. A rule's own entry wins over its group's; a rule named
|
|
175
|
+
* neither way takes its default, a warning for most. A group never turns on a rule that is off by
|
|
176
|
+
* default. Keyed by name rather than an enum, so a rule the build adds later needs no new schema; a
|
|
177
|
+
* name that is no rule or group is reported, not refused.
|
|
178
|
+
*/
|
|
179
|
+
rules: z.record(z.string(), z.enum([
|
|
180
|
+
"off",
|
|
181
|
+
"warn",
|
|
182
|
+
"error"
|
|
183
|
+
])).meta({
|
|
184
|
+
title: "Rules",
|
|
185
|
+
description: "A severity — `'off'`, `'warn'` or `'error'` — per authoring lint rule or per group of them (`portability`, `editability`, `compatibility`, `nursery`): `{ editability: 'off' }` for a system nobody edits in a visual editor such as Create. A `nursery` rule runs only when named on its own. A rule's own entry wins over its group's. Unset, most rules warn. An `'error'` makes `uds lint` exit with status 1, so a CI step that runs it fails. One site is quieted with `// uds-lint-ignore <rule>: <why>` on its line or the line above."
|
|
186
|
+
}).optional() });
|
|
187
|
+
const LintSection = defineSubEntity({
|
|
188
|
+
name: "lint",
|
|
189
|
+
label: "Lint",
|
|
190
|
+
singleton: true,
|
|
191
|
+
fields: lintFields
|
|
192
|
+
});
|
|
171
193
|
/** The folders of a path, file name dropped. */
|
|
172
194
|
const foldersOf = (file) => file.split("/").slice(0, -1);
|
|
173
195
|
/** The folders every one of `lists` starts with. */
|
|
@@ -238,6 +260,23 @@ function deriveInputDir(components, reached) {
|
|
|
238
260
|
for (let end = shared.length; end >= 2; end -= 1) if (shared[end - 2] === "uds" && shared[end - 1] === "components") return shared.slice(0, end).join("/");
|
|
239
261
|
return sharedFolders(reached().map(foldersOf)).join("/");
|
|
240
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* `build.registryDir`, the spelling `build.linkedDir` had before its rename, stored as `linkedDir`. An
|
|
265
|
+
* old seal's `uds.config.ts` still sets it, and `uds pull` still materializes that source. Neither the
|
|
266
|
+
* schema nor the authoring types offer it.
|
|
267
|
+
*/
|
|
268
|
+
function linkedDirFromRegistryDir(body) {
|
|
269
|
+
const build = body.build;
|
|
270
|
+
if (typeof build !== "object" || build === null || !("registryDir" in build)) return body;
|
|
271
|
+
const { registryDir, ...rest } = build;
|
|
272
|
+
return {
|
|
273
|
+
...body,
|
|
274
|
+
build: "linkedDir" in rest ? rest : {
|
|
275
|
+
...rest,
|
|
276
|
+
linkedDir: registryDir
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
241
280
|
const Settings = defineEntity({
|
|
242
281
|
kind: "settings",
|
|
243
282
|
label: "Settings",
|
|
@@ -245,17 +284,20 @@ const Settings = defineEntity({
|
|
|
245
284
|
singleton: true,
|
|
246
285
|
linkable: false,
|
|
247
286
|
authoredRefs: false,
|
|
287
|
+
authoredBody: linkedDirFromRegistryDir,
|
|
248
288
|
fields: z.strictObject({
|
|
249
289
|
system: systemFields.meta({ title: "System" }).optional(),
|
|
250
290
|
build: buildFields.meta({ title: "Build" }).optional(),
|
|
251
291
|
css: cssFields.meta({ title: "CSS" }).optional(),
|
|
252
|
-
playground: playgroundFields.meta({ title: "Playground" }).optional()
|
|
292
|
+
playground: playgroundFields.meta({ title: "Playground" }).optional(),
|
|
293
|
+
lint: lintFields.meta({ title: "Lint" }).optional()
|
|
253
294
|
}),
|
|
254
295
|
subEntities: {
|
|
255
296
|
system: SystemSection,
|
|
256
297
|
build: BuildSection,
|
|
257
298
|
css: CssSection,
|
|
258
|
-
playground: PlaygroundSection
|
|
299
|
+
playground: PlaygroundSection,
|
|
300
|
+
lint: LintSection
|
|
259
301
|
},
|
|
260
302
|
computed: {
|
|
261
303
|
/**
|
|
@@ -266,4 +308,4 @@ const Settings = defineEntity({
|
|
|
266
308
|
inputDir: ({ member, config }) => resolveInputDir(member.build) ?? derivedInputDir(config) }
|
|
267
309
|
});
|
|
268
310
|
//#endregion
|
|
269
|
-
export { BuildSection, CSS_DEFAULTS, CssSection, PLAYGROUND_DEFAULTS, PlaygroundSection, Settings, SystemSection, classPrefixOf, cssPrefixes, deriveInputDir, prefixed, varPrefixOf };
|
|
311
|
+
export { BuildSection, CSS_DEFAULTS, CssSection, LintSection, PLAYGROUND_DEFAULTS, PlaygroundSection, Settings, SystemSection, classPrefixOf, cssPrefixes, deriveInputDir, prefixed, varPrefixOf };
|
|
@@ -4,7 +4,7 @@ import { Config } from "../../framework/Config.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* The modules a system ships beside its components: every source file under its `inputDir` that no
|
|
6
6
|
* component reaches, so an app imports it on its own (`#uds/components/toast-portal`) and a system
|
|
7
|
-
* that links this one emits it too (`@/uds/
|
|
7
|
+
* that links this one emits it too (`@/uds/linked/<slug>/toast-portal`).
|
|
8
8
|
*
|
|
9
9
|
* One rule, read three ways. The owner's build emits these, the server serves them with a linked
|
|
10
10
|
* version, and a linking build emits them from the installed tree. Each reads it from the same
|
|
@@ -73,7 +73,7 @@ interface RecordedSource {
|
|
|
73
73
|
* The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
|
|
74
74
|
* that replaced it (see `detectedWireVersion`).
|
|
75
75
|
*/
|
|
76
|
-
declare const SERIALIZED_CONFIG_VERSION =
|
|
76
|
+
declare const SERIALIZED_CONFIG_VERSION = 20261020183330;
|
|
77
77
|
interface SerializedConfig {
|
|
78
78
|
/** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
|
|
79
79
|
readonly version: number;
|
package/dist/framework/Config.js
CHANGED
|
@@ -5862,7 +5862,7 @@ var Config = class Config {
|
|
|
5862
5862
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
5863
5863
|
hydrateFrom(stored, options) {
|
|
5864
5864
|
const claimed = schemaVersionOf(stored);
|
|
5865
|
-
if (claimed >
|
|
5865
|
+
if (claimed > 20261020183330) throw new SchemaVersionTooNew(claimed);
|
|
5866
5866
|
const wire = upgradeSerializedConfig(stored);
|
|
5867
5867
|
if (!Array.isArray(wire.ownedKinds) || typeof wire.items !== "object" || wire.items === null) throw new ConfigFormatError(`Config "${stored.name ?? "system-config"}" cannot be hydrated: this is not a serialized config-v2 config (no "ownedKinds"/"items"). A pre-cutover build artifact converts through the registered cutover migration — import the config type's module (e.g. \`configs/system\`) before hydrating.`);
|
|
5868
5868
|
const leftover = wire.options;
|
|
@@ -6196,7 +6196,8 @@ function extensionChange(entry, kind, path, raw, validate = false) {
|
|
|
6196
6196
|
* every load.
|
|
6197
6197
|
*/
|
|
6198
6198
|
function itemChange(entry, kind, path, raw, validate = false) {
|
|
6199
|
-
const
|
|
6199
|
+
const body = isRef(raw) || isComposedValue(raw) || typeof raw !== "object" || raw === null ? { [entry.primaryField]: raw } : raw;
|
|
6200
|
+
const def = validate === "author" && entry.authoredBody ? entry.authoredBody(body) : body;
|
|
6200
6201
|
if (validate) {
|
|
6201
6202
|
const parsed = wholeBodySchema(entry)?.safeParse(def);
|
|
6202
6203
|
if (parsed && !parsed.success) {
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
//#region src/framework/
|
|
1
|
+
//#region src/framework/build-dirs.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Where a project's build directories live, relative to the project root — the
|
|
4
|
-
* into, the directory it writes to, and the one it is allowed to read from.
|
|
3
|
+
* Where a project's build directories live, relative to the project root — the directory it installs
|
|
4
|
+
* linked systems into, the directory it writes to, and the one it is allowed to read from.
|
|
5
5
|
*
|
|
6
6
|
* One resolution rule each, shared by every consumer, because a default spelled at the call site is
|
|
7
7
|
* a default that drifts: `.uds` was written out at eight of them before this.
|
|
8
8
|
*
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
* consumer's own sealed sources): `build.
|
|
12
|
-
*
|
|
9
|
+
* The linked half is shared by `uds install` (writes into it), `uds build` (regenerates linked CSS
|
|
10
|
+
* from it), the bundler loaders (skip its files), and the push seal (excludes its files from a
|
|
11
|
+
* consumer's own sealed sources): `build.linkedDir` when the consumer sets one, else `linked/` beside
|
|
12
|
+
* the system's components — `src/uds/linked` in the conventional layout.
|
|
13
13
|
*/
|
|
14
14
|
/** The build setting this reads — structural, so a caller passes `Settings.get(config)?.build`, or
|
|
15
15
|
* the bare `build` record of an artifact it never loaded. */
|
|
16
|
-
interface
|
|
17
|
-
|
|
16
|
+
interface LinkedDirOptions {
|
|
17
|
+
linkedDir?: string;
|
|
18
18
|
inputDir?: string;
|
|
19
19
|
}
|
|
20
20
|
/** The build settings the directory resolvers read. Structural for the same reason. */
|
|
@@ -29,20 +29,33 @@ interface AliasOptions {
|
|
|
29
29
|
}
|
|
30
30
|
/** The prefix a system's sources are imported by (`@/uds`), for their types. */
|
|
31
31
|
declare function resolveSourceAlias(buildOptions: AliasOptions | undefined): string;
|
|
32
|
+
/** The prefix a system's linked systems are imported by, beneath its source alias: `@/uds/linked/`,
|
|
33
|
+
* as in `@/uds/linked/<slug>/button`. Their build output is `#uds/components/_linked/<slug>/…`. */
|
|
34
|
+
declare function resolveLinkedAlias(buildOptions: AliasOptions | undefined): string;
|
|
32
35
|
/** The prefix a system's build output is imported by (`#uds`), mapped onto its `outDir`. */
|
|
33
36
|
declare function resolveOutputAlias(buildOptions: AliasOptions | undefined): string;
|
|
34
37
|
/**
|
|
35
|
-
* Project-root-relative POSIX path of the linked
|
|
38
|
+
* Project-root-relative POSIX path of the directory linked systems are installed in.
|
|
36
39
|
*
|
|
37
|
-
* Unset, it sits beside the system's own components: `
|
|
38
|
-
* (`components` itself dropped, so `src/uds/components` and `src/uds` both give `src/uds/
|
|
40
|
+
* Unset, it sits beside the system's own components: `linked/` under the root its `inputDir` names
|
|
41
|
+
* (`components` itself dropped, so `src/uds/components` and `src/uds` both give `src/uds/linked`),
|
|
39
42
|
* or under `src/uds` when none is declared. Linked sources are then imported the way the system's own
|
|
40
|
-
* are, `@/uds/
|
|
43
|
+
* are, `@/uds/linked/<slug>/…` beside `@/uds/components/…`, and their build output sits at
|
|
44
|
+
* `#uds/components/_linked/<slug>/…`.
|
|
41
45
|
*
|
|
42
46
|
* Read from the AUTHORED settings only, never the derived `inputDir`: a loader that has nothing but a
|
|
43
47
|
* built `config.json` has to land on the same directory the build did.
|
|
44
48
|
*/
|
|
45
|
-
declare function
|
|
49
|
+
declare function resolveLinkedDir(buildOptions: LinkedDirOptions | undefined): string;
|
|
50
|
+
/**
|
|
51
|
+
* Where an unset `linkedDir` pointed before it was renamed from `registryDir`: `registry/` beside the
|
|
52
|
+
* components, `src/uds/registry` in the conventional layout. `undefined` for a system that names its
|
|
53
|
+
* own `linkedDir`, whose directory never moved.
|
|
54
|
+
*
|
|
55
|
+
* Read by `uds install`, which empties it of the entries it installed there, and by the tsconfig
|
|
56
|
+
* writer, which takes back the aliases and the `exclude` entry that pointed into it.
|
|
57
|
+
*/
|
|
58
|
+
declare function resolveRetiredLinkedDir(buildOptions: LinkedDirOptions | undefined): string | undefined;
|
|
46
59
|
/**
|
|
47
60
|
* Project-root-relative POSIX path of the build output directory.
|
|
48
61
|
*
|
|
@@ -59,4 +72,4 @@ declare function resolveOutDir(buildOptions: BuildDirOptions | undefined): strin
|
|
|
59
72
|
*/
|
|
60
73
|
declare function resolveInputDir(buildOptions: BuildDirOptions | undefined): string | undefined;
|
|
61
74
|
//#endregion
|
|
62
|
-
export { resolveInputDir, resolveOutDir, resolveOutputAlias,
|
|
75
|
+
export { resolveInputDir, resolveLinkedAlias, resolveLinkedDir, resolveOutDir, resolveOutputAlias, resolveRetiredLinkedDir, resolveSourceAlias };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { BUILD_DEFAULTS } from "../configs/build-options.js";
|
|
2
|
+
//#region src/framework/build-dirs.ts
|
|
3
|
+
/**
|
|
4
|
+
* Where a project's build directories live, relative to the project root — the directory it installs
|
|
5
|
+
* linked systems into, the directory it writes to, and the one it is allowed to read from.
|
|
6
|
+
*
|
|
7
|
+
* One resolution rule each, shared by every consumer, because a default spelled at the call site is
|
|
8
|
+
* a default that drifts: `.uds` was written out at eight of them before this.
|
|
9
|
+
*
|
|
10
|
+
* The linked half is shared by `uds install` (writes into it), `uds build` (regenerates linked CSS
|
|
11
|
+
* from it), the bundler loaders (skip its files), and the push seal (excludes its files from a
|
|
12
|
+
* consumer's own sealed sources): `build.linkedDir` when the consumer sets one, else `linked/` beside
|
|
13
|
+
* the system's components — `src/uds/linked` in the conventional layout.
|
|
14
|
+
*/
|
|
15
|
+
/** The prefix a system's sources are imported by (`@/uds`), for their types. */
|
|
16
|
+
function resolveSourceAlias(buildOptions) {
|
|
17
|
+
return normalize(buildOptions?.sourceAlias ?? BUILD_DEFAULTS.sourceAlias);
|
|
18
|
+
}
|
|
19
|
+
/** The prefix a system's linked systems are imported by, beneath its source alias: `@/uds/linked/`,
|
|
20
|
+
* as in `@/uds/linked/<slug>/button`. Their build output is `#uds/components/_linked/<slug>/…`. */
|
|
21
|
+
function resolveLinkedAlias(buildOptions) {
|
|
22
|
+
return `${resolveSourceAlias(buildOptions)}/${LINKED_FOLDER}/`;
|
|
23
|
+
}
|
|
24
|
+
/** The prefix a system's build output is imported by (`#uds`), mapped onto its `outDir`. */
|
|
25
|
+
function resolveOutputAlias(buildOptions) {
|
|
26
|
+
return normalize(buildOptions?.outputAlias ?? BUILD_DEFAULTS.outputAlias);
|
|
27
|
+
}
|
|
28
|
+
/** Scanned rather than matched with `/\/+$/`: anchoring a repeated group at the end makes the engine
|
|
29
|
+
* backtrack across the whole run, which is quadratic on a path that is all slashes. */
|
|
30
|
+
const normalize = (p) => {
|
|
31
|
+
const posix = p.replace(/\\/g, "/");
|
|
32
|
+
let end = posix.length;
|
|
33
|
+
while (end > 0 && posix[end - 1] === "/") end--;
|
|
34
|
+
return posix.slice(0, end);
|
|
35
|
+
};
|
|
36
|
+
/** The folder an unset `linkedDir` names beside the components. */
|
|
37
|
+
const LINKED_FOLDER = "linked";
|
|
38
|
+
/** The folder it named before `registryDir` became `linkedDir`. */
|
|
39
|
+
const RETIRED_LINKED_FOLDER = "registry";
|
|
40
|
+
/**
|
|
41
|
+
* Project-root-relative POSIX path of the directory linked systems are installed in.
|
|
42
|
+
*
|
|
43
|
+
* Unset, it sits beside the system's own components: `linked/` under the root its `inputDir` names
|
|
44
|
+
* (`components` itself dropped, so `src/uds/components` and `src/uds` both give `src/uds/linked`),
|
|
45
|
+
* or under `src/uds` when none is declared. Linked sources are then imported the way the system's own
|
|
46
|
+
* are, `@/uds/linked/<slug>/…` beside `@/uds/components/…`, and their build output sits at
|
|
47
|
+
* `#uds/components/_linked/<slug>/…`.
|
|
48
|
+
*
|
|
49
|
+
* Read from the AUTHORED settings only, never the derived `inputDir`: a loader that has nothing but a
|
|
50
|
+
* built `config.json` has to land on the same directory the build did.
|
|
51
|
+
*/
|
|
52
|
+
function resolveLinkedDir(buildOptions) {
|
|
53
|
+
if (buildOptions?.linkedDir) return normalize(buildOptions.linkedDir);
|
|
54
|
+
return besideComponents(buildOptions, LINKED_FOLDER);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Where an unset `linkedDir` pointed before it was renamed from `registryDir`: `registry/` beside the
|
|
58
|
+
* components, `src/uds/registry` in the conventional layout. `undefined` for a system that names its
|
|
59
|
+
* own `linkedDir`, whose directory never moved.
|
|
60
|
+
*
|
|
61
|
+
* Read by `uds install`, which empties it of the entries it installed there, and by the tsconfig
|
|
62
|
+
* writer, which takes back the aliases and the `exclude` entry that pointed into it.
|
|
63
|
+
*/
|
|
64
|
+
function resolveRetiredLinkedDir(buildOptions) {
|
|
65
|
+
if (buildOptions?.linkedDir) return void 0;
|
|
66
|
+
return besideComponents(buildOptions, RETIRED_LINKED_FOLDER);
|
|
67
|
+
}
|
|
68
|
+
/** `folder` under the root the authored `inputDir` names, `components` itself dropped. */
|
|
69
|
+
function besideComponents(buildOptions, folder) {
|
|
70
|
+
const inputDir = buildOptions?.inputDir ? normalize(buildOptions.inputDir) : BUILD_DEFAULTS.inputRoot;
|
|
71
|
+
const root = inputDir === "components" ? "" : inputDir.replace(/\/components$/, "");
|
|
72
|
+
return root ? `${root}/${folder}` : folder;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Project-root-relative POSIX path of the build output directory.
|
|
76
|
+
*
|
|
77
|
+
* The schema's default unless the system says otherwise. A CLI `--output-dir` overrides both, which is
|
|
78
|
+
* why this takes only the config: a scratch build is the caller's business, not the system's.
|
|
79
|
+
*/
|
|
80
|
+
function resolveOutDir(buildOptions) {
|
|
81
|
+
return normalize(buildOptions?.outDir ?? BUILD_DEFAULTS.outDir);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Config-relative POSIX path of the directory every sealed file must live under, or `undefined` when
|
|
85
|
+
* the system declares none.
|
|
86
|
+
*
|
|
87
|
+
* `undefined` is not a default in disguise — it is the answer that keeps the `uds/components/`
|
|
88
|
+
* anchor convention in charge, so a config that never set this emits exactly what it emitted before.
|
|
89
|
+
*/
|
|
90
|
+
function resolveInputDir(buildOptions) {
|
|
91
|
+
if (!buildOptions?.inputDir) return void 0;
|
|
92
|
+
return normalize(buildOptions.inputDir);
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { resolveInputDir, resolveLinkedAlias, resolveLinkedDir, resolveOutDir, resolveOutputAlias, resolveRetiredLinkedDir, resolveSourceAlias };
|
|
@@ -324,7 +324,8 @@ declare function defineEntity<F extends FieldsSchema, G extends FieldsSchema = z
|
|
|
324
324
|
* {@link EntityKind.mentionFields}. */
|
|
325
325
|
mentionFields?: readonly string[]; /** Cross-item rules the kind holds — see {@link EntityKind.invariants}. */
|
|
326
326
|
invariants?: (config: Config) => string[]; /** See {@link EntityKind.structuralBody}. */
|
|
327
|
-
structuralBody?: (data: unknown) => Record<string, unknown>;
|
|
327
|
+
structuralBody?: (data: unknown) => Record<string, unknown>; /** See {@link EntityKind.authoredBody}. */
|
|
328
|
+
authoredBody?: (data: Record<string, unknown>) => Record<string, unknown>;
|
|
328
329
|
/**
|
|
329
330
|
* For a source kind that can be resolved: the two schemas its resolutions are validated against —
|
|
330
331
|
* `{ resolved, unavailable }`. Declare it alongside `sourceIdentity` for a kind a config lists in its
|
|
@@ -134,7 +134,8 @@ function defineEntity(spec) {
|
|
|
134
134
|
authoredValue: spec.authoredValue,
|
|
135
135
|
mentionFields: spec.mentionFields,
|
|
136
136
|
invariants: spec.invariants,
|
|
137
|
-
structuralBody: spec.structuralBody
|
|
137
|
+
structuralBody: spec.structuralBody,
|
|
138
|
+
authoredBody: spec.authoredBody
|
|
138
139
|
});
|
|
139
140
|
const schema = refTo(spec.kind);
|
|
140
141
|
/**
|
|
@@ -352,6 +352,14 @@ interface EntityKind {
|
|
|
352
352
|
* which refuses the delete. `group-delete` does not cascade.
|
|
353
353
|
*/
|
|
354
354
|
readonly structuralBody?: (data: unknown) => Record<string, unknown>;
|
|
355
|
+
/**
|
|
356
|
+
* Rewrite a body authored in a spelling this kind has retired into the one it stores, before it is
|
|
357
|
+
* validated. A `uds.config.ts` written against an older version still evaluates: `uds pull`
|
|
358
|
+
* materializes the source an old seal holds, so a renamed field has to keep being accepted where
|
|
359
|
+
* it is authored. Only `register` runs it. A stored artifact or patch comes through the migration
|
|
360
|
+
* that renamed the field instead.
|
|
361
|
+
*/
|
|
362
|
+
readonly authoredBody?: (data: Record<string, unknown>) => Record<string, unknown>;
|
|
355
363
|
/**
|
|
356
364
|
* Rewrite one name wherever this kind's data holds it as a key — the cascade half of
|
|
357
365
|
* {@link nameEdges}, applied by `Config.apply`'s rename alongside `rewriteRefs`.
|
|
@@ -32,7 +32,7 @@ function titleCase(kind) {
|
|
|
32
32
|
/** Derive a kind's immutable metadata from its field schemas — the value a `defineEntity`/
|
|
33
33
|
* `defineDerivedEntity` attaches to its class and a `Config.register` stores in `#entities`. */
|
|
34
34
|
function buildKind(spec) {
|
|
35
|
-
const { name, prepareLinkedBody, rebaseLinkedBody, layerKeys, fields, groupFields, subEntities = {}, computed, label, labelPlural, schemas: userSchemas, derived, sourceIdentity, sourceOrigin, authoredRefs, linkable, localGroupFields, emergentGroups, singleton = false, bareMember, sourceResolution, bulkAdopts, bulkExcludes, bulkOverlay, bulkDirectives, bulkOverlayWrite, nameEdges, rewriteName, styleBags, uniqueLeaves, authoredValue, mentionFields, invariants, structuralBody } = spec;
|
|
35
|
+
const { name, prepareLinkedBody, rebaseLinkedBody, layerKeys, fields, groupFields, subEntities = {}, computed, label, labelPlural, schemas: userSchemas, derived, sourceIdentity, sourceOrigin, authoredRefs, linkable, localGroupFields, emergentGroups, singleton = false, bareMember, sourceResolution, bulkAdopts, bulkExcludes, bulkOverlay, bulkDirectives, bulkOverlayWrite, nameEdges, rewriteName, styleBags, uniqueLeaves, authoredValue, mentionFields, invariants, structuralBody, authoredBody } = spec;
|
|
36
36
|
const allComputed = {
|
|
37
37
|
sources: ({ member, config }) => sourceSlugs(member, config),
|
|
38
38
|
borrowedFrom: ({ config, path }) => config.borrowedFrom(name, path),
|
|
@@ -84,7 +84,8 @@ function buildKind(spec) {
|
|
|
84
84
|
...authoredValue ? { authoredValue } : {},
|
|
85
85
|
...mentionFields?.length ? { mentionFields } : {},
|
|
86
86
|
...invariants ? { invariants } : {},
|
|
87
|
-
...structuralBody ? { structuralBody } : {}
|
|
87
|
+
...structuralBody ? { structuralBody } : {},
|
|
88
|
+
...authoredBody ? { authoredBody } : {}
|
|
88
89
|
};
|
|
89
90
|
}
|
|
90
91
|
/** Every operation a kind exposes — its merged `schemas` map, flattened to `{ kind, verb, ... }`.
|
|
@@ -15,7 +15,7 @@ import { Patch, SerializedConfig } from "./Config.js";
|
|
|
15
15
|
* sets this constant to its own older timestamp; the merge ref then carries a registered migration
|
|
16
16
|
* newer than the constant, and the assertion fails until the author re-mints.
|
|
17
17
|
*/
|
|
18
|
-
declare const CURRENT_SCHEMA_VERSION =
|
|
18
|
+
declare const CURRENT_SCHEMA_VERSION = 20261020183330;
|
|
19
19
|
/** What the walkers need to see of an envelope — loose on purpose, so a typed `SerializedConfig`
|
|
20
20
|
* and a raw old-shape record both flow in without casts. */
|
|
21
21
|
interface VersionStamped {
|