@yahoo/uds-create-config 5.42.3 → 5.42.4

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.
@@ -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"; /** The registry beside the conventional components root — see `resolveRegistryDir`. */
7
- readonly registryDir: "src/uds/registry"; /** Where a system that declares no `inputDir` keeps its sources, for what sits beside them. */
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
- /** The registry beside the conventional components root — see `resolveRegistryDir`. */
31
- registryDir: "src/uds/registry",
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/registry/<slug>/<file>`.
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/registry/<slug>/<path>`.
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/registry/<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."
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}/registry/<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}\`.`,
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 pull` materializes linked-system sources — see `resolveRegistryDir`. */
113
- registryDir: z.string().min(1).meta({
114
- title: "Registry directory",
115
- description: `Where \`uds install\` places the sources of linked systems, relative to the config. Unset, \`registry/\` beside the system's components: \`${BUILD_DEFAULTS.registryDir}\` unless \`inputDir\` names another root.`,
116
- default: BUILD_DEFAULTS.registryDir
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
- registryDir: _$zod.ZodOptional<_$zod.ZodString>;
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<{
@@ -24,6 +24,7 @@ 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";
27
28
  import { CanvasRole } from "../entities/system/CanvasRole.js";
28
29
  import { GlobalStyle } from "../entities/system/GlobalStyle.js";
29
30
  import { Guidance } from "../entities/system/Guidance.js";
@@ -179,5 +180,6 @@ if (!registeredSchemaMigrations().has(20261016164316)) registerSchemaMigrations(
179
180
  if (!registeredSchemaMigrations().has(20261016201727)) registerSchemaMigrations(nativeStyleArraysAndAutoMigration);
180
181
  if (!registeredSchemaMigrations().has(20261019165323)) registerSchemaMigrations(buildExcludeMigration);
181
182
  if (!registeredSchemaMigrations().has(20261020173914)) registerSchemaMigrations(nativeLinearGradientsMigration);
183
+ if (!registeredSchemaMigrations().has(20261020182408)) registerSchemaMigrations(linkedDirMigration);
182
184
  //#endregion
183
185
  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
- registryDir: z.ZodOptional<z.ZodString>;
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<{
@@ -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 registry folder per link and names it this, so
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 registry folder per link and names it this, so
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
- registryDir: z.ZodOptional<z.ZodString>;
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> {}
@@ -114,7 +114,7 @@ declare const SettingsDefinition: EntityClass<z.ZodObject<{
114
114
  outDir: z.ZodOptional<z.ZodString>;
115
115
  sourceAlias: z.ZodOptional<z.ZodString>;
116
116
  outputAlias: z.ZodOptional<z.ZodString>;
117
- registryDir: z.ZodOptional<z.ZodString>;
117
+ linkedDir: z.ZodOptional<z.ZodString>;
118
118
  react: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<18>, z.ZodLiteral<19>]>>;
119
119
  }, z.core.$strict>>;
120
120
  css: z.ZodOptional<z.ZodObject<{
@@ -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/registry-dir.js";
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
@@ -238,6 +238,23 @@ function deriveInputDir(components, reached) {
238
238
  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
239
  return sharedFolders(reached().map(foldersOf)).join("/");
240
240
  }
241
+ /**
242
+ * `build.registryDir`, the spelling `build.linkedDir` had before its rename, stored as `linkedDir`. An
243
+ * old seal's `uds.config.ts` still sets it, and `uds pull` still materializes that source. Neither the
244
+ * schema nor the authoring types offer it.
245
+ */
246
+ function linkedDirFromRegistryDir(body) {
247
+ const build = body.build;
248
+ if (typeof build !== "object" || build === null || !("registryDir" in build)) return body;
249
+ const { registryDir, ...rest } = build;
250
+ return {
251
+ ...body,
252
+ build: "linkedDir" in rest ? rest : {
253
+ ...rest,
254
+ linkedDir: registryDir
255
+ }
256
+ };
257
+ }
241
258
  const Settings = defineEntity({
242
259
  kind: "settings",
243
260
  label: "Settings",
@@ -245,6 +262,7 @@ const Settings = defineEntity({
245
262
  singleton: true,
246
263
  linkable: false,
247
264
  authoredRefs: false,
265
+ authoredBody: linkedDirFromRegistryDir,
248
266
  fields: z.strictObject({
249
267
  system: systemFields.meta({ title: "System" }).optional(),
250
268
  build: buildFields.meta({ title: "Build" }).optional(),
@@ -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/registry/<slug>/toast-portal`).
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 = 20261020173914;
76
+ declare const SERIALIZED_CONFIG_VERSION = 20261020182408;
77
77
  interface SerializedConfig {
78
78
  /** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
79
79
  readonly version: number;
@@ -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 > 20261020173914) throw new SchemaVersionTooNew(claimed);
5865
+ if (claimed > 20261020182408) 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 def = isRef(raw) || isComposedValue(raw) || typeof raw !== "object" || raw === null ? { [entry.primaryField]: raw } : raw;
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/registry-dir.d.ts
1
+ //#region src/framework/build-dirs.d.ts
2
2
  /**
3
- * Where a project's build directories live, relative to the project root — the registry it pulls
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 registry half is shared by `uds pull` (writes the registry), `uds build` (regenerates linked
10
- * CSS from it), the bundler loaders (skip its files), and the push seal (excludes its files from a
11
- * consumer's own sealed sources): `build.registryDir` when the consumer sets one, else `registry/`
12
- * beside the system's components — `src/uds/registry` in the conventional layout.
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 RegistryDirOptions {
17
- registryDir?: string;
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-system registry.
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: `registry/` under the root its `inputDir` names
38
- * (`components` itself dropped, so `src/uds/components` and `src/uds` both give `src/uds/registry`),
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/registry/<slug>/…` beside `@/uds/components/…`.
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 resolveRegistryDir(buildOptions: RegistryDirOptions | undefined): string;
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, resolveRegistryDir, resolveSourceAlias };
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 = 20261020173914;
18
+ declare const CURRENT_SCHEMA_VERSION = 20261020182408;
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 {
@@ -13,7 +13,7 @@ import { ConfigFormatError } from "./rejection.js";
13
13
  * sets this constant to its own older timestamp; the merge ref then carries a registered migration
14
14
  * newer than the constant, and the assertion fails until the author re-mints.
15
15
  */
16
- const CURRENT_SCHEMA_VERSION = 20261020173914;
16
+ const CURRENT_SCHEMA_VERSION = 20261020182408;
17
17
  /** The mint that moved `options` into the `settings` kind — restated here, rather than imported
18
18
  * from the migration that owns it, because that migration imports this module. */
19
19
  const SETTINGS_AS_KIND_VERSION = 20260908171448;
@@ -45,7 +45,7 @@ function registerSchemaMigrations(...migrations) {
45
45
  for (const migration of migrations) {
46
46
  if (migration.version !== 1 && !isMintTimestamp(migration.version)) throw new Error(`Schema migration version ${migration.version} is not a mint timestamp — mint one with \`date +%Y%m%d%H%M%S\`, like a supabase migration.`);
47
47
  if (REGISTERED.has(migration.version)) throw new Error(`A schema migration at version ${migration.version} is already registered.`);
48
- if (migration.version > 20261020173914) throw new Error(`Schema migration ${migration.version} is newer than CURRENT_SCHEMA_VERSION (${CURRENT_SCHEMA_VERSION}) — a stale branch merged, or the mint forgot to bump the constant. Re-mint: set CURRENT_SCHEMA_VERSION to the newest migration version.`);
48
+ if (migration.version > 20261020182408) throw new Error(`Schema migration ${migration.version} is newer than CURRENT_SCHEMA_VERSION (${CURRENT_SCHEMA_VERSION}) — a stale branch merged, or the mint forgot to bump the constant. Re-mint: set CURRENT_SCHEMA_VERSION to the newest migration version.`);
49
49
  REGISTERED.set(migration.version, migration);
50
50
  }
51
51
  }
@@ -102,7 +102,7 @@ function pendingFor(version, target, options) {
102
102
  * Already-current input passes through untouched — same reference, so hot paths pay nothing.
103
103
  */
104
104
  function upgradeSerializedConfig(json, options) {
105
- const target = options?.target ?? 20261020173914;
105
+ const target = options?.target ?? 20261020182408;
106
106
  const version = detectedWireVersion(json);
107
107
  if (version > target) throw new SchemaVersionTooNew(version, target);
108
108
  const raw = json;
@@ -160,7 +160,7 @@ function upgradedSources(json, options) {
160
160
  * meaning under the current schema and replay skips it — reported by the caller, not swallowed here.
161
161
  */
162
162
  function upgradePatch(patch, writtenAt, options) {
163
- const target = options?.target ?? 20261020173914;
163
+ const target = options?.target ?? 20261020182408;
164
164
  if (writtenAt > target) throw new SchemaVersionTooNew(writtenAt, target);
165
165
  let current = patch;
166
166
  for (const migration of pendingFor(writtenAt, target, options)) {
@@ -179,7 +179,7 @@ function upgradePatch(patch, writtenAt, options) {
179
179
  * stamp would cause.
180
180
  */
181
181
  function upgradeDraftEntries(draft, options) {
182
- const target = options?.target ?? 20261020173914;
182
+ const target = options?.target ?? 20261020182408;
183
183
  const entries = [];
184
184
  const held = [];
185
185
  const retired = [];