rimecms 0.30.1 → 0.30.3

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 (123) hide show
  1. package/README.md +1 -1
  2. package/dist/adapter-sqlite/generate-schema/column.server.js +1 -4
  3. package/dist/adapter-sqlite/generate-schema/write.server.js +1 -3
  4. package/dist/core/config/auth/better-auth-hooks.server.js +6 -3
  5. package/dist/core/dev/cli/build/index.d.ts +1 -0
  6. package/dist/core/dev/cli/build/index.js +10 -5
  7. package/dist/core/dev/cli/build/templates.d.ts +1 -1
  8. package/dist/core/dev/cli/build/templates.js +10 -7
  9. package/dist/core/dev/cli/index.js +1 -0
  10. package/dist/core/dev/cli/init/index.server.js +3 -3
  11. package/dist/core/dev/cli/init/templates.js +8 -1
  12. package/dist/core/dev/cli/util/package-manager.server.js +27 -26
  13. package/dist/core/dev/constants.d.ts +11 -2
  14. package/dist/core/dev/constants.js +11 -0
  15. package/dist/core/dev/generate/runtime/index.server.d.ts +24 -0
  16. package/dist/core/dev/generate/runtime/index.server.js +93 -0
  17. package/dist/core/dev/generate/types/index.server.js +42 -18
  18. package/dist/core/dev/vite/index.server.js +38 -17
  19. package/dist/core/fields/builders/boolean-builder.d.ts +1 -2
  20. package/dist/core/fields/builders/field-builder.d.ts +11 -6
  21. package/dist/core/fields/builders/field-builder.js +12 -5
  22. package/dist/core/fields/builders/form-field-builder.d.ts +11 -14
  23. package/dist/core/fields/builders/form-field-builder.js +17 -15
  24. package/dist/core/fields/util.js +1 -1
  25. package/dist/core/operations/blocks/extract.server.js +1 -1
  26. package/dist/core/operations/hooks/before-read/process-document-fields.server.js +3 -3
  27. package/dist/core/operations/hooks/before-upsert/set-default-values.server.js +3 -3
  28. package/dist/core/operations/hooks/before-upsert/validate-fields.server.js +7 -7
  29. package/dist/core/operations/shared/fallback-data-from-original.js +1 -1
  30. package/dist/core/operations/tree/extract.server.js +1 -1
  31. package/dist/core/plugins/api-init/index.server.js +2 -2
  32. package/dist/fields/blocks/index.d.ts +1 -1
  33. package/dist/fields/blocks/index.js +5 -2
  34. package/dist/fields/checkbox/index.d.ts +1 -1
  35. package/dist/fields/checkbox/index.js +3 -2
  36. package/dist/fields/combobox/index.d.ts +1 -1
  37. package/dist/fields/combobox/index.js +4 -2
  38. package/dist/fields/date/index.d.ts +2 -2
  39. package/dist/fields/date/index.js +7 -5
  40. package/dist/fields/email/index.d.ts +3 -3
  41. package/dist/fields/email/index.js +9 -8
  42. package/dist/fields/group/component/Group.svelte +1 -1
  43. package/dist/fields/group/index.d.ts +1 -1
  44. package/dist/fields/group/index.js +4 -2
  45. package/dist/fields/link/index.d.ts +1 -1
  46. package/dist/fields/link/index.js +9 -5
  47. package/dist/fields/link/module.server.d.ts +2 -0
  48. package/dist/fields/link/{runtime.server.js → module.server.js} +0 -8
  49. package/dist/fields/number/index.d.ts +1 -1
  50. package/dist/fields/number/index.js +3 -2
  51. package/dist/fields/radio/index.d.ts +1 -1
  52. package/dist/fields/radio/index.js +4 -2
  53. package/dist/fields/relation/index.d.ts +2 -6
  54. package/dist/fields/relation/index.js +5 -9
  55. package/dist/fields/relation/{runtime.d.ts → module.d.ts} +2 -2
  56. package/dist/fields/relation/module.js +4 -0
  57. package/dist/fields/relation/module.server.d.ts +5 -0
  58. package/dist/fields/relation/{runtime.server.js → module.server.js} +3 -6
  59. package/dist/fields/rich-text/index.d.ts +1 -1
  60. package/dist/fields/rich-text/index.js +3 -2
  61. package/dist/fields/select/index.d.ts +1 -1
  62. package/dist/fields/select/index.js +4 -1
  63. package/dist/fields/slug/index.d.ts +1 -1
  64. package/dist/fields/slug/index.js +3 -2
  65. package/dist/fields/tabs/index.d.ts +1 -1
  66. package/dist/fields/tabs/index.js +10 -2
  67. package/dist/fields/text/index.d.ts +1 -1
  68. package/dist/fields/text/index.js +3 -2
  69. package/dist/fields/textarea/index.d.ts +1 -1
  70. package/dist/fields/textarea/index.js +3 -2
  71. package/dist/fields/time/index.d.ts +1 -1
  72. package/dist/fields/time/index.js +3 -2
  73. package/dist/fields/toggle/index.d.ts +1 -1
  74. package/dist/fields/toggle/index.js +3 -3
  75. package/dist/fields/tree/index.d.ts +1 -1
  76. package/dist/fields/tree/index.js +4 -2
  77. package/dist/panel/components/fields/RenderFields.svelte +1 -1
  78. package/dist/panel/context/collection.svelte.js +5 -3
  79. package/dist/panel/context/documentForm.svelte.js +47 -28
  80. package/dist/panel/context/form.svelte.js +3 -3
  81. package/dist/util/doc.js +1 -1
  82. package/package.json +1 -8
  83. package/dist/fields/blocks/index.server.d.ts +0 -3
  84. package/dist/fields/blocks/index.server.js +0 -5
  85. package/dist/fields/checkbox/index.server.d.ts +0 -3
  86. package/dist/fields/checkbox/index.server.js +0 -3
  87. package/dist/fields/combobox/index.server.d.ts +0 -3
  88. package/dist/fields/combobox/index.server.js +0 -4
  89. package/dist/fields/date/index.server.d.ts +0 -3
  90. package/dist/fields/date/index.server.js +0 -3
  91. package/dist/fields/email/index.server.d.ts +0 -4
  92. package/dist/fields/email/index.server.js +0 -3
  93. package/dist/fields/group/index.server.d.ts +0 -2
  94. package/dist/fields/group/index.server.js +0 -12
  95. package/dist/fields/index.server.d.ts +0 -14
  96. package/dist/fields/index.server.js +0 -65
  97. package/dist/fields/link/runtime.server.d.ts +0 -5
  98. package/dist/fields/number/index.server.d.ts +0 -3
  99. package/dist/fields/number/index.server.js +0 -3
  100. package/dist/fields/radio/index.server.d.ts +0 -3
  101. package/dist/fields/radio/index.server.js +0 -4
  102. package/dist/fields/relation/runtime.js +0 -4
  103. package/dist/fields/relation/runtime.server.d.ts +0 -8
  104. package/dist/fields/rich-text/index.server.d.ts +0 -3
  105. package/dist/fields/rich-text/index.server.js +0 -3
  106. package/dist/fields/select/index.server.d.ts +0 -3
  107. package/dist/fields/select/index.server.js +0 -4
  108. package/dist/fields/slug/index.server.d.ts +0 -3
  109. package/dist/fields/slug/index.server.js +0 -3
  110. package/dist/fields/tabs/index.server.d.ts +0 -3
  111. package/dist/fields/tabs/index.server.js +0 -18
  112. package/dist/fields/text/index.server.d.ts +0 -3
  113. package/dist/fields/text/index.server.js +0 -3
  114. package/dist/fields/textarea/index.server.d.ts +0 -3
  115. package/dist/fields/textarea/index.server.js +0 -3
  116. package/dist/fields/time/index.server.d.ts +0 -3
  117. package/dist/fields/time/index.server.js +0 -3
  118. package/dist/fields/toggle/index.server.d.ts +0 -3
  119. package/dist/fields/toggle/index.server.js +0 -3
  120. package/dist/fields/tree/index.server.d.ts +0 -3
  121. package/dist/fields/tree/index.server.js +0 -4
  122. /package/dist/fields/link/{runtime.d.ts → module.d.ts} +0 -0
  123. /package/dist/fields/link/{runtime.js → module.js} +0 -0
