rimecms 0.30.2 → 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 (191) hide show
  1. package/README.md +2 -40
  2. package/dist/.rime-modules.d.ts +23 -0
  3. package/dist/.rime-modules.json +1 -0
  4. package/dist/adapter-sqlite/generate-schema/column.server.js +1 -4
  5. package/dist/adapter-sqlite/generate-schema/write.server.js +7 -9
  6. package/dist/core/areas/config/builder.server.d.ts +0 -1
  7. package/dist/core/collections/auth/hooks/before-upsert/augment-fields-password.server.d.ts +1 -1
  8. package/dist/core/collections/config/builder.server.d.ts +0 -1
  9. package/dist/core/collections/upload/hooks/convert-base64.server.d.ts +1 -1
  10. package/dist/core/collections/upload/hooks/extract-path.server.d.ts +1 -1
  11. package/dist/core/collections/upload/hooks/handle-path-creation.server.d.ts +1 -1
  12. package/dist/core/collections/upload/hooks/process-file-upload.server.d.ts +1 -1
  13. package/dist/core/config/auth/better-auth-hooks.server.js +6 -3
  14. package/dist/core/config/auth/better-auth.server.d.ts +3 -3
  15. package/dist/core/config/client/augment-plugins.d.ts +1 -5
  16. package/dist/core/config/client/augment-plugins.js +3 -3
  17. package/dist/core/config/client/build-config.d.ts +1 -5
  18. package/dist/core/config/config-context.server.d.ts +1 -1
  19. package/dist/core/config/server/augment-plugins.server.d.ts +2 -42
  20. package/dist/core/config/server/augment-plugins.server.js +11 -6
  21. package/dist/core/config/server/build-config.server.d.ts +9 -4007
  22. package/dist/core/config/types.d.ts +12 -11
  23. package/dist/core/dev/cache/index.server.js +1 -1
  24. package/dist/core/dev/cli/build/index.d.ts +1 -0
  25. package/dist/core/dev/cli/build/index.js +10 -5
  26. package/dist/core/dev/cli/build/templates.d.ts +1 -1
  27. package/dist/core/dev/cli/build/templates.js +10 -7
  28. package/dist/core/dev/cli/clear/index.server.js +2 -0
  29. package/dist/core/dev/cli/generate-manifest/index.server.d.ts +17 -0
  30. package/dist/core/dev/cli/generate-manifest/index.server.js +221 -0
  31. package/dist/core/dev/cli/index.js +43 -0
  32. package/dist/core/dev/cli/init/index.server.js +13 -6
  33. package/dist/core/dev/cli/init/templates.d.ts +1 -1
  34. package/dist/core/dev/cli/init/templates.js +13 -4
  35. package/dist/core/dev/cli/util/package-manager.server.js +29 -26
  36. package/dist/core/dev/constants.d.ts +21 -2
  37. package/dist/core/dev/constants.js +26 -0
  38. package/dist/core/dev/generate/routes/common.server.js +4 -7
  39. package/dist/core/dev/generate/routes/index.server.js +25 -4
  40. package/dist/core/dev/generate/runtime/index.server.d.ts +40 -0
  41. package/dist/core/dev/generate/runtime/index.server.js +85 -0
  42. package/dist/core/dev/generate/runtime/parse-exports.server.d.ts +7 -0
  43. package/dist/core/dev/generate/runtime/parse-exports.server.js +40 -0
  44. package/dist/core/dev/generate/sanitize/index.server.d.ts +0 -7
  45. package/dist/core/dev/generate/sanitize/index.server.js +57 -5
  46. package/dist/core/dev/generate/types/index.server.js +21 -20
  47. package/dist/core/dev/vite/index.server.js +227 -33
  48. package/dist/core/ensure.server.d.ts +4 -3
  49. package/dist/core/ensure.server.js +8 -8
  50. package/dist/core/fields/builders/boolean-builder.d.ts +1 -2
  51. package/dist/core/fields/builders/field-builder.d.ts +11 -6
  52. package/dist/core/fields/builders/field-builder.js +12 -5
  53. package/dist/core/fields/builders/form-field-builder.d.ts +11 -14
  54. package/dist/core/fields/builders/form-field-builder.js +17 -15
  55. package/dist/core/fields/util.js +1 -1
  56. package/dist/core/handlers/index.js +2 -2
  57. package/dist/core/operations/blocks/extract.server.js +1 -1
  58. package/dist/core/operations/hooks/before-read/process-document-fields.server.js +3 -3
  59. package/dist/core/operations/hooks/before-upsert/data-config-map.server.d.ts +1 -1
  60. package/dist/core/operations/hooks/before-upsert/handle-new-version.server.d.ts +1 -1
  61. package/dist/core/operations/hooks/before-upsert/original-config-map.server.d.ts +1 -1
  62. package/dist/core/operations/hooks/before-upsert/set-default-values.server.d.ts +1 -1
  63. package/dist/core/operations/hooks/before-upsert/set-default-values.server.js +3 -3
  64. package/dist/core/operations/hooks/before-upsert/validate-fields.server.d.ts +1 -1
  65. package/dist/core/operations/hooks/before-upsert/validate-fields.server.js +7 -7
  66. package/dist/core/operations/hooks/index.server.d.ts +0 -4
  67. package/dist/core/operations/hooks/index.server.js +0 -4
  68. package/dist/core/operations/shared/fallback-data-from-original.js +1 -1
  69. package/dist/core/operations/tree/extract.server.js +1 -1
  70. package/dist/core/plugins/api-init/index.d.ts +2 -0
  71. package/dist/core/plugins/api-init/index.js +1 -0
  72. package/dist/core/plugins/api-init/{index.server.d.ts → module.server.d.ts} +0 -1
  73. package/dist/core/plugins/api-init/{index.server.js → module.server.js} +2 -3
  74. package/dist/core/plugins/cache/index.d.ts +2 -5
  75. package/dist/core/plugins/cache/index.js +1 -21
  76. package/dist/core/plugins/cache/module.d.ts +4 -0
  77. package/dist/core/plugins/cache/module.js +22 -0
  78. package/dist/core/plugins/cache/{index.server.d.ts → module.server.d.ts} +1 -2
  79. package/dist/core/plugins/cache/{index.server.js → module.server.js} +0 -1
  80. package/dist/core/plugins/index.d.ts +1 -8
  81. package/dist/core/plugins/index.js +0 -3
  82. package/dist/core/plugins/mailer/index.d.ts +2 -0
  83. package/dist/core/plugins/mailer/index.js +1 -0
  84. package/dist/core/plugins/mailer/{index.server.d.ts → module.server.d.ts} +0 -1
  85. package/dist/core/plugins/mailer/{index.server.js → module.server.js} +0 -1
  86. package/dist/core/plugins/sse/index.d.ts +2 -0
  87. package/dist/core/plugins/sse/index.js +1 -0
  88. package/dist/core/plugins/sse/{index.server.d.ts → module.server.d.ts} +0 -1
  89. package/dist/core/plugins/sse/{index.server.js → module.server.js} +0 -1
  90. package/dist/core/rime.server.d.ts +16 -16
  91. package/dist/fields/blocks/index.d.ts +1 -1
  92. package/dist/fields/blocks/index.js +5 -2
  93. package/dist/fields/checkbox/index.d.ts +1 -1
  94. package/dist/fields/checkbox/index.js +3 -2
  95. package/dist/fields/combobox/index.d.ts +1 -1
  96. package/dist/fields/combobox/index.js +4 -2
  97. package/dist/fields/date/index.d.ts +2 -2
  98. package/dist/fields/date/index.js +7 -5
  99. package/dist/fields/email/index.d.ts +3 -3
  100. package/dist/fields/email/index.js +9 -8
  101. package/dist/fields/group/component/Group.svelte +1 -1
  102. package/dist/fields/group/index.d.ts +1 -1
  103. package/dist/fields/group/index.js +4 -2
  104. package/dist/fields/link/index.d.ts +1 -1
  105. package/dist/fields/link/index.js +63 -56
  106. package/dist/fields/link/module.server.d.ts +2 -0
  107. package/dist/fields/link/{runtime.server.js → module.server.js} +0 -8
  108. package/dist/fields/number/index.d.ts +1 -1
  109. package/dist/fields/number/index.js +3 -2
  110. package/dist/fields/radio/index.d.ts +1 -1
  111. package/dist/fields/radio/index.js +4 -2
  112. package/dist/fields/relation/index.d.ts +2 -6
  113. package/dist/fields/relation/index.js +64 -75
  114. package/dist/fields/relation/{runtime.d.ts → module.d.ts} +2 -2
  115. package/dist/fields/relation/module.js +4 -0
  116. package/dist/fields/relation/module.server.d.ts +5 -0
  117. package/dist/fields/relation/{runtime.server.js → module.server.js} +3 -6
  118. package/dist/fields/rich-text/index.d.ts +1 -1
  119. package/dist/fields/rich-text/index.js +3 -2
  120. package/dist/fields/select/index.d.ts +1 -1
  121. package/dist/fields/select/index.js +4 -1
  122. package/dist/fields/slug/index.d.ts +1 -1
  123. package/dist/fields/slug/index.js +3 -2
  124. package/dist/fields/tabs/index.d.ts +1 -1
  125. package/dist/fields/tabs/index.js +10 -2
  126. package/dist/fields/text/index.d.ts +1 -1
  127. package/dist/fields/text/index.js +3 -2
  128. package/dist/fields/textarea/index.d.ts +1 -1
  129. package/dist/fields/textarea/index.js +3 -2
  130. package/dist/fields/time/index.d.ts +1 -1
  131. package/dist/fields/time/index.js +3 -2
  132. package/dist/fields/toggle/index.d.ts +1 -1
  133. package/dist/fields/toggle/index.js +3 -3
  134. package/dist/fields/tree/index.d.ts +1 -1
  135. package/dist/fields/tree/index.js +4 -2
  136. package/dist/index.d.ts +3 -3
  137. package/dist/index.js +3 -3
  138. package/dist/panel/components/fields/RenderFields.svelte +1 -1
  139. package/dist/panel/context/collection.svelte.js +5 -3
  140. package/dist/panel/context/config.svelte.d.ts +2 -2
  141. package/dist/panel/context/documentForm.svelte.d.ts +2 -2
  142. package/dist/panel/context/documentForm.svelte.js +47 -28
  143. package/dist/panel/context/form.svelte.js +3 -3
  144. package/dist/panel/context/live.svelte.js +1 -1
  145. package/dist/panel/pages/auth/sign-in/SignIn.svelte +6 -1
  146. package/dist/server.d.ts +4 -4
  147. package/dist/server.js +4 -4
  148. package/dist/types.d.ts +1 -1
  149. package/dist/util/doc.js +1 -1
  150. package/package.json +2 -9
  151. package/dist/fields/blocks/index.server.d.ts +0 -3
  152. package/dist/fields/blocks/index.server.js +0 -5
  153. package/dist/fields/checkbox/index.server.d.ts +0 -3
  154. package/dist/fields/checkbox/index.server.js +0 -3
  155. package/dist/fields/combobox/index.server.d.ts +0 -3
  156. package/dist/fields/combobox/index.server.js +0 -4
  157. package/dist/fields/date/index.server.d.ts +0 -3
  158. package/dist/fields/date/index.server.js +0 -3
  159. package/dist/fields/email/index.server.d.ts +0 -4
  160. package/dist/fields/email/index.server.js +0 -3
  161. package/dist/fields/group/index.server.d.ts +0 -2
  162. package/dist/fields/group/index.server.js +0 -12
  163. package/dist/fields/index.server.d.ts +0 -14
  164. package/dist/fields/index.server.js +0 -65
  165. package/dist/fields/link/runtime.server.d.ts +0 -5
  166. package/dist/fields/number/index.server.d.ts +0 -3
  167. package/dist/fields/number/index.server.js +0 -3
  168. package/dist/fields/radio/index.server.d.ts +0 -3
  169. package/dist/fields/radio/index.server.js +0 -4
  170. package/dist/fields/relation/runtime.js +0 -4
  171. package/dist/fields/relation/runtime.server.d.ts +0 -8
  172. package/dist/fields/rich-text/index.server.d.ts +0 -3
  173. package/dist/fields/rich-text/index.server.js +0 -3
  174. package/dist/fields/select/index.server.d.ts +0 -3
  175. package/dist/fields/select/index.server.js +0 -4
  176. package/dist/fields/slug/index.server.d.ts +0 -3
  177. package/dist/fields/slug/index.server.js +0 -3
  178. package/dist/fields/tabs/index.server.d.ts +0 -3
  179. package/dist/fields/tabs/index.server.js +0 -18
  180. package/dist/fields/text/index.server.d.ts +0 -3
  181. package/dist/fields/text/index.server.js +0 -3
  182. package/dist/fields/textarea/index.server.d.ts +0 -3
  183. package/dist/fields/textarea/index.server.js +0 -3
  184. package/dist/fields/time/index.server.d.ts +0 -3
  185. package/dist/fields/time/index.server.js +0 -3
  186. package/dist/fields/toggle/index.server.d.ts +0 -3
  187. package/dist/fields/toggle/index.server.js +0 -3
  188. package/dist/fields/tree/index.server.d.ts +0 -3
  189. package/dist/fields/tree/index.server.js +0 -4
  190. /package/dist/fields/link/{runtime.d.ts → module.d.ts} +0 -0
  191. /package/dist/fields/link/{runtime.js → module.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { logger } from '../../../logger/index.server.js';
2
2
  import { execSync } from 'child_process';
3
- import { existsSync, readFileSync, writeFileSync } from 'fs';
3
+ import { existsSync } from 'fs';
4
4
  import path from 'path';
5
5
  const packageManagersMap = {
6
6
  yarn: 'yarn.lock',
@@ -9,22 +9,24 @@ const packageManagersMap = {
9
9
  npm: 'package-lock.json',
10
10
  deno: 'deno.lock'
11
11
  };
12
+ const deps = ['drizzle-orm', '@libsql/client', '@lucide/svelte', 'sharp'];
13
+ const devDeps = ['@sveltejs/adapter-node', 'drizzle-kit'];
12
14
  const packageManagerConfigs = {
13
15
  yarn: {
14
16
  command: 'echo "yarn is not supported, please use pnpm or npm" && exit 1'
15
17
  },
16
18
  pnpm: {
17
- command: 'pnpm add -D drizzle-kit && pnpm add -D @sveltejs/adapter-node && pnpm add drizzle-orm @lucide/svelte sharp',
18
- preInstall: updatePackageJsonForPnpm,
19
+ command: `pnpm add -D ${devDeps.join(' ')} && pnpm add ${deps.join(' ')}`,
20
+ preInstall: configurePnpm,
19
21
  postInstall: () => {
20
22
  execSync('pnpm rebuild');
21
23
  }
22
24
  },
23
25
  bun: {
24
- command: 'bun add -D drizzle-kit && bun add -D @sveltejs/adapter-node && bun add drizzle-orm @lucide/svelte sharp'
26
+ command: `bun add -D ${devDeps.join(' ')} && bun add ${deps.join(' ')}`
25
27
  },
26
28
  npm: {
27
- command: 'npm install -D drizzle-kit && npm install -D @sveltejs/adapter-node && npm install drizzle-orm @lucide/svelte sharp'
29
+ command: `npm install -D ${devDeps.join(' ')} && npm install ${deps.join(' ')}`
28
30
  },
29
31
  deno: {
30
32
  command: 'echo "deno is not supported, please use pnpm or npm" && exit 1'
@@ -55,7 +57,7 @@ export function getInvokingPackageManager() {
55
57
  return 'npm';
56
58
  }
57
59
  export function installDependencies() {
58
- const pm = getPackageManager();
60
+ const pm = getInvokingPackageManager();
59
61
  if (pm === 'deno' || pm === 'yarn') {
60
62
  throw new Error('Unsupported package manager ' + pm);
61
63
  }
@@ -68,28 +70,29 @@ export function installDependencies() {
68
70
  // Post installation hooks
69
71
  config.postInstall?.();
70
72
  }
71
- function updatePackageJsonForPnpm() {
72
- const packageJsonPath = path.resolve(process.cwd(), 'package.json');
73
- if (!existsSync(packageJsonPath)) {
74
- logger.warn('package.json not found, skipping pnpm configuration');
75
- return;
76
- }
73
+ /**
74
+ * pnpm 11 no longer reads the "pnpm" field in package.json (onlyBuiltDependencies
75
+ * included) - every pnpm-specific setting moved to pnpm-workspace.yaml, and
76
+ * onlyBuiltDependencies itself was replaced by allowBuilds (a name -> boolean map).
77
+ * `pnpm config set --location=project` writes there directly (creating the file if
78
+ * it doesn't exist yet, even for a non-monorepo single package).
79
+ *
80
+ * strictDepBuilds is also now on by default: any dependency (however deep) with a
81
+ * build script that isn't in allowBuilds hard-fails the whole install with
82
+ * ERR_PNPM_IGNORED_BUILDS instead of just skipping that one script. allowBuilds only
83
+ * covers esbuild/sharp; the rest of this command's tree (drizzle-kit, adapter-node,
84
+ * ...) will always have *something* pnpm hasn't seen before, and pinning every one of
85
+ * them here would just break again on their next update - so strict-dep-builds is
86
+ * turned off the same way.
87
+ */
88
+ function configurePnpm() {
89
+ const allowBuilds = JSON.stringify({ esbuild: false, sharp: true, 'better-sqlite3': false });
77
90
  try {
78
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
79
- // Initialize pnpm configuration if it doesn't exist
80
- if (!packageJson.pnpm) {
81
- packageJson.pnpm = {};
82
- }
83
- // Update onlyBuiltDependencies
84
- const existingBuiltDeps = packageJson.pnpm.onlyBuiltDependencies || [];
85
- const newBuiltDeps = ['esbuild', 'sharp'];
86
- // Merge and deduplicate
87
- const mergedBuiltDeps = [...new Set([...existingBuiltDeps, ...newBuiltDeps])];
88
- packageJson.pnpm.onlyBuiltDependencies = mergedBuiltDeps;
89
- writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, '\t'), 'utf-8');
90
- logger.info('[✓] Updated package.json with pnpm configuration');
91
+ execSync(`pnpm config set --location=project --json allowBuilds '${allowBuilds}'`);
92
+ execSync('pnpm config set --location=project strict-dep-builds false');
93
+ logger.info('[✓] Configured pnpm-workspace.yaml: allowBuilds (sharp) and strict-dep-builds (false)');
91
94
  }
92
95
  catch (error) {
93
- logger.error('Failed to update package.json for pnpm:', error);
96
+ logger.error('Failed to configure pnpm:', error);
94
97
  }
95
98
  }
@@ -1,2 +1,21 @@
1
- export const OUTPUT_DIR: "+rime.generated";
2
- export const INPUT_DIR: "+rime";
1
+ export declare const OUTPUT_DIR = "+rime.generated";
2
+ export declare const INPUT_DIR = "+rime";
3
+ /** Where the panel/live route templates (common.server.ts) should import the client config
4
+ * from, and where the dev-server watcher/hot-reload should read the server config from —
5
+ * one source of truth instead of hardcoding the path at each call site. */
6
+ export declare function configImportPaths(): {
7
+ client: string;
8
+ server: string;
9
+ };
10
+ /** Same idea as configImportPaths — one source of truth for where the generated schema
11
+ * lives instead of hardcoding the path at each call site. */
12
+ export declare function schemaPath(root?: string): string;
13
+ /**
14
+ * True when `metaUrl` (pass the caller's own `import.meta.url` — this can't be one shared
15
+ * value, it depends on which file is asking) is running from inside an installed `rimecms`
16
+ * package (node_modules/rimecms/...), false when running from rime's own repo. Deliberately
17
+ * filesystem-based rather than an env var: a shell dotenv plugin auto-loading this repo's own
18
+ * .env (which used to set IS_RIME_REPO=true) into every child process — including an unrelated
19
+ * consumer app's dev server — made that value unreliable; this can't leak the same way.
20
+ */
21
+ export declare function isInstalledDependency(metaUrl: string): boolean;
@@ -1,2 +1,28 @@
1
+ import path from 'node:path';
1
2
  export const OUTPUT_DIR = '+rime.generated';
2
3
  export const INPUT_DIR = '+rime';
4
+ /** Where the panel/live route templates (common.server.ts) should import the client config
5
+ * from, and where the dev-server watcher/hot-reload should read the server config from —
6
+ * one source of truth instead of hardcoding the path at each call site. */
7
+ export function configImportPaths() {
8
+ return {
9
+ client: `$lib/${OUTPUT_DIR}/rime.config.js`,
10
+ server: `$lib/${OUTPUT_DIR}/rime.config.server.js`
11
+ };
12
+ }
13
+ /** Same idea as configImportPaths — one source of truth for where the generated schema
14
+ * lives instead of hardcoding the path at each call site. */
15
+ export function schemaPath(root = process.cwd()) {
16
+ return path.resolve(root, 'src/lib', OUTPUT_DIR, 'schema.server.ts');
17
+ }
18
+ /**
19
+ * True when `metaUrl` (pass the caller's own `import.meta.url` — this can't be one shared
20
+ * value, it depends on which file is asking) is running from inside an installed `rimecms`
21
+ * package (node_modules/rimecms/...), false when running from rime's own repo. Deliberately
22
+ * filesystem-based rather than an env var: a shell dotenv plugin auto-loading this repo's own
23
+ * .env (which used to set IS_RIME_REPO=true) into every child process — including an unrelated
24
+ * consumer app's dev server — made that value unreliable; this can't leak the same way.
25
+ */
26
+ export function isInstalledDependency(metaUrl) {
27
+ return metaUrl.includes('/node_modules/rimecms/');
28
+ }
@@ -1,8 +1,5 @@
1
1
  import { PACKAGE_NAME } from '../../../constant.server.js';
2
- import { OUTPUT_DIR } from '../../constants.js';
3
- // Trick to prevent svelte-kit to replace $lib with resolved imports path
4
- // same thing for PACKAGE_NAME
5
- const DOLLAR_LIB = '$lib';
2
+ import { configImportPaths } from '../../constants.js';
6
3
  /**
7
4
  * Main base layout
8
5
  * /+layout.server.ts
@@ -149,8 +146,8 @@ export const load = (event: ServerLoadEvent) => event.locals.routes.panel.load.r
149
146
  const panelLayout = () => `
150
147
  <script>
151
148
  import { Panel } from '${PACKAGE_NAME}/panel';
152
- import config from '${DOLLAR_LIB}/${OUTPUT_DIR}/rime.config.js';
153
-
149
+ import config from '${configImportPaths().client}';
150
+
154
151
  const { children, data } = $props();
155
152
 
156
153
  const user = $derived.by(() => {
@@ -199,7 +196,7 @@ export const load = (event: ServerLoadEvent) => event.locals.routes.panel.load.d
199
196
  const livePage = () => `
200
197
  <script>
201
198
  import { Live } from '${PACKAGE_NAME}/panel';
202
- import config from '${DOLLAR_LIB}/${OUTPUT_DIR}/rime.config.js';
199
+ import config from '${configImportPaths().client}';
203
200
 
204
201
  const { data } = $props();
205
202
  </script>
@@ -30,15 +30,36 @@ function generateRoutes(config) {
30
30
  ensureDir(rimeRoutes);
31
31
  ensureDir(panelRoute);
32
32
  ensureDir(paramsDir);
33
- // 3. Process common routes (now includes the matcher-disambiguated [slug=collection|area] routes)
33
+ // 3. Process common routes (now includes the matcher-disambiguated [slug=collection|area] routes) —
34
+ // skip the [slug=collection]/[slug=area] routes and their matchers when there are no collections/areas
35
+ // configured, rather than generating a matcher that can never match anything.
36
+ const hasCollections = (config.collections || []).length > 0;
37
+ const hasAreas = (config.areas || []).length > 0;
34
38
  for (const [pattern, files] of Object.entries(commonRoutes)) {
39
+ if (!hasCollections && pattern.includes('[slug=collection]'))
40
+ continue;
41
+ if (!hasAreas && pattern.includes('[slug=area]'))
42
+ continue;
35
43
  for (const [fileType, templateFn] of Object.entries(files)) {
36
44
  writeRouteFile(rootRoutes, pattern, fileType, templateFn());
37
45
  }
38
46
  }
39
- // 4. Write the [slug=collection]/[slug=area] param matchers
40
- fs.writeFileSync(path.join(paramsDir, 'collection.ts'), paramMatcher((config.collections || []).map((c) => c.kebab)));
41
- fs.writeFileSync(path.join(paramsDir, 'area.ts'), paramMatcher((config.areas || []).map((a) => a.kebab)));
47
+ // 4. Write the [slug=collection]/[slug=area] param matchers, only when needed — and remove any
48
+ // stale matcher left over from a previous config that did have collections/areas.
49
+ const collectionMatcherPath = path.join(paramsDir, 'collection.ts');
50
+ if (hasCollections) {
51
+ fs.writeFileSync(collectionMatcherPath, paramMatcher((config.collections || []).map((c) => c.kebab)));
52
+ }
53
+ else {
54
+ fs.rmSync(collectionMatcherPath, { force: true });
55
+ }
56
+ const areaMatcherPath = path.join(paramsDir, 'area.ts');
57
+ if (hasAreas) {
58
+ fs.writeFileSync(areaMatcherPath, paramMatcher((config.areas || []).map((a) => a.kebab)));
59
+ }
60
+ else {
61
+ fs.rmSync(areaMatcherPath, { force: true });
62
+ }
42
63
  // 5. Handle custom routes from config
43
64
  const customRoutes = config.panel?.routes;
44
65
  if (customRoutes) {
@@ -0,0 +1,40 @@
1
+ export type RuntimeRegistryEntry = {
2
+ client: string;
3
+ server: string;
4
+ };
5
+ export type RuntimeRegistry = Map<string, RuntimeRegistryEntry>;
6
+ /**
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.
10
+ *
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/`).
34
+ *
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.
39
+ */
40
+ export declare function scanModulePairs(root: string): RuntimeRegistry;
@@ -0,0 +1,85 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { OUTPUT_DIR } from '../../constants.js';
4
+ /**
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.
17
+ */
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
+ }
25
+ const parent = path.dirname(dir);
26
+ if (parent === dir) {
27
+ throw new Error(`$rime/modules: could not find installed package '${pkgName}' above ${fromDir}`);
28
+ }
29
+ dir = parent;
30
+ }
31
+ }
32
+ /** `module.ts` when scanning TS source, `module.js` when scanning a built `dist/`. */
33
+ function findModuleFile(dir, baseName) {
34
+ for (const ext of ['.ts', '.js']) {
35
+ const candidate = path.join(dir, `${baseName}${ext}`);
36
+ if (fs.existsSync(candidate))
37
+ return candidate;
38
+ }
39
+ return null;
40
+ }
41
+ /**
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/`).
59
+ *
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.
64
+ */
65
+ export function scanModulePairs(root) {
66
+ const registry = new Map();
67
+ if (!fs.existsSync(root))
68
+ return registry;
69
+ const walk = (dir) => {
70
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
71
+ if (!entry.isDirectory() || entry.name === OUTPUT_DIR)
72
+ continue;
73
+ const fullPath = path.join(dir, entry.name);
74
+ const client = findModuleFile(fullPath, 'module');
75
+ const server = findModuleFile(fullPath, 'module.server');
76
+ if (client || server) {
77
+ const key = path.relative(root, fullPath).split(path.sep).join('/');
78
+ registry.set(key, { client: client ?? '', server: server ?? '' });
79
+ }
80
+ walk(fullPath);
81
+ }
82
+ };
83
+ walk(root);
84
+ return registry;
85
+ }
@@ -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
 
