@byline/cli 3.20.4 → 3.21.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.
Files changed (42) hide show
  1. package/README.md +14 -0
  2. package/dist/phases/scaffold.d.ts +2 -0
  3. package/dist/phases/scaffold.d.ts.map +1 -1
  4. package/dist/phases/scaffold.js +26 -0
  5. package/dist/phases/scaffold.js.map +1 -1
  6. package/dist/phases/scaffold.test.d.ts +2 -0
  7. package/dist/phases/scaffold.test.d.ts.map +1 -0
  8. package/dist/phases/scaffold.test.js +29 -0
  9. package/dist/phases/scaffold.test.js.map +1 -0
  10. package/dist/templates/byline/admin.config.ts +8 -7
  11. package/dist/templates/byline/client.server.ts +32 -0
  12. package/dist/templates/byline/collection-types.contract.ts +42 -0
  13. package/dist/templates/byline/collections/index.ts +17 -0
  14. package/dist/templates/byline/generated/collection-types.ts +11 -0
  15. package/dist/templates/byline/i18n.ts +3 -3
  16. package/dist/templates/byline/locales.ts +9 -9
  17. package/dist/templates/byline/public.ts +12 -0
  18. package/dist/templates/byline/scripts/generate-types.ts +81 -0
  19. package/dist/templates/byline/server.config.ts +3 -5
  20. package/dist/templates/byline-examples/admin.config.ts +12 -10
  21. package/dist/templates/byline-examples/client.server.ts +32 -0
  22. package/dist/templates/byline-examples/collection-types.contract.ts +42 -0
  23. package/dist/templates/byline-examples/collections/docs/hooks.ts +11 -11
  24. package/dist/templates/byline-examples/collections/docs/schema.ts +21 -12
  25. package/dist/templates/byline-examples/collections/index.ts +22 -0
  26. package/dist/templates/byline-examples/collections/media/schema.ts +13 -0
  27. package/dist/templates/byline-examples/collections/news/schema.ts +13 -0
  28. package/dist/templates/byline-examples/collections/news-categories/schema.ts +13 -0
  29. package/dist/templates/byline-examples/collections/pages/schema.ts +13 -0
  30. package/dist/templates/byline-examples/generated/collection-types.ts +139 -0
  31. package/dist/templates/byline-examples/i18n.ts +3 -3
  32. package/dist/templates/byline-examples/locales.ts +9 -9
  33. package/dist/templates/byline-examples/public.ts +12 -0
  34. package/dist/templates/byline-examples/scripts/generate-types.ts +81 -0
  35. package/dist/templates/byline-examples/server.config.ts +2 -12
  36. package/dist/templates/routes/_byline/admin/collections/$collection/$id/api.tsx +1 -6
  37. package/dist/templates/routes/_byline/admin/collections/$collection/$id/history.tsx +1 -7
  38. package/dist/templates/routes/_byline/admin/collections/$collection/$id/index.tsx +1 -6
  39. package/dist/templates/ui-byline/blocks/photo-block/index.tsx +1 -2
  40. package/dist/templates/ui-byline/blocks/richtext-block/index.tsx +1 -1
  41. package/dist/templates/ui-byline/render-blocks.tsx +3 -7
  42. package/package.json +1 -1
package/README.md CHANGED
@@ -22,6 +22,20 @@ byline doctor Inspect the current app and report what's wired.
22
22
 
23
23
  See `byline init --help` for the full flag list.
24
24
 
25
+ ### Generated collection types
26
+
27
+ `byline init` commits `byline/generated/collection-types.ts` and adds two application scripts:
28
+
29
+ ```sh
30
+ pnpm byline:generate # regenerate after changing collection or block schemas
31
+ pnpm byline:generate:check # fail without writing when the artifact is missing or stale
32
+ ```
33
+
34
+ The application script evaluates `byline/collections/index.ts`; it does not load
35
+ `server.config.ts`. That collection tuple remains the runtime registry, while the generated module
36
+ is its deterministic, standalone TypeScript projection for typed clients and frontend code. Keep
37
+ the artifact committed and run the check in CI.
38
+
25
39
  ### Already-wired apps (post-manual-config)
26
40
 
27
41
  If you wired Byline into your app by hand (collections, `server.config.ts`, env, routes, scaffold files all in place) and just need to provision the database and seed, use `setup` instead of `init`:
@@ -1,3 +1,5 @@
1
+ import type { Context } from '../context.js';
1
2
  import type { Phase } from '../types.js';