package/README.md CHANGED
@@ -245,7 +245,7 @@ npx rime build
245
245
  npx rime build -d # to copy the database directory
246
246
  ```
247
247
 
248
- It's doing bascically `vite build` under the hood and create the polka server file inside an app directory, plus giving some info on how to run it.
248
+ It's doing bascically `vite build` under the hood and create the server file inside an app directory, plus giving some info on how to run it.
249
249
 
250
250
  ## ROADMAP
251
251
 
@@ -66,10 +66,7 @@ export function toSchemaColumn(field, parentPath) {
66
66
  // reading it off the generic FormField base needs a narrow cast, same as
67
67
  // each field's own toSchema did today by typing against its concrete field.
68
68
  const { unique, required } = field.get;
69
- // `dataType` is implemented by every leaf field builder but isn't declared
70
- // on the FormFieldBuilder base (see the comment there) — narrow cast here.
71
- const dataType = field.dataType;
72
- const columnExpr = COLUMN_EXPR[dataType](snake);
69
+ const columnExpr = COLUMN_EXPR[field.dataType](snake);
73
70
  const referencesStr = field._references ? templateReferences(field._references) : '';
74
71
  const defaultValue = RESOLVE_DEFAULT[field.type]?.(field);
75
72
  const suffix = templateUniqueRequired({ unique, required }, defaultValue);
@@ -31,19 +31,17 @@ const write = (schema) => {
31
31
  };
32
32
  const command = commandMap[pm];
33
33
  logger.info('[✓] Schema: generated at src/lib/server/schema.ts');
34
- console.log('============================================================');
35
34
  console.log(`\n ⚡︎ ${command} drizzle-kit generate \n`);
36
35
  const generateResult = spawnSync(command, ['drizzle-kit', 'generate'], { stdio: 'inherit' });
37
36
  if (generateResult.error || generateResult.status !== 0) {
38
37
  throw new Error('drizzle-kit generate failed');
39
38
  }
40
- console.log('\n============================================================');
41
39
  console.log(`\n ⚡︎ ${command} drizzle-kit migrate \n`);
42
40
  const migrateResult = spawnSync(command, ['drizzle-kit', 'migrate'], { stdio: 'inherit' });
43
41
  if (migrateResult.error || migrateResult.status !== 0) {
44
42
  throw new Error('drizzle-kit migrate failed');
45
43
  }
46
- console.log('\n============================================================');
44
+ console.log('\n');
47
45
  // Only mark the schema as in sync once generate + migrate both actually
48
46
  // succeeded — otherwise a future run with an unchanged schema string would
49
47
  // skip re-running drizzle-kit and the DB would stay silently out of sync.
@@ -74,7 +74,7 @@ const handleUserCreation = async (ctx) => {
74
74
  /**
75
75
  * Create the collection document
76
76
  */
77
- const [error] = await trycatch(() => event.locals.rime
77
+ const [error, userData] = await trycatch(() => event.locals.rime
78
78
  .collection(ctx.body.type)
79
79
  .system(event.locals.isInit)
80
80
  .create({
@@ -89,7 +89,7 @@ const handleUserCreation = async (ctx) => {
89
89
  }
90
90
  }));
91
91
  // If error clean up session/account/user created
92
- // Would be great to do it with the admin plugin,
92
+ // note: would be cleaner to do it with the admin plugin, not possible at the moment
93
93
  if (error) {
94
94
  console.log(error);
95
95
  logger.error(error.message);
@@ -105,6 +105,8 @@ const handleUserCreation = async (ctx) => {
105
105
  await rime.adapter.db.delete(authUsersTable).where(eq(authUsersTable.id, user.id));
106
106
  throw new APIError('BAD_REQUEST');
107
107
  }
108
+ // Return rime user data
109
+ return userData;
108
110
  };
109
111
  /****************************************************/
110
112
  /* Before Hooks
@@ -131,6 +133,7 @@ export const betterAuthAfterHook = createAuthMiddleware(async (ctx) => {
131
133
  }
132
134
  // Handle sign-up
133
135
  if (ctx.path.startsWith('/sign-up')) {
134
- return await handleUserCreation(ctx);
136
+ await handleUserCreation(ctx);
137
+ return await getUserAttributes(ctx);
135
138
  }
136
139
  });
@@ -2,4 +2,5 @@
2
2
  export declare const build: (args: {
3
3
  withDatabase?: boolean;
4
4
  withEnv?: boolean;
5
+ withStatic?: boolean;
5
6
  }) => void;
@@ -4,15 +4,15 @@ import chalk from 'chalk';
4
4
  import { spawnSync } from 'child_process';
5
5
  import { copyFileSync, cpSync, existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'fs';
6
6
  import { getInvokingPackageManager } from '../util/package-manager.server.js';
7
- import { envProduction, polkaServer } from './templates.js';
7
+ import { envProduction, nodeServer } from './templates.js';
8
8
  const installCommands = {
9
9
  pnpm: {
10
- addDeps: 'pnpm install polka sharp serve-static',
10
+ addDeps: 'pnpm install sharp serve-static',
11
11
  prodInstall: 'pnpm install --prod',
12
12
  runEnv: 'pnpm rime env'
13
13
  },
14
14
  npm: {
15
- addDeps: 'npm install polka sharp serve-static',
15
+ addDeps: 'npm install sharp serve-static',
16
16
  prodInstall: 'npm install --omit=dev',
17
17
  runEnv: 'npx rime env'
18
18
  }
@@ -38,9 +38,14 @@ export const build = (args) => {
38
38
  cpSync('./db', './app/db', { recursive: true });
39
39
  logger.info('[✓] database copied');
40
40
  }
41
+ // Copy static folder if flag is set
42
+ if (args.withStatic) {
43
+ cpSync('./static', './app/static', { recursive: true });
44
+ logger.info('[✓] static directory copied');
45
+ }
41
46
  // Create main entry server file
42
- writeFileSync('./app/index.js', polkaServer);
43
- logger.info('[✓] polka server created at app/index.js');
47
+ writeFileSync('./app/index.js', nodeServer);
48
+ logger.info('[✓] server created at app/index.js');
44
49
  // Create .env file if flag is set
45
50
  const envContent = envProduction();
46
51
  if (args.withEnv) {
@@ -1,2 +1,2 @@
1
1
  export declare const envProduction: () => string;
2
- export declare const polkaServer = "import polka from 'polka';\nimport serveStatic from 'serve-static';\nimport { handler } from './build/handler.js';\n\nconst serve = serveStatic('./static');\n\nconst port = process.env.PORT || 3000;\nconst host = process.env.HOST || '127.0.0.1';\n\npolka()\n\t.use(serve)\n\t.use(handler)\n\t.listen(port, host, () => {\n\t\tconsole.log(`server running on ${host}:${port}`);\n\t});\n";
2
+ export declare const nodeServer = "import { createServer } from 'node:http';\nimport serveStatic from 'serve-static';\nimport { handler } from './build/handler.js';\n\nconst serve = serveStatic('./static');\n\nconst port = process.env.PORT || 3000;\nconst host = process.env.HOST || '127.0.0.1';\nconst protocol = process.env.ORIGIN?.startsWith('https') ? 'https' : 'http';\n\ncreateServer((req, res) => {\n\tserve(req, res, () => handler(req, res, () => {\n\t\tres.statusCode = 404;\n\t\tres.end();\n\t}));\n}).listen(port, host, () => {\n\tconsole.log(`server running on ${protocol}://${host}:${port}`);\n});\n";
@@ -22,7 +22,7 @@ HOST=localhost
22
22
  # MISC