@@ -6,15 +6,14 @@ import { logger } from '../../../logger/index.server.js';
6
6
  import { withVersionsSuffix } from '../../../naming.js';
7
7
  import { BlocksBuilder } from '../../../../fields/blocks/index.js';
8
8
  import { GroupFieldBuilder } from '../../../../fields/group/index.js';
9
- import { getFieldPrivateModule } from '../../../../fields/index.server.js';
10
9
  import { TabsBuilder } from '../../../../fields/tabs/index.js';
11
10
  import { TreeBuilder } from '../../../../fields/tree/index.js';
12
11
  import { trycatchSync } from '../../../../util/function.js';
13
12
  import { capitalize, toPascalCase } from '../../../../util/string.js';
14
13
  import fs from 'node:fs';
15
14
  import path from 'node:path';
16
- import { OUTPUT_DIR } from '../../constants.js';
17
- const IS_PACKAGE_DEV = process.env.IS_PACKAGE_DEV === 'true';
15
+ import { isInstalledDependency, OUTPUT_DIR } from '../../constants.js';
16
+ const IS_RIME_REPO = !isInstalledDependency(import.meta.url);
18
17
  // Relation type
19
18
  const relationValueType = `
20
19
  export type RelationValue<T> =
@@ -110,12 +109,23 @@ const templateRegister = (config) => {
110
109
  };
111
110
  const templateDeclareVirtualModule = () => [
112
111
  `declare module '$rime/config' {`,
113
- ...(IS_PACKAGE_DEV ? ['\t// eslint-disable-next-line no-restricted-imports'] : []),
112
+ ...(IS_RIME_REPO ? ['\t// eslint-disable-next-line no-restricted-imports'] : []),
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';`,
116
+ `\texport * from '$lib/rime.schema.server.js';`,
118
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).
119
129
  ].join('\n');