2
3
  export declare const scaffoldPhase: Phase;
4
+ export declare function ensureGenerationScripts(ctx: Context): string[];
3
5
  //# sourceMappingURL=scaffold.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/phases/scaffold.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAcxC,eAAO,MAAM,aAAa,EAAE,KA0D3B,CAAA"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/phases/scaffold.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAkBxC,eAAO,MAAM,aAAa,EAAE,KA8D3B,CAAA;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,CAmB9D"}
@@ -3,6 +3,10 @@ import { dirname, join, relative } from 'node:path';
3
3
  const TARGET_DIR = 'byline';
4
4
  const BASE_TEMPLATE = 'byline';
5
5
  const EXAMPLES_TEMPLATE = 'byline-examples';
6
+ const GENERATION_SCRIPTS = {
7
+ 'byline:generate': 'tsx byline/scripts/generate-types.ts',
8
+ 'byline:generate:check': 'tsx byline/scripts/generate-types.ts --check',
9
+ };
6
10
  export const scaffoldPhase = {
7
11
  id: 'scaffold',
8
12
  title: 'Scaffold — copy byline/ config tree into the app root',
@@ -48,9 +52,31 @@ export const scaffoldPhase = {
48
52
  if (skipped > 0) {
49
53
  ctx.logger.info(`${skipped} file(s) already existed and were left untouched — re-running scaffold is non-destructive`);
50
54
  }
55
+ const addedScripts = ensureGenerationScripts(ctx);
56
+ if (addedScripts.length > 0) {
57
+ ctx.logger.success(`added package.json scripts: ${addedScripts.join(', ')}`);
58
+ }
51
59
  return { state: 'done' };
52
60
  },
53
61
  };
62
+ export function ensureGenerationScripts(ctx) {
63
+ const packagePath = ctx.resolve('package.json');
64
+ if (!existsSync(packagePath))
65
+ return [];
66
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
67
+ const scripts = { ...(pkg.scripts ?? {}) };
68
+ const added = [];
69
+ for (const [name, command] of Object.entries(GENERATION_SCRIPTS)) {
70
+ if (scripts[name] !== undefined)
71
+ continue;
72
+ scripts[name] = command;
73
+ added.push(name);
74
+ }
75
+ if (added.length === 0)
76
+ return added;
77
+ writeFileSync(packagePath, `${JSON.stringify({ ...pkg, scripts }, null, 2)}\n`, 'utf8');
78
+ return added;
79
+ }
54
80
  function collectEntries(ctx, examples, importDocs) {
55
81
  const targetRoot = ctx.resolve(TARGET_DIR);
56
82
  const baseRoot = join(ctx.templatesDir(), BASE_TEMPLATE);
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/phases/scaffold.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/phases/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scaffold.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.test.d.ts","sourceRoot":"","sources":["../../src/phases/scaffold.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join, resolve } from 'node:path';
4
+ import { afterEach, describe, expect, it } from 'vitest';
5
+ import { ensureGenerationScripts } from './scaffold.js';
6
+ const temporaryDirectories = [];
7
+ afterEach(() => {
8
+ for (const directory of temporaryDirectories.splice(0)) {
9
+ rmSync(directory, { recursive: true, force: true });
10
+ }
11
+ });
12
+ describe('ensureGenerationScripts', () => {
13
+ it('adds generation commands without replacing an existing script', () => {
14
+ const directory = mkdtempSync(join(tmpdir(), 'byline-scaffold-'));
15
+ temporaryDirectories.push(directory);
16
+ writeFileSync(join(directory, 'package.json'), `${JSON.stringify({ scripts: { 'byline:generate': 'custom-generator' } }, null, 2)}\n`, 'utf8');
17
+ const ctx = {
18
+ resolve: (...parts) => resolve(directory, ...parts),
19
+ };
20
+ expect(ensureGenerationScripts(ctx)).toEqual(['byline:generate:check']);
21
+ const pkg = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));
22
+ expect(pkg.scripts).toEqual({
23
+ 'byline:generate': 'custom-generator',
24
+ 'byline:generate:check': 'tsx byline/scripts/generate-types.ts --check',
25
+ });
26
+ expect(ensureGenerationScripts(ctx)).toEqual([]);
27
+ });
28
+ });
29
+ //# sourceMappingURL=scaffold.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.test.js","sourceRoot":"","sources":["../../src/phases/scaffold.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC1E,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAExD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAGvD,MAAM,oBAAoB,GAAa,EAAE,CAAA;AAEzC,SAAS,CAAC,GAAG,EAAE;IACb,KAAK,MAAM,SAAS,IAAI,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACrD,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAA;QACjE,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACpC,aAAa,CACX,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,EAC/B,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EACtF,MAAM,CACP,CAAA;QACD,MAAM,GAAG,GAAG;YACV,OAAO,EAAE,CAAC,GAAG,KAAe,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC;SACnD,CAAA;QAEZ,MAAM,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAA;QACvE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAE3E,CAAA;QACD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;YAC1B,iBAAiB,EAAE,kBAAkB;YACrC,uBAAuB,EAAE,8CAA8C;SACxE,CAAC,CAAA;QACF,MAAM,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -8,20 +8,21 @@
8
8
 
