rimecms 0.30.3 → 0.31.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 (84) hide show
  1. package/README.md +1 -39
  2. package/dist/.rime-modules.d.ts +23 -0
  3. package/dist/.rime-modules.json +1 -0
  4. package/dist/adapter-sqlite/generate-schema/write.server.js +6 -6
  5. package/dist/core/areas/config/builder.server.d.ts +0 -1
  6. package/dist/core/collections/auth/hooks/before-upsert/augment-fields-password.server.d.ts +1 -1
  7. package/dist/core/collections/config/builder.server.d.ts +0 -1
  8. package/dist/core/collections/upload/hooks/convert-base64.server.d.ts +1 -1
  9. package/dist/core/collections/upload/hooks/extract-path.server.d.ts +1 -1
  10. package/dist/core/collections/upload/hooks/handle-path-creation.server.d.ts +1 -1
  11. package/dist/core/collections/upload/hooks/process-file-upload.server.d.ts +1 -1
  12. package/dist/core/config/auth/better-auth.server.d.ts +3 -3
  13. package/dist/core/config/client/augment-plugins.d.ts +1 -5
  14. package/dist/core/config/client/augment-plugins.js +3 -3
  15. package/dist/core/config/client/build-config.d.ts +1 -5
  16. package/dist/core/config/config-context.server.d.ts +1 -1
  17. package/dist/core/config/server/augment-plugins.server.d.ts +2 -42
  18. package/dist/core/config/server/augment-plugins.server.js +11 -6
  19. package/dist/core/config/server/build-config.server.d.ts +9 -4007
  20. package/dist/core/config/types.d.ts +12 -11
  21. package/dist/core/dev/cache/index.server.js +1 -1
  22. package/dist/core/dev/cli/clear/index.server.js +2 -0
  23. package/dist/core/dev/cli/generate-manifest/index.server.d.ts +17 -0
  24. package/dist/core/dev/cli/generate-manifest/index.server.js +221 -0
  25. package/dist/core/dev/cli/index.js +42 -0
  26. package/dist/core/dev/cli/init/index.server.js +11 -4
  27. package/dist/core/dev/cli/init/templates.d.ts +1 -1
  28. package/dist/core/dev/cli/init/templates.js +5 -3
  29. package/dist/core/dev/cli/util/package-manager.server.js +5 -3
  30. package/dist/core/dev/constants.d.ts +10 -0
  31. package/dist/core/dev/constants.js +15 -0
  32. package/dist/core/dev/generate/routes/common.server.js +4 -7
  33. package/dist/core/dev/generate/routes/index.server.js +25 -4
  34. package/dist/core/dev/generate/runtime/index.server.d.ts +31 -15
  35. package/dist/core/dev/generate/runtime/index.server.js +45 -53
  36. package/dist/core/dev/generate/runtime/parse-exports.server.d.ts +7 -0
  37. package/dist/core/dev/generate/runtime/parse-exports.server.js +40 -0
  38. package/dist/core/dev/generate/sanitize/index.server.d.ts +0 -7
  39. package/dist/core/dev/generate/sanitize/index.server.js +57 -5
  40. package/dist/core/dev/generate/types/index.server.js +16 -39
  41. package/dist/core/dev/vite/index.server.js +217 -39
  42. package/dist/core/ensure.server.d.ts +4 -3
  43. package/dist/core/ensure.server.js +8 -8
  44. package/dist/core/handlers/index.js +2 -2
  45. package/dist/core/operations/hooks/before-upsert/data-config-map.server.d.ts +1 -1
  46. package/dist/core/operations/hooks/before-upsert/handle-new-version.server.d.ts +1 -1
  47. package/dist/core/operations/hooks/before-upsert/original-config-map.server.d.ts +1 -1
  48. package/dist/core/operations/hooks/before-upsert/set-default-values.server.d.ts +1 -1
  49. package/dist/core/operations/hooks/before-upsert/validate-fields.server.d.ts +1 -1
  50. package/dist/core/operations/hooks/index.server.d.ts +0 -4
  51. package/dist/core/operations/hooks/index.server.js +0 -4
  52. package/dist/core/plugins/api-init/index.d.ts +2 -0
  53. package/dist/core/plugins/api-init/index.js +1 -0
  54. package/dist/core/plugins/api-init/{index.server.d.ts → module.server.d.ts} +0 -1
  55. package/dist/core/plugins/api-init/{index.server.js → module.server.js} +0 -1
  56. package/dist/core/plugins/cache/index.d.ts +2 -5
  57. package/dist/core/plugins/cache/index.js +1 -21
  58. package/dist/core/plugins/cache/module.d.ts +4 -0
  59. package/dist/core/plugins/cache/module.js +22 -0
  60. package/dist/core/plugins/cache/{index.server.d.ts → module.server.d.ts} +1 -2
  61. package/dist/core/plugins/cache/{index.server.js → module.server.js} +0 -1
  62. package/dist/core/plugins/index.d.ts +1 -8
  63. package/dist/core/plugins/index.js +0 -3
  64. package/dist/core/plugins/mailer/index.d.ts +2 -0
  65. package/dist/core/plugins/mailer/index.js +1 -0
  66. package/dist/core/plugins/mailer/{index.server.d.ts → module.server.d.ts} +0 -1
  67. package/dist/core/plugins/mailer/{index.server.js → module.server.js} +0 -1
  68. package/dist/core/plugins/sse/index.d.ts +2 -0
  69. package/dist/core/plugins/sse/index.js +1 -0
  70. package/dist/core/plugins/sse/{index.server.d.ts → module.server.d.ts} +0 -1
  71. package/dist/core/plugins/sse/{index.server.js → module.server.js} +0 -1
  72. package/dist/core/rime.server.d.ts +16 -16
  73. package/dist/fields/link/index.js +59 -56
  74. package/dist/fields/relation/index.js +63 -70
  75. package/dist/index.d.ts +3 -3
  76. package/dist/index.js +3 -3
  77. package/dist/panel/context/config.svelte.d.ts +2 -2
  78. package/dist/panel/context/documentForm.svelte.d.ts +2 -2
  79. package/dist/panel/context/live.svelte.js +1 -1
  80. package/dist/panel/pages/auth/sign-in/SignIn.svelte +6 -1
  81. package/dist/server.d.ts +4 -4
  82. package/dist/server.js +4 -4
  83. package/dist/types.d.ts +1 -1
  84. package/package.json +2 -2