120
130
  /**
121
131
  * Generates type definitions for image sizes
@@ -155,17 +165,7 @@ export async function generateTypesString(config) {
155
165
  * @returns An array of string containing fields type definitions
156
166
  */
157
167
  const buildFieldsTypes = async (fields) => {
158
- const strFields = [];
159
- for (const field of fields) {
160
- if (field instanceof FormFieldBuilder || field instanceof TabsBuilder) {
161
- const fieldServerMethods = await getFieldPrivateModule(field);
162
- if (fieldServerMethods) {
163
- const result = await Promise.resolve(fieldServerMethods.toType(field));
164
- strFields.push(result);
165
- }
166
- }
167
- }
168
- return strFields;
168
+ return fields.map((field) => field.use.generateType()).filter(Boolean);
169
169
  };
170
170
  const buildblocksTypes = async (fields) => {
171
171
  for (const field of fields) {
@@ -259,6 +259,7 @@ export async function generateTypesString(config) {
259
259
  const blocksTypeNames = `export type BlockTypes = ${registeredBlocks.map((name) => `'${name}'`).join('|')}\n`;
260
260
  const anyBlock = `export type AnyBlock = ${registeredBlocks.map((name) => `Block${toPascalCase(name)}`).join('|')}\n`;
261
261
  const typeImports = `import type { ${Array.from(imports).join(', ')} } from '${PACKAGE_NAME}/types'`;
262
+ const rimeConfigServerPath = `./lib/${OUTPUT_DIR}/rime.config.server.ts`;
262
263
  const locals = `declare global {
263
264
  namespace App {
264
265
  interface Locals {
@@ -292,7 +293,7 @@ export async function generateTypesString(config) {
292
293
  /** Singleton providing access to auth, config and local-api */
293
294
  rime: ReturnType<
294
295
  Awaited<
295
- typeof import('./lib/${OUTPUT_DIR}/rime.config.server.ts').default
296
+ typeof import('${rimeConfigServerPath}').default
296
297
  >['createRimeContext']
297
298
  >;
298
299
  /** Flag enabled by the core plugin rime.cache when the API cache is ON */
@@ -314,10 +315,10 @@ export async function generateTypesString(config) {
314
315
  }
315
316
  }`;
316
317
  const content = [
317
- ...(IS_PACKAGE_DEV ? ['// eslint-disable-next-line no-restricted-imports'] : []),
318
+ ...(IS_RIME_REPO ? ['// eslint-disable-next-line no-restricted-imports'] : []),
318
319
  `import '${PACKAGE_NAME}';`,
319
320
  `import type { Session } from 'better-auth';`,
320
- ...(IS_PACKAGE_DEV ? ['// eslint-disable-next-line no-restricted-imports'] : []),
321
+ ...(IS_RIME_REPO ? ['// eslint-disable-next-line no-restricted-imports'] : []),
321
322
  typeImports,
322
323
  '',
323
324
  relationValueType,
@@ -357,7 +358,7 @@ function write(key, content, filePath) {
357
358
  */
358
359
  async function generateTypes(config) {
359
360
  const mainTypes = await generateTypesString(config);
360
- const declarations = [templateDeclareVirtualModule()].join('\n');
361
+ const declarations = templateDeclareVirtualModule();
361
362
  const appGeneratedPath = path.resolve(process.cwd(), 'src', 'app.generated.d.ts');
362
363
  const virtualModuleGeneratedPath = path.resolve(process.cwd(), 'src', 'rime.generated.d.ts');
363
364
  write('app.generated', mainTypes, appGeneratedPath);