23
23
  BODY_SIZE_LIMIT=10485760 # 10(MB) * 1024 * 1024 = 10485760 bytes
24
24
  `;
25
- export const polkaServer = `import polka from 'polka';
25
+ export const nodeServer = `import { createServer } from 'node:http';
26
26
  import serveStatic from 'serve-static';
27
27
  import { handler } from './build/handler.js';
28
28
 
@@ -30,11 +30,14 @@ const serve = serveStatic('./static');
30
30
 
31
31
  const port = process.env.PORT || 3000;
32
32
  const host = process.env.HOST || '127.0.0.1';
33
+ const protocol = process.env.ORIGIN?.startsWith('https') ? 'https' : 'http';
33
34
 
34
- polka()
35
- .use(serve)
36
- .use(handler)
37
- .listen(port, host, () => {
38
- console.log(\`server running on \${host}:\${port}\`);
39
- });
35
+ createServer((req, res) => {
36
+ serve(req, res, () => handler(req, res, () => {
37
+ res.statusCode = 404;
38
+ res.end();
39
+ }));
40
+ }).listen(port, host, () => {
41
+ console.log(\`server running on \${protocol}://\${host}:\${port}\`);
42
+ });
40
43
  `;
@@ -18,6 +18,7 @@ program
18
18
  .command('build')