@@ -4,21 +4,37 @@ export type RuntimeRegistryEntry = {
4
4
  };
5
5
  export type RuntimeRegistry = Map<string, RuntimeRegistryEntry>;
6
6
  /**
7
- * Builds the `$rime/<name>` → { browser file, server file } lookup table.
7
+ * Finds an installed package's root by walking up from `fromDir`, checking each ancestor's
8
+ * `node_modules/<pkgName>` directly for a `package.json` — the pre-`exports` Node resolution
9
+ * algorithm, pure directory existence, no `exports`-conditions matching involved at all.
8
10
  *
9
- * @example rime's own field, installed as a dependency in some app
10
- * // /my-app/node_modules/rimecms/dist/fields/relation/module.ts + module.server.ts
11
- * // → registry.get('fields/relation') → { client: '.../module.ts', server: '.../module.server.ts' }
12
- * // → import { x } from '$rime/fields/relation' // resolves
13
- *
14
- * @example that same app's own custom field
15
- * // /my-app/src/lib/fields/geocode/module.ts + module.server.ts
16
- * // → registry.get('fields/geocode') → { client: '.../module.ts', server: '.../module.server.ts' }
17
- * // → import { x } from '$rime/fields/geocode' // resolves, same mechanism, zero extra setup
11
+ * Needed in place of `require.resolve`/`import.meta.resolve`: both gate on a package's own
12
+ * `exports` map matching some condition set (`"require"`/`"default"` for the former, whatever
13
+ * conditions the resolving runtime declares for the latter), and a package that only exposes
14
+ * conditions neither uses — confirmed in practice with rime's own `"."` export, `types`/
15
+ * `svelte`/`import` only, no `"require"`/`"default"` fallback — fails to resolve even its own
16
+ * main entry through either API. This sidesteps that entirely: `node_modules/<pkgName>` is a
17
+ * real directory (or, under pnpm, a real symlink `fs.existsSync` follows transparently)
18
+ * regardless of what that package's `exports` map declares.
19
+ */
20
+ export declare function findInstalledPackageRoot(pkgName: string, fromDir: string): string;
21
+ /**
22
+ * Direct, single-lookup check for one `<root>/<name>/module(.server)?.ts|js` pair — no
23
+ * directory walk, just the two `fs.existsSync`-style checks `findModuleFile` already does.
24
+ * Used by the Vite plugin's `resolveId`/`load` for on-demand resolution of a `$rime/modules`
25
+ * self-reference; `scanModulePairs` below (a real walk) stays reserved for the barrel (which
26
+ * needs every pair at once) and type generation.
27
+ */
28
+ export declare function findModulePair(root: string, name: string): RuntimeRegistryEntry | null;
29
+ /**
30
+ * Recursively finds every `module`/`module.server` folder under `root`, registered under the
31
+ * containing folder's path relative to `root` — e.g. `fields/relation/module.ts` under a
32
+ * `src/lib`-rooted scan registers as `fields/relation`. Used by the `$rime/modules` barrel
33
+ * (live, dev-mode only) and by `generate-manifest` (once, at prepack, scanning `dist/`).
18
34
  *
19
- * @example nothing on disk for that name
20
- * // no folder anywhere has both typo/module.ts and typo/module.server.ts
21
- * // → registry.get('typo') → undefined
22
- * // → import { x } from '$rime/typo' // fails: "cannot resolve module"
35
+ * Either file alone is enough to register (single-sided is legitimate — a server-only piece,
36
+ * e.g. a collection's hooks, never needs a hand-written client stub; the missing side is just
37
+ * an empty string here, same convention `findModulePair` above uses). `rime.generated/` is
38
+ * skipped since it's sanitize's own output, unrelated to this.
23
39
  */