9
9
  /**
10
10
  * Registers Byline's client-side config (collection admin UI configs,
11
- * field editors, i18n, routes) in the current module graph. Imported as
12
- * a side-effect from `src/routes/_byline/route.lazy.tsx` so registration
13
- * fires only when a `_byline/*` URL matches — keeping the Lexical
14
- * editor module graph out of public-route bundles entirely.
11
+ * field editors, i18n, routes) in the current module graph. The `_byline`
12
+ * route registers it from two complementary points: a dynamic import in
13
+ * `route.tsx` covers child loaders, while the side-effect import in
14
+ * `route.lazy.tsx` covers component render and initial hydration. Keeping
15
+ * both imports behind `_byline/*` keeps the admin graph off public routes.
15
16
  *
16
17
  * This is the minimal scaffold: no example collections registered. Add
17
- * collection schemas + admin configs to the `collections` and `admin`
18
- * arrays as you create them.
18
+ * schemas to `collections/index.ts` and presentation configs to `admin`.
19
19
  */
20
20
 
21
21
  import type { ClientConfig } from '@byline/core'
22
22
  import { defineClientConfig } from '@byline/core'
23
23
  import { RichTextField as LexicalRichTextField } from '@byline/richtext-lexical'
24
24
 
25
+ import { collections } from './collections/index.js'
25
26
  import { i18n } from './i18n.js'
26
27
  import { DEFAULT_SERVER_URL, routes } from './routes.js'
27
28
 