19
19
  .option('-d, --with-database', 'Include database', false)
20
20
  .option('-e, --with-env', 'Create the /app/.env file from the production template', false)
21
+ .option('-s, --with-static', 'Copy the current static directory', false)
21
22
  .action(async (args) => {
22
23
  const build = await import('./build/index.js').then((m) => m.build);
23
24
  build(args);
@@ -3,7 +3,7 @@ import { randomId } from '../../../../util/random.js';
3
3
  import { isValidSlug, slugify } from '../../../../util/string.js';
4
4
  import { generate as generateCode } from '@babel/generator';
5
5
  import * as t from '@babel/types';
6
- import { babelParse } from 'ast-kit';
6
+ import { babelParse, getLang } from 'ast-kit';
7
7
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
8
8
  import { cp, mkdir } from 'fs/promises';
9
9
  import fs from 'node:fs';
@@ -60,7 +60,7 @@ export const init = async ({ force, name: incomingName, skipInstall }) => {
60
60
  mkdirSync(configDirPath);
61
61
  }
62
62
  writeFileSync(configPath, templates.defaultConfig(name.toString()));
63
- logger.info('[✓] Config created src/lib/config/rime.config.ts');
63
+ logger.info(`[✓] Config created at ${configDirPath}/rime.config.ts`);
64
64
  }
65
65
  else {
66
66
  logger.info('[✓] Config already exists (skip)');
@@ -113,7 +113,7 @@ export const init = async ({ force, name: incomingName, skipInstall }) => {
113
113
  }
114
114
  let content = fs.readFileSync(configPath, 'utf-8');
115
115
  content = content.replace("from '@sveltejs/adapter-auto'", "from '@sveltejs/adapter-node'");
116
- const program = babelParse(content, configPath); // t.Program
116
+ const program = babelParse(content, getLang(configPath));
117
117
  const programBody = program.body;
118
118
  const configObject = findSvelteConfigObject(programBody);
119
119
  if (!configObject) {
@@ -24,9 +24,16 @@ const Pages = Collection.create('pages', {
24
24
  fields: [text('title').isTitle()]
25
25
  });
26
26
 
27
+ const Medias = Collection.create('medias', {
28
+ upload: true,
29
+ fields: [
30
+ text('alt').required(),
31
+ ]
32
+ });
33
+
27
34
  export default rime({
28
35
  $adapter: adapterSqlite('${name}.sqlite'),
29
- collections: [Pages]
36
+ collections: [Pages, Medias]
30
37
  });
31
38
  `;
32
39
  export const drizzleConfig = (name) => `
@@ -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',
@@ -14,17 +14,17 @@ const packageManagerConfigs = {
14
14
  command: 'echo "yarn is not supported, please use pnpm or npm" && exit 1'
15
15
  },
16
16
  pnpm: {
17
- command: 'pnpm add -D drizzle-kit && pnpm add -D @sveltejs/adapter-node && pnpm add drizzle-orm @lucide/svelte sharp',
18
- preInstall: updatePackageJsonForPnpm,
17
+ command: 'pnpm add -D drizzle-kit && pnpm add -D @sveltejs/adapter-node && pnpm add drizzle-orm @libsql/client @lucide/svelte sharp',
18
+ preInstall: configurePnpm,
19
19
  postInstall: () => {
20
20
  execSync('pnpm rebuild');
21
21
  }
22
22
  },
23
23
  bun: {
24
- command: 'bun add -D drizzle-kit && bun add -D @sveltejs/adapter-node && bun add drizzle-orm @lucide/svelte sharp'
24
+ command: 'bun add -D drizzle-kit && bun add -D @sveltejs/adapter-node && bun add drizzle-orm @libsql/client @lucide/svelte sharp'
25
25
  },
26
26
  npm: {
27
- command: 'npm install -D drizzle-kit && npm install -D @sveltejs/adapter-node && npm install drizzle-orm @lucide/svelte sharp'
27
+ command: 'npm install -D drizzle-kit && npm install -D @sveltejs/adapter-node && npm install drizzle-orm @libsql/client @lucide/svelte sharp'
28
28
  },
29
29
  deno: {
30
30
  command: 'echo "deno is not supported, please use pnpm or npm" && exit 1'
@@ -55,7 +55,7 @@ export function getInvokingPackageManager() {
55
55
  return 'npm';
56
56
  }
57
57
  export function installDependencies() {
58
- const pm = getPackageManager();
58
+ const pm = getInvokingPackageManager();
59
59
  if (pm === 'deno' || pm === 'yarn') {
60
60
  throw new Error('Unsupported package manager ' + pm);
61
61
  }
@@ -68,28 +68,29 @@ export function installDependencies() {
68
68
  // Post installation hooks
69
69
  config.postInstall?.();
70
70
  }
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
- }
71
+ /**
72
+ * pnpm 11 no longer reads the "pnpm" field in package.json (onlyBuiltDependencies
73
+ * included) - every pnpm-specific setting moved to pnpm-workspace.yaml, and
74
+ * onlyBuiltDependencies itself was replaced by allowBuilds (a name -> boolean map).
75
+ * `pnpm config set --location=project` writes there directly (creating the file if
76
+ * it doesn't exist yet, even for a non-monorepo single package).
77
+ *
78
+ * strictDepBuilds is also now on by default: any dependency (however deep) with a
79
+ * build script that isn't in allowBuilds hard-fails the whole install with
80
+ * ERR_PNPM_IGNORED_BUILDS instead of just skipping that one script. allowBuilds only
81
+ * covers esbuild/sharp; the rest of this command's tree (drizzle-kit, adapter-node,
82
+ * ...) will always have *something* pnpm hasn't seen before, and pinning every one of
83
+ * them here would just break again on their next update - so strict-dep-builds is
84
+ * turned off the same way.
85
+ */
86
+ function configurePnpm() {
87
+ const allowBuilds = JSON.stringify({ esbuild: false, sharp: true, 'better-sqlite3': false });
77
88
  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');
89
+ execSync(`pnpm config set --location=project --json allowBuilds '${allowBuilds}'`);
90
+ execSync('pnpm config set --location=project strict-dep-builds false');
91
+ logger.info('[✓] Configured pnpm-workspace.yaml: allowBuilds (sharp) and strict-dep-builds (false)');
91
92
  }
92
93
  catch (error) {
93
- logger.error('Failed to update package.json for pnpm:', error);
94
+ logger.error('Failed to configure pnpm:', error);
94
95
  }
95
96
  }
@@ -1,2 +1,11 @@
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
+ /**
4
+ * True when `metaUrl` (pass the caller's own `import.meta.url` — this can't be one shared
5
+ * value, it depends on which file is asking) is running from inside an installed `rimecms`
6
+ * package (node_modules/rimecms/...), false when running from rime's own repo. Deliberately
7
+ * filesystem-based rather than an env var: a shell dotenv plugin auto-loading this repo's own
8
+ * .env (which used to set IS_RIME_REPO=true) into every child process — including an unrelated
9
+ * consumer app's dev server — made that value unreliable; this can't leak the same way.
10
+ */
11
+ export declare function isInstalledDependency(metaUrl: string): boolean;
@@ -1,2 +1,13 @@
1
1
  export const OUTPUT_DIR = '+rime.generated';
2
2
  export const INPUT_DIR = '+rime';
3
+ /**
4
+ * True when `metaUrl` (pass the caller's own `import.meta.url` — this can't be one shared
5
+ * value, it depends on which file is asking) is running from inside an installed `rimecms`
6
+ * package (node_modules/rimecms/...), false when running from rime's own repo. Deliberately
7
+ * filesystem-based rather than an env var: a shell dotenv plugin auto-loading this repo's own
8
+ * .env (which used to set IS_RIME_REPO=true) into every child process — including an unrelated
9
+ * consumer app's dev server — made that value unreliable; this can't leak the same way.
10
+ */
11
+ export function isInstalledDependency(metaUrl) {
12
+ return metaUrl.includes('/node_modules/rimecms/');
13
+ }
@@ -0,0 +1,24 @@
1
+ export type RuntimeRegistryEntry = {
2
+ client: string;
3
+ server: string;
4
+ };
5
+ export type RuntimeRegistry = Map<string, RuntimeRegistryEntry>;
6
+ /**
7
+ * Builds the `$rime/<name>` → { browser file, server file } lookup table.
8
+ *
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
18
+ *
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"
23
+ */
24
+ export declare function buildRuntimeRegistry(): RuntimeRegistry;
@@ -0,0 +1,93 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { isInstalledDependency, OUTPUT_DIR } from '../../constants.js';
5
+ /**
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).
10
+ */
11
+ function findDistRoot(startDir) {
12
+ let dir = startDir;
13
+ while (!fs.existsSync(path.join(dir, 'package.json'))) {
14
+ const parent = path.dirname(dir);
15
+ if (parent === dir) {
16
+ throw new Error(`$rime: could not locate rimecms's package root above ${startDir}`);
17
+ }
18
+ dir = parent;
19
+ }
20
+ return path.join(dir, 'dist');
21
+ }
22
+ /** `module.ts` when scanning TS source, `module.js` when scanning a built `dist/`. */
23
+ function findModuleFile(dir, baseName) {
24
+ for (const ext of ['.ts', '.js']) {
25
+ const candidate = path.join(dir, `${baseName}${ext}`);
26
+ if (fs.existsSync(candidate))
27
+ return candidate;
28
+ }
29
+ return null;
30
+ }
31
+ /**
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`.
35
+ *
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.
40
+ */
41
+ function scanModulePairs(root) {
42
+ const registry = new Map();
43
+ if (!fs.existsSync(root))
44
+ return registry;
45
+ const walk = (dir) => {
46
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
47
+ if (!entry.isDirectory() || entry.name === OUTPUT_DIR)
48
+ continue;
49
+ const fullPath = path.join(dir, entry.name);
50
+ const client = findModuleFile(fullPath, 'module');
51
+ const server = findModuleFile(fullPath, 'module.server');
52
+ if (client && server) {
53
+ const key = path.relative(root, fullPath).split(path.sep).join('/');
54
+ registry.set(key, { client, server });
55
+ }
56
+ walk(fullPath);
57
+ }
58
+ };
59
+ walk(root);
60
+ return registry;
61
+ }
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
+ }
@@ -6,15 +6,15 @@ 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
+ import { buildRuntimeRegistry } from '../runtime/index.server.js';
17
+ const IS_RIME_REPO = !isInstalledDependency(import.meta.url);
18
18
  // Relation type
19
19
  const relationValueType = `
20
20
  export type RelationValue<T> =
@@ -110,13 +110,47 @@ const templateRegister = (config) => {
110
110
  };
111
111
  const templateDeclareVirtualModule = () => [
112
112
  `declare module '$rime/config' {`,
113
- ...(IS_PACKAGE_DEV ? ['\t// eslint-disable-next-line no-restricted-imports'] : []),
113
+ ...(IS_RIME_REPO ? ['\t// eslint-disable-next-line no-restricted-imports'] : []),
114
114
  `\texport * from '${PACKAGE_NAME}/config/server';`,
115
115
  `}`,
116
116
  `declare module '$rime/schema' {`,
117
117
  `\texport * from '$lib/+rime.generated/schema.server.js';`,
118
- `}`
118
+ `}`,
119
+ ...templateDeclareFieldModules()
119
120
  ].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
+ }
120
154
  /**
121
155
  * Generates type definitions for image sizes
122
156
  * @returns A string containing the type definition for image sizes
@@ -155,17 +189,7 @@ export async function generateTypesString(config) {
155
189
  * @returns An array of string containing fields type definitions
156
190
  */
157
191
  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;
192
+ return fields.map((field) => field.use.generateType()).filter(Boolean);
169
193
  };
170
194
  const buildblocksTypes = async (fields) => {
171
195
  for (const field of fields) {
@@ -314,10 +338,10 @@ export async function generateTypesString(config) {
314
338
  }
315
339
  }`;
316
340
  const content = [
317
- ...(IS_PACKAGE_DEV ? ['// eslint-disable-next-line no-restricted-imports'] : []),
341
+ ...(IS_RIME_REPO ? ['// eslint-disable-next-line no-restricted-imports'] : []),
318
342
  `import '${PACKAGE_NAME}';`,
319
343
  `import type { Session } from 'better-auth';`,
320
- ...(IS_PACKAGE_DEV ? ['// eslint-disable-next-line no-restricted-imports'] : []),
344
+ ...(IS_RIME_REPO ? ['// eslint-disable-next-line no-restricted-imports'] : []),
321
345
  typeImports,
322
346
  '',
323
347
  relationValueType,