24
- export declare function buildRuntimeRegistry(): RuntimeRegistry;
40
+ export declare function scanModulePairs(root: string): RuntimeRegistry;
@@ -1,23 +1,33 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import { isInstalledDependency, OUTPUT_DIR } from '../../constants.js';
3
+ import { OUTPUT_DIR } from '../../constants.js';
5
4
  /**
6
- * Walks up from `startDir` to the nearest ancestor containing a `package.json`, then returns
7
- * its `dist/` — self-correcting regardless of how deeply nested this file is (unlike counting
8
- * a fixed number of `..` segments, which would silently point at the wrong directory, not
9
- * error, the moment this file's own location changes).
5
+ * Finds an installed package's root by walking up from `fromDir`, checking each ancestor's
6
+ * `node_modules/<pkgName>` directly for a `package.json` — the pre-`exports` Node resolution
7
+ * algorithm, pure directory existence, no `exports`-conditions matching involved at all.
8
+ *
9
+ * Needed in place of `require.resolve`/`import.meta.resolve`: both gate on a package's own
10
+ * `exports` map matching some condition set (`"require"`/`"default"` for the former, whatever
11
+ * conditions the resolving runtime declares for the latter), and a package that only exposes
12
+ * conditions neither uses — confirmed in practice with rime's own `"."` export, `types`/
13
+ * `svelte`/`import` only, no `"require"`/`"default"` fallback — fails to resolve even its own
14
+ * main entry through either API. This sidesteps that entirely: `node_modules/<pkgName>` is a
15
+ * real directory (or, under pnpm, a real symlink `fs.existsSync` follows transparently)
16
+ * regardless of what that package's `exports` map declares.
10
17
  */