@@ -31,7 +32,7 @@ export const config: ClientConfig = {
31
32
  serverURL,
32
33
  i18n,
33
34
  routes,
34
- collections: [],
35
+ collections,
35
36
  admin: [],
36
37
  fields: {
37
38
  richText: { editor: LexicalRichTextField },
@@ -0,0 +1,32 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Server-only application boundary for the host-owned client singletons.
11
+ * The host stays config-agnostic; this assertion binds it to the app registry.
12
+ */
13
+ import type { BylineClient } from '@byline/client'
14
+ import {
15
+ getAdminBylineClient as getHostAdminBylineClient,
16
+ getSystemBylineClient as getHostSystemBylineClient,
17
+ } from '@byline/host-tanstack-start/integrations/byline-client'
18
+ import { getPublicBylineClient as getHostPublicBylineClient } from '@byline/host-tanstack-start/integrations/byline-public-client'
19
+ import {
20
+ getViewerBylineClient as getHostViewerBylineClient,
21
+ isPreviewActive,
22
+ } from '@byline/host-tanstack-start/integrations/byline-viewer-client'
23
+
24
+ import type { BylineCollections } from './collections/index.js'
25
+
26
+ type AppBylineClient = BylineClient<BylineCollections>
27
+
28
+ export const getAdminBylineClient = getHostAdminBylineClient as () => AppBylineClient
29
+ export const getPublicBylineClient = getHostPublicBylineClient as () => AppBylineClient
30
+ export const getSystemBylineClient = getHostSystemBylineClient as () => AppBylineClient
31
+ export const getViewerBylineClient = getHostViewerBylineClient as () => AppBylineClient
32
+ export { isPreviewActive }
@@ -0,0 +1,42 @@
1
+ import type { CollectionFieldData, CollectionFieldDataAllLocales } from '@byline/core'
2
+
3
+ import type { collections } from './collections/index.js'
4
+ import type {
5
+ CollectionFieldsAllLocalesByPath,
6
+ CollectionFieldsByPath,
7
+ } from './generated/collection-types.js'
8
+
9
+ type InferredFieldsByPath = {
10
+ [Definition in (typeof collections)[number] as Definition['path']]: CollectionFieldData<Definition>
11
+ }
12
+
13
+ type InferredFieldsAllLocalesByPath = {
14
+ [Definition in (typeof collections)[number] as Definition['path']]: CollectionFieldDataAllLocales<Definition>
15
+ }
16
+
17
+ type IsAny<Value> = 0 extends 1 & Value ? true : false
18
+ type Exact<Left, Right> = IsAny<Left> extends true
19
+ ? IsAny<Right>
20
+ : IsAny<Right> extends true
21
+ ? false
22
+ : (<Value>() => Value extends Left ? 1 : 2) extends <Value>() =>
23
+ Value extends Right ? 1 : 2
24
+ ? (<Value>() => Value extends Right ? 1 : 2) extends <Value>() =>
25
+ Value extends Left ? 1 : 2
26
+ ? true
27
+ : false
28
+ : false
29
+ type Assert<Value extends true> = Value
30
+
31
+ type GeneratedKeysMatchRuntimeRegistry = Assert<
32
+ Exact<keyof CollectionFieldsByPath, keyof InferredFieldsByPath>
33
+ >
34
+ type GeneratedFieldsMatchRuntimeRegistry = Assert<
35
+ Exact<CollectionFieldsByPath, InferredFieldsByPath>
36
+ >
37
+ type GeneratedAllLocalesKeysMatchRuntimeRegistry = Assert<
38
+ Exact<keyof CollectionFieldsAllLocalesByPath, keyof InferredFieldsAllLocalesByPath>
39
+ >
40
+ type GeneratedAllLocalesFieldsMatchRuntimeRegistry = Assert<
41
+ Exact<CollectionFieldsAllLocalesByPath, InferredFieldsAllLocalesByPath>
42
+ >
@@ -0,0 +1,17 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import type { InferCollectionRegistry } from '@byline/core'
10
+
11
+ import type { CollectionFieldsByPath } from '../generated/collection-types.js'
12
+
13
+ /** Add schema-only collection imports here; keep admin presentation separate. */
14
+ export const collections = [] as const
15
+
16
+ export type InferredBylineCollections = InferCollectionRegistry<typeof collections>
17
+ export type BylineCollections = CollectionFieldsByPath
@@ -0,0 +1,11 @@
1
+ // Generated by @byline/core/codegen
2
+ // Format version: 1
3
+ // Hash: 4ef42b796dc35fceb394f97eaef885c5795c52031ba91f9ca6ca7e0fd2f20608
4
+
5
+ export type CollectionFieldsByPath = {
6
+ }
7
+
8
+ export type CollectionFieldsAllLocalesByPath = {
9
+ }
10
+
11
+ export type CollectionPath = keyof CollectionFieldsByPath
@@ -12,9 +12,9 @@
12
12
  * translation bundle.
13
13
  *
14
14
  * The locale arrays themselves live in `./locales.ts`, a dependency-free leaf
15
- * module, so the public frontend can import them without pulling in the admin
16
- * translation graph this file depends on. Re-exported here for back-compat
17
- * with existing `~/i18n` importers.
15
+ * module exposed to public frontend code by `./public.ts`, without pulling in
16
+ * the admin translation graph this file depends on. Re-exported here for
17
+ * back-compat with existing `~/i18n` importers.
18
18
  *
19
19
  * `interface` locales govern the CMS admin UI language.
20
20
  * `content` locales govern the languages a document can be published in.
@@ -12,12 +12,12 @@
12
12
  * registry). `byline/i18n.ts` consumes these to assemble the
13
13
  * `defineServerConfig` / `defineClientConfig` payload.
14
14
  *
15
- * Kept separate from `byline/i18n.ts` so a public frontend (or a collection
16
- * schema file loaded outside Vite — e.g. when running seeds via tsx) can
17
- * import the locale arrays without dragging in the admin translation graph
18
- * (`@byline/i18n/admin`) that `i18n.ts` depends on. Importing `byline/i18n.ts`
19
- * would; importing this won't — which keeps the lazy admin/public bundle split
20
- * (`src/routes/_byline/route.lazy.tsx`) intact.
15
+ * Kept separate from `byline/i18n.ts` so `byline/public.ts` can expose the
16
+ * locale arrays to a public frontend (or a collection schema file loaded
17
+ * outside Vite — e.g. when running seeds via tsx) without dragging in the
18
+ * admin translation graph (`@byline/i18n/admin`) that `i18n.ts` depends on.
19
+ * Importing `byline/i18n.ts` would; importing this won't — which keeps the lazy
20
+ * admin/public bundle split (`src/routes/_byline/route.lazy.tsx`) intact.
21
21
  *
22
22
  * `interface` locales govern the CMS admin UI language; `content` locales
23
23
  * govern the languages a document can be published in.
@@ -33,10 +33,10 @@ export interface LocaleDefinition {
33
33
  // (or a third-party plugin merged in via `mergeTranslations(...)`).
34
34
  // `adminTranslations({ locales })` in `byline/i18n.ts` throws at boot if a
35
35
  // requested code is not bundled — `@byline/i18n/admin` ships English and French.
36
- export const interfaceLocales: LocaleDefinition[] = [
36
+ export const interfaceLocales = [
37
37
  { code: 'en', label: 'English' },
38
38
  { code: 'fr', label: 'Français' },
39
- ]
39
+ ] as const satisfies readonly LocaleDefinition[]
40
40
 
41
41
  /** Locales a document can be published in. */
42
42
  export const contentLocales = [
@@ -44,4 +44,4 @@ export const contentLocales = [
44
44
  { code: 'fr', label: 'Français' },
45
45
  { code: 'es', label: 'Español' },
46
46
  { code: 'de', label: 'Deutsch' },
47
- ] as const
47
+ ] as const satisfies readonly LocaleDefinition[]
@@ -0,0 +1,12 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /** Client-safe public configuration. Keep this barrel free of admin and server imports. */
10
+ export { contentLocales, interfaceLocales } from './locales.js'
11
+ export { routes } from './routes.js'
12
+ export type { LocaleDefinition } from './locales.js'
@@ -0,0 +1,81 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { dirname, resolve } from 'node:path'
4
+ import { fileURLToPath, pathToFileURL } from 'node:url'
5
+
6
+ import { emitCollectionTypes } from '@byline/core/codegen'
7
+
8
+ import { collections } from '../collections/index.js'
9
+
10
+ const defaultOutputPath = fileURLToPath(
11
+ new URL('../generated/collection-types.ts', import.meta.url)
12
+ )
13
+
14
+ export type GenerationStatus = 'current' | 'unchanged' | 'written'
15
+
16
+ export function writeCollectionTypes(
17
+ outputPath: string,
18
+ source: string,
19
+ check: boolean
20
+ ): GenerationStatus {
21
+ const existing = existsSync(outputPath) ? readFileSync(outputPath, 'utf8') : undefined
22
+
23
+ if (check) {
24
+ if (existing === undefined) {
25
+ throw new Error(
26
+ `generated collection types are missing: ${outputPath}. Run pnpm byline:generate`
27
+ )
28
+ }
29
+ if (existing !== source) {
30
+ throw new Error(
31
+ `generated collection types are stale: ${outputPath}. Run pnpm byline:generate`
32
+ )
33
+ }
34
+ return 'current'
35
+ }
36
+
37
+ if (existing === source) return 'unchanged'
38
+
39
+ mkdirSync(dirname(outputPath), { recursive: true })
40
+ const temporaryPath = `${outputPath}.${process.pid}.${randomUUID()}.tmp`
41
+ try {
42
+ writeFileSync(temporaryPath, source, 'utf8')
43
+ renameSync(temporaryPath, outputPath)
44
+ } finally {
45
+ rmSync(temporaryPath, { force: true })
46
+ }
47
+ return 'written'
48
+ }
49
+
50
+ function parseArgs(args: readonly string[]): { check: boolean } {
51
+ const unknown = args.filter((arg) => arg !== '--check')
52
+ if (unknown.length > 0) throw new Error(`unknown argument: ${unknown.join(', ')}`)
53
+ if (args.filter((arg) => arg === '--check').length > 1) {
54
+ throw new Error('duplicate argument: --check')
55
+ }
56
+ return { check: args.includes('--check') }
57
+ }
58
+
59
+ export function generateCollectionTypes(
60
+ outputPath = defaultOutputPath,
61
+ check = false
62
+ ): { hash: string; status: GenerationStatus } {
63
+ const { source, hash } = emitCollectionTypes(collections)
64
+ return { hash, status: writeCollectionTypes(outputPath, source, check) }
65
+ }
66
+
67
+ function main(): void {
68
+ const { check } = parseArgs(process.argv.slice(2))
69
+ const { hash, status } = generateCollectionTypes(defaultOutputPath, check)
70
+ console.log(`collection types: ${collections.length} collections ${hash.slice(0, 12)} ${status}`)
71
+ }
72
+
73
+ const entryPath = process.argv[1]
74
+ if (entryPath !== undefined && pathToFileURL(resolve(entryPath)).href === import.meta.url) {
75
+ try {
76
+ main()
77
+ } catch (error) {
78
+ console.error(`collection type generation failed: ${(error as Error).message}`)
79
+ process.exitCode = 1
80
+ }
81
+ }
@@ -14,8 +14,7 @@
14
14
  * read it back with `getBylineCore<AdminStore>()`.
15
15
  *
16
16
  * This is the minimal scaffold: no example collections registered. Add
17
- * collection schemas to the `collections` array as you create them under
18
- * `byline/collections/<name>/schema.ts`.
17
+ * collection schemas to the shared tuple in `byline/collections/index.ts`.
19
18
  */
20
19
 
21
20
  import { type AdminStore, registerAdminAbilities } from '@byline/admin'
@@ -23,7 +22,6 @@ import { JwtSessionProvider } from '@byline/admin/auth'
23
22
  import { type BylineCore, initBylineCore } from '@byline/core'
24
23
  import { pgAdapter } from '@byline/db-postgres'
25
24
  import { createAdminStore } from '@byline/db-postgres/admin'
26
- import { getAdminBylineClient } from '@byline/host-tanstack-start/integrations/byline-client'
27
25
  import {
28
26
  lexicalEditorEmbedServer,
29
27
  lexicalEditorPopulateServer,
@@ -31,13 +29,13 @@ import {
31
29
  } from '@byline/richtext-lexical/server'
32
30
  import { localStorageProvider } from '@byline/storage-local'
33
31
 
32
+ import { getAdminBylineClient } from './client.server.js'
33
+ import { collections } from './collections/index.js'
34
34
  import { i18n } from './i18n.js'
35
35
  import { DEFAULT_SERVER_URL, routes } from './routes.js'
36
36
 
37
37
  const serverURL = process.env.VITE_SERVER_URL || DEFAULT_SERVER_URL
38
38
 
39
- const collections: Parameters<typeof pgAdapter>[0]['collections'] = []
40
-
41
39
  // HMR-safe singleton. Vite's program reload re-evaluates this module
42
40
  // without disposing the previous module's resources — every reload
43
41
  // would otherwise allocate a fresh pg `Pool` (max: 20) inside
@@ -8,10 +8,11 @@
8
8
 
9
9
  /**
10
10
  * Registers Byline's client-side config (collection admin UI configs,
11
- * field editors, i18n, routes) in the current module graph. Imported as
12
- * a side-effect from `src/routes/_byline/route.lazy.tsx` so registration
13
- * fires only when a `_byline/*` URL matches — keeping the Lexical
14
- * editor module graph out of public-route bundles entirely.
11
+ * field editors, i18n, routes) in the current module graph. The `_byline`
12
+ * route registers it from two complementary points: a dynamic import in
13
+ * `route.tsx` covers child loaders, while the side-effect import in
14
+ * `route.lazy.tsx` covers component render and initial hydration. Keeping
15
+ * both imports behind `_byline/*` keeps the admin graph off public routes.
15
16
  *
16
17
  * In TanStack Start with Vite 6 the server entry (`src/server.ts`) and
17
18
  * the SSR rendering context run in separate Vite environments, so
@@ -28,11 +29,12 @@ import { RichTextField as LexicalRichTextField } from '@byline/richtext-lexical'
28
29
  // commented `richText` block below for the exact shape.
29
30
  // import { lexicalEditor } from '@byline/richtext-lexical'
30
31
 
31
- import { Docs, DocsAdmin } from './collections/docs/index.js'
32
- import { Media, MediaAdmin } from './collections/media/index.js'
33
- import { News, NewsAdmin } from './collections/news/index.js'
34
- import { NewsCategories, NewsCategoriesAdmin } from './collections/news-categories/index.js'
35
- import { Pages, PagesAdmin } from './collections/pages/index.js'
32
+ import { collections } from './collections/index.js'
33
+ import { DocsAdmin } from './collections/docs/admin.js'
34
+ import { MediaAdmin } from './collections/media/admin.js'
35
+ import { NewsAdmin } from './collections/news/admin.js'
36
+ import { NewsCategoriesAdmin } from './collections/news-categories/admin.js'
37
+ import { PagesAdmin } from './collections/pages/admin.js'
36
38
  import { i18n } from './i18n.js'
37
39
  import { DEFAULT_SERVER_URL, routes } from './routes.js'
38
40
 
@@ -42,7 +44,7 @@ export const config: ClientConfig = {
42
44
  serverURL,
43
45
  i18n,
44
46
  routes,
45
- collections: [Docs, News, Pages, Media, NewsCategories],
47
+ collections,
46
48
  admin: [DocsAdmin, NewsAdmin, PagesAdmin, MediaAdmin, NewsCategoriesAdmin],
47
49
  fields: {
48
50
  // Default registration — every `type: 'richText'` field gets the full
@@ -0,0 +1,32 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Server-only application boundary for the host-owned client singletons.
11
+ * The host stays config-agnostic; this assertion binds it to the app registry.
12
+ */
13
+ import type { BylineClient } from '@byline/client'
14
+ import {
15
+ getAdminBylineClient as getHostAdminBylineClient,
16
+ getSystemBylineClient as getHostSystemBylineClient,
17
+ } from '@byline/host-tanstack-start/integrations/byline-client'
18
+ import { getPublicBylineClient as getHostPublicBylineClient } from '@byline/host-tanstack-start/integrations/byline-public-client'
19
+ import {
20
+ getViewerBylineClient as getHostViewerBylineClient,
21
+ isPreviewActive,
22
+ } from '@byline/host-tanstack-start/integrations/byline-viewer-client'
23
+
24
+ import type { BylineCollections } from './collections/index.js'
25
+
26
+ type AppBylineClient = BylineClient<BylineCollections>
27
+
28
+ export const getAdminBylineClient = getHostAdminBylineClient as () => AppBylineClient
29
+ export const getPublicBylineClient = getHostPublicBylineClient as () => AppBylineClient
30
+ export const getSystemBylineClient = getHostSystemBylineClient as () => AppBylineClient
31
+ export const getViewerBylineClient = getHostViewerBylineClient as () => AppBylineClient
32
+ export { isPreviewActive }
@@ -0,0 +1,42 @@
1
+ import type { CollectionFieldData, CollectionFieldDataAllLocales } from '@byline/core'
2
+
3
+ import type { collections } from './collections/index.js'
4
+ import type {
5
+ CollectionFieldsAllLocalesByPath,
6
+ CollectionFieldsByPath,
7
+ } from './generated/collection-types.js'
8
+
9
+ type InferredFieldsByPath = {
10
+ [Definition in (typeof collections)[number] as Definition['path']]: CollectionFieldData<Definition>
11
+ }
12
+
13
+ type InferredFieldsAllLocalesByPath = {
14
+ [Definition in (typeof collections)[number] as Definition['path']]: CollectionFieldDataAllLocales<Definition>
15
+ }
16
+
17
+ type IsAny<Value> = 0 extends 1 & Value ? true : false
18
+ type Exact<Left, Right> = IsAny<Left> extends true
19
+ ? IsAny<Right>
20
+ : IsAny<Right> extends true
21
+ ? false
22
+ : (<Value>() => Value extends Left ? 1 : 2) extends <Value>() =>
23
+ Value extends Right ? 1 : 2
24
+ ? (<Value>() => Value extends Right ? 1 : 2) extends <Value>() =>
25
+ Value extends Left ? 1 : 2
26
+ ? true
27
+ : false
28
+ : false
29
+ type Assert<Value extends true> = Value
30
+
31
+ type GeneratedKeysMatchRuntimeRegistry = Assert<
32
+ Exact<keyof CollectionFieldsByPath, keyof InferredFieldsByPath>
33
+ >
34
+ type GeneratedFieldsMatchRuntimeRegistry = Assert<
35
+ Exact<CollectionFieldsByPath, InferredFieldsByPath>
36
+ >
37
+ type GeneratedAllLocalesKeysMatchRuntimeRegistry = Assert<
38
+ Exact<keyof CollectionFieldsAllLocalesByPath, keyof InferredFieldsAllLocalesByPath>
39
+ >
40
+ type GeneratedAllLocalesFieldsMatchRuntimeRegistry = Assert<
41
+ Exact<CollectionFieldsAllLocalesByPath, InferredFieldsAllLocalesByPath>
42
+ >