11
- function findDistRoot(startDir) {
12
- let dir = startDir;
13
- while (!fs.existsSync(path.join(dir, 'package.json'))) {
18
+ export function findInstalledPackageRoot(pkgName, fromDir) {
19
+ let dir = fromDir;
20
+ while (true) {
21
+ const candidate = path.join(dir, 'node_modules', pkgName);
22
+ if (fs.existsSync(path.join(candidate, 'package.json'))) {
23
+ return candidate;
24
+ }
14
25
  const parent = path.dirname(dir);
15
26
  if (parent === dir) {
16
- throw new Error(`$rime: could not locate rimecms's package root above ${startDir}`);
27
+ throw new Error(`$rime/modules: could not find installed package '${pkgName}' above ${fromDir}`);
17
28
  }
18
29
  dir = parent;
19
30
  }
20
- return path.join(dir, 'dist');
21
31
  }
22
32
  /** `module.ts` when scanning TS source, `module.js` when scanning a built `dist/`. */
23
33
  function findModuleFile(dir, baseName) {
@@ -29,16 +39,30 @@ function findModuleFile(dir, baseName) {
29
39
  return null;
30
40
  }
31
41
  /**
32
- * Recursively finds every `module`/`module.server` pair under `root`, registered under the
33
- * containing folder's path relative to `root` — `$rime/<key>` mirrors the real path, e.g.
34
- * `fields/relation/module.ts` under a `lib`-rooted scan registers as `fields/relation`.
42
+ * Direct, single-lookup check for one `<root>/<name>/module(.server)?.ts|js` pair — no
43
+ * directory walk, just the two `fs.existsSync`-style checks `findModuleFile` already does.
44
+ * Used by the Vite plugin's `resolveId`/`load` for on-demand resolution of a `$rime/modules`
45
+ * self-reference; `scanModulePairs` below (a real walk) stays reserved for the barrel (which
46
+ * needs every pair at once) and type generation.
47
+ */
48
+ export function findModulePair(root, name) {
49
+ const dir = path.join(root, name);
50
+ const client = findModuleFile(dir, 'module');
51
+ const server = findModuleFile(dir, 'module.server');
52
+ return client || server ? { client: client ?? '', server: server ?? '' } : null;
53
+ }
54
+ /**
55
+ * Recursively finds every `module`/`module.server` folder under `root`, registered under the
56
+ * containing folder's path relative to `root` — e.g. `fields/relation/module.ts` under a
57
+ * `src/lib`-rooted scan registers as `fields/relation`. Used by the `$rime/modules` barrel
58
+ * (live, dev-mode only) and by `generate-manifest` (once, at prepack, scanning `dist/`).
35
59
  *
36
- * Both files are required to register a `$rime/<key>` entry: this pair only exists for a field
37
- * whose hook needs different server/client behavior (relation, link) — a field with nothing to
38
- * split just overrides `generateType()` on its builder directly and never needs either file.
39
- * `+rime.generated/` is skipped since it's sanitize's own output, unrelated to this.
60
+ * Either file alone is enough to register (single-sided is legitimate — a server-only piece,
61
+ * e.g. a collection's hooks, never needs a hand-written client stub; the missing side is just
62
+ * an empty string here, same convention `findModulePair` above uses). `rime.generated/` is
63
+ * skipped since it's sanitize's own output, unrelated to this.
40
64
  */
41
- function scanModulePairs(root) {
65
+ export function scanModulePairs(root) {
42
66
  const registry = new Map();
43
67
  if (!fs.existsSync(root))
44
68
  return registry;
@@ -49,9 +73,9 @@ function scanModulePairs(root) {
49
73
  const fullPath = path.join(dir, entry.name);
50
74
  const client = findModuleFile(fullPath, 'module');
51
75
  const server = findModuleFile(fullPath, 'module.server');
52
- if (client && server) {
76
+ if (client || server) {
53
77
  const key = path.relative(root, fullPath).split(path.sep).join('/');
54
- registry.set(key, { client, server });
78
+ registry.set(key, { client: client ?? '', server: server ?? '' });
55
79
  }
56
80
  walk(fullPath);
57
81
  }
@@ -59,35 +83,3 @@ function scanModulePairs(root) {
59
83
  walk(root);
60
84
  return registry;
61
85
  }
62
- /**
63
- * Builds the `$rime/<name>` → { browser file, server file } lookup table.
64
- *
65
- * @example rime's own field, installed as a dependency in some app
66
- * // /my-app/node_modules/rimecms/dist/fields/relation/module.ts + module.server.ts
67
- * // → registry.get('fields/relation') → { client: '.../module.ts', server: '.../module.server.ts' }
68
- * // → import { x } from '$rime/fields/relation' // resolves
69
- *
70
- * @example that same app's own custom field
71
- * // /my-app/src/lib/fields/geocode/module.ts + module.server.ts
72
- * // → registry.get('fields/geocode') → { client: '.../module.ts', server: '.../module.server.ts' }
73
- * // → import { x } from '$rime/fields/geocode' // resolves, same mechanism, zero extra setup
74
- *
75
- * @example nothing on disk for that name
76
- * // no folder anywhere has both typo/module.ts and typo/module.server.ts
77
- * // → registry.get('typo') → undefined
78
- * // → import { x } from '$rime/typo' // fails: "cannot resolve module"
79
- */
80
- export function buildRuntimeRegistry() {
81
- const registry = new Map();
82
- if (isInstalledDependency(import.meta.url)) {
83
- const nativeLibDir = findDistRoot(path.dirname(fileURLToPath(import.meta.url)));
84
- for (const [key, entry] of scanModulePairs(nativeLibDir)) {
85
- registry.set(key, entry);
86
- }
87
- }
88
- const consumerLibDir = path.resolve(process.cwd(), 'src/lib');
89
- for (const [key, entry] of scanModulePairs(consumerLibDir)) {
90
- registry.set(key, entry);
91
- }
92
- return registry;
93
- }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Top-level named + default export identifiers of a module — used by the Vite plugin's
3
+ * `exportFrom()` to stub out a side of a `$rime/<name>` pair that wasn't authored (e.g. a
4
+ * collection's server-only hook, requested by a client build), matching the real side's
5
+ * export names so ESM's static named-export binding doesn't throw at link time.
6
+ */
7
+ export declare function parseExportNames(filePath: string): string[];
@@ -0,0 +1,40 @@
1
+ import { babelParse } from 'ast-kit';
2
+ import * as t from '@babel/types';
3
+ import fs from 'node:fs';
4
+ /**
5
+ * Top-level named + default export identifiers of a module — used by the Vite plugin's
6
+ * `exportFrom()` to stub out a side of a `$rime/<name>` pair that wasn't authored (e.g. a
7
+ * collection's server-only hook, requested by a client build), matching the real side's
8
+ * export names so ESM's static named-export binding doesn't throw at link time.
9
+ */
10
+ export function parseExportNames(filePath) {
11
+ const content = fs.readFileSync(filePath, 'utf-8');
12
+ const ast = babelParse(content, 'ts', { sourceType: 'module', attachComment: false });
13
+ const names = [];
14
+ for (const node of ast.body) {
15
+ if (t.isExportNamedDeclaration(node)) {
16
+ if (node.declaration) {
17
+ if (t.isVariableDeclaration(node.declaration)) {
18
+ for (const decl of node.declaration.declarations) {
19
+ if (t.isIdentifier(decl.id))
20
+ names.push(decl.id.name);
21
+ }
22
+ }
23
+ else if ('id' in node.declaration &&
24
+ node.declaration.id &&
25
+ t.isIdentifier(node.declaration.id)) {
26
+ names.push(node.declaration.id.name);
27
+ }
28
+ }
29
+ for (const spec of node.specifiers) {
30
+ if (t.isExportSpecifier(spec) && t.isIdentifier(spec.exported)) {
31
+ names.push(spec.exported.name);
32
+ }
33
+ }
34
+ }
35
+ if (t.isExportDefaultDeclaration(node)) {
36
+ names.push('default');
37
+ }
38
+ }
39
+ return names;
40
+ }
@@ -1,10 +1,3 @@
1
- /**
2
- * $ prefix-based sanitizer that rules:
3
- * - All server-only config properties begin with $ (e.g., $url, $hooks)
4
- * - All server-only field methods begin with $ (e.g., $beforeSave, $beforeRead)
5
- * - Content must be pure functions or imports from .server.ts files
6
- * - Outputs to .generated folder, preserving original folder structure
7
- */
8
1
  export function sanitize(): Promise<void>;
9
2
  /**
10
3
  * Updates imports in server files to use server versions of split modules
@@ -15,6 +15,11 @@ import { INPUT_DIR, OUTPUT_DIR } from '../../constants.js';
15
15
  * - Outputs to .generated folder, preserving original folder structure
16
16
  */
17
17
 
18
+ // The top-level config — real, hand-authored. Unlike every other .server.ts file under
19
+ // +rime/ (copied verbatim, server-only, no client counterpart needed), this one always needs
20
+ // a sanitized client copy too — the panel still needs +rime.generated/rime.config.ts.
21
+ const TOP_LEVEL_CONFIG = 'rime.config.server.ts';
22
+
18
23
  export async function sanitize() {
19
24
  const root = process.cwd();
20
25
  const configDir = path.resolve(root, 'src/lib/', INPUT_DIR);
@@ -66,12 +71,22 @@ export async function sanitize() {
66
71
  }
67
72
  }
68
73
 
69
- // Also scan and copy all existing .server.ts files
70
- const serverFiles = await scanServerFiles(configDir);
74
+ // Also scan and copy all existing .server.ts files — except the top-level config, which
75
+ // gets its own client+server split below instead of a plain verbatim copy.
76
+ const serverFiles = (await scanServerFiles(configDir)).filter(
77
+ (filePath) => path.relative(configDir, filePath) !== TOP_LEVEL_CONFIG
78
+ );
71
79
 
72
80
  // Scan and copy all other files (non-.ts files like .svelte, .json, etc.)
73
81
  const otherFiles = await scanOtherFiles(configDir);
74
82
 
83
+ // Top-level config split: same client/server treatment as every other split file below,
84
+ // just addressed by its fixed name instead of found via the scan (it's .server.ts-suffixed,
85
+ // so scanConfigFiles/allFiles never sees it). Runs after the pre-scan above so splitFiles is
86
+ // fully populated — this file's own `import { Pages } from './pages'`-style imports need to
87
+ // know which nested files themselves got split.
88
+ await processTopLevelConfig(configDir, outputDir, outputFiles, splitFiles);
89
+
75
90
  for (const filePath of allFiles) {
76
91
  const relativePath = path.relative(configDir, filePath);
77
92
  logger.debug(` Processing: ${relativePath}`);
@@ -130,10 +145,13 @@ export async function sanitize() {
130
145
  }
131
146
  }
132
147
 
133
- // Delete files that exist in current list but not in output list
148
+ // Delete files that exist in current list but not in output list. schema.server.ts is
149
+ // excluded — write.server.ts owns it (writes it straight into OUTPUT_DIR, see schemaPath()
150
+ // in constants.ts), sanitize() never adds it to outputFiles, so without this exclusion
151
+ // every sanitize run deletes it out from under write()'s own cache guard.
152
+ const SCHEMA_FILE = 'schema.server.ts';
134
153
  for (const existingFile of existingFiles) {
135
- // Do not delete "schema.server.ts"
136
- if (!outputFiles.has(existingFile) && existingFile !== 'schema.server.ts') {
154
+ if (existingFile !== SCHEMA_FILE && !outputFiles.has(existingFile)) {
137
155
  const fileToDelete = path.join(outputDir, existingFile);
138
156
  fs.unlinkSync(fileToDelete);
139
157
  logger.debug(` Deleted: ${existingFile}`);
@@ -143,6 +161,40 @@ export async function sanitize() {
143
161
  logger.info('Sanitization complete');
144
162
  }
145
163
 
164
+ /**
165
+ * The top-level config (src/lib/+rime/rime.config.server.ts) — real, hand-authored. Client
166
+ * copy via analyzeFile + sanitizeClientAst + removeUnusedImports, written into
167
+ * +rime.generated/, plus a server copy via updateServerImports so its own imports of nested
168
+ * split files resolve like every other split file's do.
169
+ */
170
+ async function processTopLevelConfig(configDir, outputDir, outputFiles, splitFiles) {
171
+ const sourcePath = path.join(configDir, TOP_LEVEL_CONFIG);
172
+ if (!fs.existsSync(sourcePath)) return; // ensureUserConfigExist() already guards this
173
+
174
+ const content = fs.readFileSync(sourcePath, 'utf-8');
175
+ const ast = babelParse(content, 'ts', { sourceType: 'module', attachComment: false });
176
+ const analysis = analyzeFile(ast);
177
+
178
+ const clientPath = path.join(outputDir, 'rime.config.ts');
179
+ const serverPath = path.join(outputDir, TOP_LEVEL_CONFIG);
180
+ outputFiles.add(path.relative(outputDir, clientPath));
181
+ outputFiles.add(path.relative(outputDir, serverPath));
182
+
183
+ const clientAst = sanitizeClientAst(ast, analysis);
184
+ const cleanedAst = removeUnusedImports(clientAst);
185
+ const clientCode = generate(cleanedAst, { compact: false, comments: true }).code;
186
+ if (shouldWriteFile(clientPath, clientCode)) {
187
+ fs.writeFileSync(clientPath, clientCode);
188
+ logger.debug(` Sanitized: rime.config.ts`);
189
+ }
190
+
191
+ const serverContent = updateServerImports(content, splitFiles, '.');
192
+ if (shouldWriteFile(serverPath, serverContent)) {
193
+ fs.writeFileSync(serverPath, serverContent);
194
+ logger.debug(` Created: ${TOP_LEVEL_CONFIG}`);
195
+ }
196
+ }
197
+
146
198
  async function scanConfigFiles(configDir) {
147
199
  const files = [];
148
200
 
@@ -13,7 +13,6 @@ import { capitalize, toPascalCase } from '../../../../util/string.js';
13
13
  import fs from 'node:fs';
14
14
  import path from 'node:path';
15
15
  import { isInstalledDependency, OUTPUT_DIR } from '../../constants.js';
16
- import { buildRuntimeRegistry } from '../runtime/index.server.js';
17
16
  const IS_RIME_REPO = !isInstalledDependency(import.meta.url);
18
17
  // Relation type
19
18
  const relationValueType = `
@@ -114,43 +113,20 @@ const templateDeclareVirtualModule = () => [
114
113
  `\texport * from '${PACKAGE_NAME}/config/server';`,
115
114
  `}`,
116
115
  `declare module '$rime/schema' {`,
117
- `\texport * from '$lib/+rime.generated/schema.server.js';`,
118
- `}`,
119
- ...templateDeclareFieldModules()
116
+ `\texport * from '$lib/rime.schema.server.js';`,
117
+ `}`
118
+ // $rime/modules (the bare barrel) is typed by src/rime.modules.generated.d.ts instead —
119
+ // written directly by the Vite plugin's own dev-server watcher (regenerateModulesDeclaration
120
+ // in core/dev/vite/index.server.ts), not here, since it needs to react to module.(server.)ts
121
+ // files appearing/changing on their own, independent of a config regen.
122
+ //
123
+ // A package's own qualified $rime/modules/<pkg>/<path> references are self-contained —
124
+ // generate-manifest inserts a /// <reference> directly into each rewritten .d.ts file at
125
+ // prepack (see generate-manifest/index.server.ts), so this app never needs to know which
126
+ // installed packages exist or eagerly reference all of them; verified directly (a real tsc
127
+ // run resolves it correctly from a file two levels deep inside node_modules, zero consumer
128
+ // awareness needed).
120
129
  ].join('\n');
121
- /**
122
- * One `declare module '$rime/<name>'` per registered field module.ts + module.server.ts pair
123
- * (rime-native and consumer-local alike — see buildRuntimeRegistry), sourced from the
124
- * `.server.ts` side (the real, server-only implementation of the shared hook; the client
125
- * module.ts only has the no-op). Unrelated to type generation itself — that goes through
126
- * `field.use.generateType()` on the builder instance directly, not through this registry.
127
- * Runtime resolution (the Vite plugin's `load()`) is unaffected either way — it still picks
128
- * the real client-vs-server module dynamically based on build context.
129
- */
130
- const templateDeclareFieldModules = () => {
131
- const registry = buildRuntimeRegistry();
132
- return Array.from(registry.entries()).map(([name, entry]) => {
133
- const modulePath = toModuleSpecifier(entry.server);
134
- return `declare module '$rime/${name}' {\n\texport * from '${modulePath}';\n}`;
135
- });
136
- };
137
- /**
138
- * `$lib/...` when the file is inside this app's own src/lib (true for a consumer's own custom
139
- * field, and for rime's own fields when developing rime itself, since they live inside src/lib
140
- * in that case too) — matches the `$rime/schema` declaration above, and stays readable/portable
141
- * instead of an absolute path. Falls back to an absolute path only when there's genuinely no
142
- * alias for it: rime's own fields when installed as a real dependency, living in
143
- * node_modules/rimecms/dist/, outside this app's src/lib entirely.
144
- */
145
- function toModuleSpecifier(serverPath) {
146
- const libRoot = path.resolve(process.cwd(), 'src/lib');
147
- const jsPath = serverPath.replace(/\.ts$/, '.js');
148
- const relative = path.relative(libRoot, jsPath);
149
- if (!relative.startsWith('..')) {
150
- return `$lib/${relative.split(path.sep).join('/')}`;
151
- }
152
- return jsPath;
153
- }
154
130
  /**
155
131
  * Generates type definitions for image sizes
156
132
  * @returns A string containing the type definition for image sizes
@@ -283,6 +259,7 @@ export async function generateTypesString(config) {
283
259
  const blocksTypeNames = `export type BlockTypes = ${registeredBlocks.map((name) => `'${name}'`).join('|')}\n`;
284
260
  const anyBlock = `export type AnyBlock = ${registeredBlocks.map((name) => `Block${toPascalCase(name)}`).join('|')}\n`;
285
261
  const typeImports = `import type { ${Array.from(imports).join(', ')} } from '${PACKAGE_NAME}/types'`;
262
+ const rimeConfigServerPath = `./lib/${OUTPUT_DIR}/rime.config.server.ts`;
286
263
  const locals = `declare global {
287
264
  namespace App {
288
265
  interface Locals {
@@ -316,7 +293,7 @@ export async function generateTypesString(config) {
316
293
  /** Singleton providing access to auth, config and local-api */
317
294
  rime: ReturnType<
318
295
  Awaited<
319
- typeof import('./lib/${OUTPUT_DIR}/rime.config.server.ts').default
296
+ typeof import('${rimeConfigServerPath}').default
320
297
  >['createRimeContext']
321
298
  >;
322
299
  /** Flag enabled by the core plugin rime.cache when the API cache is ON */
@@ -381,7 +358,7 @@ function write(key, content, filePath) {
381
358
  */
382
359
  async function generateTypes(config) {
383
360
  const mainTypes = await generateTypesString(config);
384
- const declarations = [templateDeclareVirtualModule()].join('\n');
361
+ const declarations = templateDeclareVirtualModule();
385
362
  const appGeneratedPath = path.resolve(process.cwd(), 'src', 'app.generated.d.ts');
386
363
  const virtualModuleGeneratedPath = path.resolve(process.cwd(), 'src', 'rime.generated.d.ts');
387
364
  write('app.generated', mainTypes, appGeneratedPath);