rimecms 0.31.3 → 0.31.5

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 (88) hide show
  1. package/README.md +6 -207
  2. package/dist/adapter-sqlite/auth.server.d.ts +1 -1
  3. package/dist/adapter-sqlite/generate-schema/templates.server.d.ts +2 -2
  4. package/dist/adapter-sqlite/generate-schema/templates.server.js +3 -1
  5. package/dist/adapter-sqlite/generate-schema/write.server.js +7 -3
  6. package/dist/adapter-sqlite/transform.server.js +1 -1
  7. package/dist/core/collections/upload/util/path.d.ts +2 -1
  8. package/dist/core/collections/upload/util/path.js +3 -3
  9. package/dist/core/config/auth/better-auth-hooks.server.d.ts +1 -1
  10. package/dist/core/config/auth/better-auth-hooks.server.js +1 -2
  11. package/dist/core/config/auth/better-auth-permissions.d.ts +27 -67
  12. package/dist/core/config/auth/better-auth.server.d.ts +649 -481
  13. package/dist/core/config/auth/better-auth.server.js +2 -1
  14. package/dist/core/config/server/write.server.js +5 -0
  15. package/dist/core/constant.d.ts +10 -0
  16. package/dist/core/constant.js +22 -0
  17. package/dist/core/dev/cli/build/templates.js +2 -0
  18. package/dist/core/dev/cli/clear/index.server.js +5 -5
  19. package/dist/core/dev/cli/init/index.server.js +15 -28
  20. package/dist/core/dev/cli/init/templates.d.ts +14 -0
  21. package/dist/core/dev/cli/init/templates.js +58 -3
  22. package/dist/core/dev/constants.d.ts +20 -7
  23. package/dist/core/dev/constants.js +58 -10
  24. package/dist/core/dev/generate/routes/common.server.js +36 -33
  25. package/dist/core/dev/generate/routes/index.server.d.ts +6 -3
  26. package/dist/core/dev/generate/routes/index.server.js +13 -7
  27. package/dist/core/dev/generate/routes/util.server.d.ts +14 -6
  28. package/dist/core/dev/generate/routes/util.server.js +17 -6
  29. package/dist/core/dev/generate/sanitize/index.server.js +3 -3
  30. package/dist/core/dev/generate/types/index.server.js +3 -2
  31. package/dist/core/dev/vite/index.server.js +4 -8
  32. package/dist/core/ensure.server.d.ts +2 -2
  33. package/dist/core/ensure.server.js +17 -8
  34. package/dist/core/handlers/auth.server.js +18 -11
  35. package/dist/core/handlers/routes.server.d.ts +1 -1
  36. package/dist/core/handlers/routes.server.js +4 -3
  37. package/dist/core/operations/hooks/before-read/populate-url.server.js +2 -1
  38. package/dist/core/plugins/cache/handler.server.js +1 -1
  39. package/dist/core/rime.server.d.ts +1302 -966
  40. package/dist/core/rime.server.js +6 -0
  41. package/dist/fields/number/component/number.css +0 -2
  42. package/dist/fields/relation/component/upload/upload.css +9 -0
  43. package/dist/fields/rich-text/component/drag-handle/drag-handle.css +0 -1
  44. package/dist/fields/rich-text/component/styles/rich-text.css +7 -0
  45. package/dist/fields/rich-text/core/extensions/drag-handle/helpers/findNextElementFromCursor.d.ts +1 -1
  46. package/dist/fields/rich-text/core/svelte/node-view-wrapper.svelte.d.ts +12 -5
  47. package/dist/panel/components/sections/collection/header/SelectUI.svelte.d.ts +3 -3
  48. package/dist/panel/components/sections/collection/list/row/Row.svelte +2 -1
  49. package/dist/panel/components/sections/collection/tree/CollectionTreeNode.svelte +2 -1
  50. package/dist/panel/components/sections/document/AuthAPIKeyDialog.svelte.d.ts +3 -3
  51. package/dist/panel/components/sections/document/AuthFooter.svelte +2 -1
  52. package/dist/panel/components/sections/document/Versions.svelte +1 -1
  53. package/dist/panel/components/sections/live/Consumer.svelte.d.ts +11 -4
  54. package/dist/panel/components/sections/live/LiveEdit.svelte.d.ts +13 -6
  55. package/dist/panel/components/sections/live/LiveFloatingUI.svelte.d.ts +3 -3
  56. package/dist/panel/components/ui/calendar/calendar.css +0 -11
  57. package/dist/panel/components/ui/card-resource/card-resource.svelte +2 -1
  58. package/dist/panel/components/ui/command/command-group.css +0 -1
  59. package/dist/panel/components/ui/label/label.css +1 -0
  60. package/dist/panel/components/ui/nav/Nav.svelte +1 -1
  61. package/dist/panel/components/ui/nav/NavItem.svelte +3 -2
  62. package/dist/panel/components/ui/nav/UserButton.svelte +3 -3
  63. package/dist/panel/components/ui/radio-group/radio-group.css +0 -2
  64. package/dist/panel/components/ui/scroll-area/scroll-area.css +4 -0
  65. package/dist/panel/components/ui/sheet/sheet.css +16 -0
  66. package/dist/panel/components/ui/tabs/tabs.css +0 -7
  67. package/dist/panel/context/documentForm.svelte.js +5 -6
  68. package/dist/panel/navigation.d.ts +7 -2
  69. package/dist/panel/navigation.js +11 -6
  70. package/dist/panel/pages/area/actions.server.js +4 -3
  71. package/dist/panel/pages/area/load.server.js +3 -2
  72. package/dist/panel/pages/auth/forgot-password/ForgotPassword.svelte +2 -1
  73. package/dist/panel/pages/auth/reset-password/ResetPassword.svelte +2 -1
  74. package/dist/panel/pages/auth/sign-in/SignIn.svelte +3 -2
  75. package/dist/panel/pages/auth/sign-in/actions.server.js +2 -2
  76. package/dist/panel/pages/auth/sign-in/load.server.d.ts +1 -1
  77. package/dist/panel/pages/auth/sign-in/load.server.js +2 -2
  78. package/dist/panel/pages/collection/load.server.js +9 -4
  79. package/dist/panel/pages/collection-document/actions.server.js +5 -3
  80. package/dist/panel/pages/collection-document/load.server.js +10 -6
  81. package/dist/panel/pages/dashboard/load.server.js +5 -4
  82. package/dist/panel/pages/live/Live.svelte +3 -7
  83. package/dist/panel/style/index.css +0 -6
  84. package/dist/panel/util/auth.js +1 -1
  85. package/dist/panel/util/url.d.ts +30 -1
  86. package/dist/panel/util/url.js +36 -2
  87. package/package.json +8 -6
  88. package/dist/panel/components/ui/dropdown-menu/index.d.ts +0 -23
@@ -1,6 +1,7 @@
1
1
  import { dev } from '$app/environment';
2
2
  import { env } from '$env/dynamic/public';
3
- import { admin as adminPlugin, apiKey } from 'better-auth/plugins';
3
+ import { apiKey } from '@better-auth/api-key';
4
+ import { admin as adminPlugin } from 'better-auth/plugins';
4
5
  import { betterAuthAfterHook, betterAuthBeforeHook } from './better-auth-hooks.server.js';
5
6
  import { accessControl, admin, staff, user } from './better-auth-permissions.js';
6
7
  export function getBaseAuthConfig(ctx) {
@@ -1,5 +1,6 @@
1
1
  import { flatten } from 'flat';
2
2
  import cache from '../../dev/cache/index.server.js';
3
+ import { CONFIG_DIR } from '../../dev/constants.js';
3
4
  /**
4
5
  * We actually need to serialize config values that will trigger
5
6
  * types, routes, or schema generations, meaning that for now
@@ -59,6 +60,10 @@ const writeMemo = (config) => {
59
60
  throw new Error(`Config error : Unable to parse value for key ${key}: ${err.message}`);
60
61
  }
61
62
  })
63
+ // Not part of the user's config object, but generated output (routes, schema, hooks) is
64
+ // relative to it — a RIME_CONFIG_DIR change must invalidate the memo just like a config
65
+ // change does, even when the config content itself is byte-identical.
66
+ .concat(`CONFIG_DIR:${CONFIG_DIR}`)
62
67
  .join('\n');
63
68
  const cached = cache.get('config');
64
69
  if (cached !== memoStr) {
@@ -1,3 +1,13 @@
1
+ /** The generated panel route always lives at this fixed matcher-folder id — the literal
2
+ * folder name never changes, only which segment value src/params/panel.ts's matcher
3
+ * accepts (see PANEL_ROUTE in core/dev/constants.ts). Route ids never carry the resolved
4
+ * param value, so these are safe to compare against event.route.id without leaking the
5
+ * configured secret. */
6
+ export declare const PANEL_ROUTE_ID = "/(rime)/[panel=panel]";
7
+ export declare const PANEL_SIGN_IN_ROUTE_ID = "/(rime)/[panel=panel]/sign-in";
8
+ export declare const PANEL_FORGOT_PASSWORD_ROUTE_ID = "/(rime)/[panel=panel]/forgot-password";
9
+ export declare const PANEL_RESET_PASSWORD_ROUTE_ID = "/(rime)/[panel=panel]/reset-password";
10
+ export declare function isPublicPanelAuthRoute(routeId: string | null): boolean;
1
11
  export declare const VERSIONS_STATUS: {
2
12
  readonly DRAFT: "draft";
3
13
  readonly PUBLISHED: "published";
@@ -1,3 +1,25 @@
1
+ /** The generated panel route always lives at this fixed matcher-folder id — the literal
2
+ * folder name never changes, only which segment value src/params/panel.ts's matcher
3
+ * accepts (see PANEL_ROUTE in core/dev/constants.ts). Route ids never carry the resolved
4
+ * param value, so these are safe to compare against event.route.id without leaking the
5
+ * configured secret. */
6
+ export const PANEL_ROUTE_ID = '/(rime)/[panel=panel]';
7
+ export const PANEL_SIGN_IN_ROUTE_ID = `${PANEL_ROUTE_ID}/sign-in`;
8
+ export const PANEL_FORGOT_PASSWORD_ROUTE_ID = `${PANEL_ROUTE_ID}/forgot-password`;
9
+ export const PANEL_RESET_PASSWORD_ROUTE_ID = `${PANEL_ROUTE_ID}/reset-password`;
10
+ /** sign-in, forgot-password and reset-password all have to stay reachable by an
11
+ * unauthenticated visitor (someone locked out, or clicking a reset link from email) even
12
+ * though they now live under the same hidden [panel=panel] segment as the rest of the
13
+ * panel — this is the auth-gate's "public, no-session-required" exception, broadened from
14
+ * sign-in alone to cover all three. */
15
+ const PANEL_PUBLIC_AUTH_ROUTE_IDS = new Set([
16
+ PANEL_SIGN_IN_ROUTE_ID,
17
+ PANEL_FORGOT_PASSWORD_ROUTE_ID,
18
+ PANEL_RESET_PASSWORD_ROUTE_ID
19
+ ]);
20
+ export function isPublicPanelAuthRoute(routeId) {
21
+ return !!routeId && PANEL_PUBLIC_AUTH_ROUTE_IDS.has(routeId);
22
+ }
1
23
  export const VERSIONS_STATUS = {
2
24
  DRAFT: 'draft',
3
25
  PUBLISHED: 'published'
@@ -1,9 +1,11 @@
1
+ import { PANEL_ROUTE } from '../../constants.js';
1
2
  import { randomId } from '../../../../util/random.js';
2
3
  export const envProduction = () => `# BETTER-AUTH
3
4
  BETTER_AUTH_SECRET=${randomId(32)}
4
5
 
5
6
  # RIME
6
7
  PUBLIC_RIME_URL=http://localhost:3000
8
+ RIME_PANEL_ROUTE=${PANEL_ROUTE}
7
9
  RIME_LOG_LEVEL=ERROR
8
10
  RIME_LOG_TO_FILE=true
9
11
  RIME_CACHE_ENABLED=true
@@ -2,14 +2,14 @@ import { RIME_DEV_CACHE_DIR } from '../../../constant.server.js';
2
2
  import { logger } from '../../../logger/index.server.js';
3
3
  import { rmSync } from 'node:fs';
4
4
  import path from 'node:path';
5
- import { INPUT_DIR, OUTPUT_DIR } from '../../constants.js';
5
+ import { CONFIG_DIR, GENERATED_DIR } from '../../constants.js';
6
6
  import { prompt } from '../util/prompt.server.js';
7
7
  const clearMessage = `Are you sure you want to delete all related rime files (Y/n):
8
8
  - ./static/medias
9
9
  - ./db
10
10
  - ./src/routes/(rime)
11
- - ./src/lib/${INPUT_DIR}
12
- - ./src/lib/${OUTPUT_DIR}
11
+ - ./${CONFIG_DIR}
12
+ - ./${GENERATED_DIR}
13
13
  - ./src/app.generated.d.ts
14
14
  - ./src/rime.generated.d.ts
15
15
  - ./src/rime.modules.generated.d.ts
@@ -28,8 +28,8 @@ export const clear = async (args) => {
28
28
  // Remove directories
29
29
  rmSync(RIME_DEV_CACHE_DIR, { recursive: true, force: true });
30
30
  rmSync(path.join('src', 'routes', '(rime)'), { recursive: true, force: true });
31
- rmSync(path.join('src', 'lib', INPUT_DIR), { recursive: true, force: true });
32
- rmSync(path.join('src', 'lib', OUTPUT_DIR), { recursive: true, force: true });
31
+ rmSync(CONFIG_DIR, { recursive: true, force: true });
32
+ rmSync(GENERATED_DIR, { recursive: true, force: true });
33
33
  rmSync(path.join('db'), { recursive: true, force: true });
34
34
  rmSync(path.join('static', 'medias'), { recursive: true, force: true });
35
35
  // Remove files
@@ -9,7 +9,7 @@ import { cp, mkdir } from 'fs/promises';
9
9
  import fs from 'node:fs';
10
10
  import path from 'path';
11
11
  import { fileURLToPath } from 'url';
12
- import { INPUT_DIR } from '../../constants.js';
12
+ import { CONFIG_DIR, OUTPUT_DIR, PANEL_ROUTE } from '../../constants.js';
13
13
  import { generate } from '../generate/index.server.js';
14
14
  import { installDependencies } from '../util/package-manager.server.js';
15
15
  import { getPackageInfoByKey } from '../util/package.server.js';
@@ -25,6 +25,8 @@ export const init = async ({ force, name: incomingName, skipInstall }) => {
25
25
  const envUpdates = {
26
26
  BETTER_AUTH_SECRET: randomId(32),
27
27
  PUBLIC_RIME_URL: 'http://localhost:5173',
28
+ RIME_CONFIG_DIR: CONFIG_DIR,
29
+ RIME_PANEL_ROUTE: PANEL_ROUTE,
28
30
  '# RIME_SMTP_USER': 'user@mail.com',
29
31
  '# RIME_SMTP_PASSWORD': 'supersecret',
30
32
  '# RIME_SMTP_PORT': '465',
@@ -53,7 +55,7 @@ export const init = async ({ force, name: incomingName, skipInstall }) => {
53
55
  }
54
56
  }
55
57
  function setConfig(name) {
56
- const configDirPath = path.resolve(root, 'src/lib', INPUT_DIR);
58
+ const configDirPath = path.resolve(root, CONFIG_DIR);
57
59
  const configPath = path.join(configDirPath, 'rime.config.server.ts');
58
60
  if (!existsSync(configPath)) {
59
61
  if (!existsSync(configDirPath)) {
@@ -83,21 +85,24 @@ export const init = async ({ force, name: incomingName, skipInstall }) => {
83
85
  return;
84
86
  }
85
87
  let gitignoreContent = readFileSync(gitignorePath, 'utf-8');
88
+ // OUTPUT_DIR is derived from RIME_CONFIG_DIR, so its first character isn't always '+' —
89
+ // escape every regex metacharacter generically instead of hand-picking one to backslash.
90
+ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
86
91
  const updates = [
87
- '\\.cache',
92
+ '.cache',
88
93
  '/logs',
89
94
  '/db',
90
- '\\+rime.generated',
95
+ OUTPUT_DIR,
91
96
  'src/app.generated.d.ts',
92
97
  'src/rime.generated.d.ts'
93
98
  ];
94
99
  if (!gitignoreContent.includes('# rime'))
95
100
  gitignoreContent += '\n# rime';
96
101
  for (const line of updates) {
97
- const exists = gitignoreContent.match(new RegExp(`^${line}`, 'm'));
102
+ const exists = gitignoreContent.match(new RegExp(`^${escapeRegExp(line)}`, 'm'));
98
103
  if (!exists) {
99
104
  // Add new value if doesn't exist
100
- gitignoreContent += `\n${line.replace('\\', '')}`;
105
+ gitignoreContent += `\n${line}`;
101
106
  }
102
107
  }
103
108
  writeFileSync(gitignorePath, gitignoreContent);
@@ -108,10 +113,11 @@ export const init = async ({ force, name: incomingName, skipInstall }) => {
108
113
  if (!existsSync(drizzleConfigPath)) {
109
114
  writeFileSync(drizzleConfigPath, templates.drizzleConfig(name.toString()));
110
115
  logger.info('[✓] Drizzle config added');
116
+ return;
111
117
  }
112
- else {
113
- logger.info('[✓] Drizzle config already exists (skip)');
114
- }
118
+ logger.info(templates.regenerateDrizzleConfig(root)
119
+ ? '[✓] Drizzle config schema path updated'
120
+ : '[✓] Drizzle config already exists (skip)');
115
121
  }
116
122
  function configureVite() {
117
123
  const configPath = path.resolve(root, 'vite.config.ts');
@@ -182,23 +188,6 @@ export const init = async ({ force, name: incomingName, skipInstall }) => {
182
188
  }
183
189
  return null;
184
190
  }
185
- function setHooks() {
186
- const hooksPath = path.join(root, 'src', 'hooks.server.ts');
187
- const srcDir = path.join(root, 'src');
188
- // Check if file exists
189
- if (!existsSync(hooksPath)) {
190
- // Ensure src directory exists
191
- if (!existsSync(srcDir)) {
192
- mkdirSync(srcDir, { recursive: true });
193
- }
194
- // Create hooks.server.ts with template content
195
- writeFileSync(hooksPath, templates.hooks(), 'utf-8');
196
- logger.info('[✓] hooks.server.ts created');
197
- }
198
- else {
199
- logger.info('[✓] hooks.server.ts already exists (skip)');
200
- }
201
- }
202
191
  async function copyAssets() {
203
192
  try {
204
193
  const currentDir = path.dirname(fileURLToPath(import.meta.url));
@@ -219,7 +208,6 @@ export const init = async ({ force, name: incomingName, skipInstall }) => {
219
208
  setConfig(name);
220
209
  setDatabase();
221
210
  setDrizzle(name);
222
- setHooks();
223
211
  configureVite();
224
212
  await copyAssets();
225
213
  !skipInstall && installDependencies();
@@ -244,7 +232,6 @@ export const init = async ({ force, name: incomingName, skipInstall }) => {
244
232
  setConfig(name);
245
233
  setDatabase();
246
234
  setDrizzle(name);
247
- setHooks();
248
235
  configureVite();
249
236
  await copyAssets();
250
237
  !skipInstall && installDependencies();
@@ -2,3 +2,17 @@ export declare const env: () => string;
2
2
  export declare const defaultConfig: (name: string) => string;
3
3
  export declare const drizzleConfig: (name: string) => string;
4
4
  export declare const hooks: () => string;
5
+ /** Writes src/hooks.server.ts if missing. If it already exists, patches only the config
6
+ * import line if it's stale (e.g. after RIME_CONFIG_DIR changes the import path) — the rest
7
+ * of the file may carry real customizations (e.g. an appended handler), so this never
8
+ * touches anything else. Called both at `rime init` and whenever the dev server detects a
9
+ * config change, so the import path never goes stale between the two. Returns whether it
10
+ * wrote anything. */
11
+ export declare function regenerateHooks(root?: string): boolean;
12
+ /** Patches drizzle.config.ts's schema path line if it's stale (e.g. after RIME_CONFIG_DIR
13
+ * changes) — only that one line, since the rest of the file may carry real customizations
14
+ * (verbose, custom fields, etc.). Must run before generateSchema()'s own drizzle-kit
15
+ * generate/migrate shell-out, which reads this file from disk. No-op if the file doesn't
16
+ * exist — creating it from scratch needs a project name, which is `rime init`'s job
17
+ * (setDrizzle() in init/index.server.ts). Returns whether it patched anything. */
18
+ export declare function regenerateDrizzleConfig(root?: string): boolean;
@@ -1,8 +1,18 @@
1
1
  import { randomId } from '../../../../util/random.js';
2
- import { configImportPaths, OUTPUT_DIR } from '../../constants.js';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { CONFIG_DIR, GENERATED_DIR, PANEL_ROUTE, configImportPaths } from '../../constants.js';
3
5
  const PACKAGE = 'rimecms';
4
6
  export const env = () => `BETTER_AUTH_SECRET=${randomId(32)}
5
7
  PUBLIC_RIME_URL=http://localhost:5173
8
+ RIME_CONFIG_DIR=${CONFIG_DIR}
9
+
10
+ # Panel URL segment (e.g. /panel/...). Change it to hide the panel from
11
+ # automated CMS-admin scanners — requests to the old segment 404 instead of
12
+ # revealing a login page. Run 'rime generate' (or restart 'rime dev') after
13
+ # changing this — the dev server only regenerates routes when it detects a
14
+ # change under RIME_CONFIG_DIR, not on every restart.
15
+ RIME_PANEL_ROUTE=${PANEL_ROUTE}
6
16
 
7
17
  # RIME_CACHE_ENABLED=false
8
18
  # RIME_SMTP_USER=user@mail.com
@@ -40,7 +50,7 @@ export const drizzleConfig = (name) => `
40
50
  import { defineConfig, type Config } from 'drizzle-kit';
41
51
 
42
52
  export const config: Config = {
43
- schema: './src/lib/${OUTPUT_DIR}/schema.server.ts',
53
+ schema: './${GENERATED_DIR}/schema.server.ts',
44
54
  out: './db',
45
55
  strict: false,
46
56
  dialect: 'sqlite',
@@ -53,9 +63,54 @@ export default defineConfig(config);
53
63
  `;
54
64
  // Regenerated fresh by rime init every time (setHooks() only skips if the file already
55
65
  // exists, and `rime clear`/useConfig.js delete it first) — safe to make mode-aware.
66
+ const HOOKS_DIR = path.resolve(process.cwd(), 'src');
56
67
  export const hooks = () => `import { sequence } from '@sveltejs/kit/hooks';
57
68
  import { handlers } from '${PACKAGE}/server';
58
- import config from '.${configImportPaths().server.replace('$lib', '/lib')}';
69
+ import config from '${configImportPaths(HOOKS_DIR).server}';
59
70
 
60
71
  export const handle = sequence(...(await handlers(config)));
61
72
  `;
73
+ const HOOKS_CONFIG_IMPORT_LINE_REGEX = /^import config from ['"][^'"]*['"];\s*$/m;
74
+ /** Writes src/hooks.server.ts if missing. If it already exists, patches only the config
75
+ * import line if it's stale (e.g. after RIME_CONFIG_DIR changes the import path) — the rest
76
+ * of the file may carry real customizations (e.g. an appended handler), so this never
77
+ * touches anything else. Called both at `rime init` and whenever the dev server detects a
78
+ * config change, so the import path never goes stale between the two. Returns whether it
79
+ * wrote anything. */
80
+ export function regenerateHooks(root = process.cwd()) {
81
+ const hooksPath = path.join(root, 'src', 'hooks.server.ts');
82
+ const srcDir = path.join(root, 'src');
83
+ if (!existsSync(hooksPath)) {
84
+ if (!existsSync(srcDir)) {
85
+ mkdirSync(srcDir, { recursive: true });
86
+ }
87
+ writeFileSync(hooksPath, hooks(), 'utf-8');
88
+ return true;
89
+ }
90
+ const expectedImportLine = `import config from '${configImportPaths(HOOKS_DIR).server}';`;
91
+ const content = readFileSync(hooksPath, 'utf-8');
92
+ if (HOOKS_CONFIG_IMPORT_LINE_REGEX.test(content) && !content.includes(expectedImportLine)) {
93
+ writeFileSync(hooksPath, content.replace(HOOKS_CONFIG_IMPORT_LINE_REGEX, expectedImportLine));
94
+ return true;
95
+ }
96
+ return false;
97
+ }
98
+ const DRIZZLE_SCHEMA_LINE_REGEX = /^\s*schema:\s*['"][^'"]*['"],?\s*$/m;
99
+ /** Patches drizzle.config.ts's schema path line if it's stale (e.g. after RIME_CONFIG_DIR
100
+ * changes) — only that one line, since the rest of the file may carry real customizations
101
+ * (verbose, custom fields, etc.). Must run before generateSchema()'s own drizzle-kit
102
+ * generate/migrate shell-out, which reads this file from disk. No-op if the file doesn't
103
+ * exist — creating it from scratch needs a project name, which is `rime init`'s job
104
+ * (setDrizzle() in init/index.server.ts). Returns whether it patched anything. */
105
+ export function regenerateDrizzleConfig(root = process.cwd()) {
106
+ const drizzleConfigPath = path.join(root, 'drizzle.config.ts');
107
+ if (!existsSync(drizzleConfigPath))
108
+ return false;
109
+ const expectedSchemaLine = ` schema: './${GENERATED_DIR}/schema.server.ts',`;
110
+ const content = readFileSync(drizzleConfigPath, 'utf-8');
111
+ if (DRIZZLE_SCHEMA_LINE_REGEX.test(content) && !content.includes(expectedSchemaLine)) {
112
+ writeFileSync(drizzleConfigPath, content.replace(DRIZZLE_SCHEMA_LINE_REGEX, expectedSchemaLine));
113
+ return true;
114
+ }
115
+ return false;
116
+ }
@@ -1,15 +1,28 @@
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(): {
1
+ export declare const CONFIG_DIR: string;
2
+ export declare const GENERATED_DIR: string;
3
+ export declare const INPUT_DIR: string;
4
+ export declare const OUTPUT_DIR: string;
5
+ /** The panel's URL segment (e.g. "panel" -> /panel/...). Kept as a bare segment, never a
6
+ * PUBLIC_-prefixed env var: SvelteKit ships every PUBLIC_ value into the JS bundle of every
7
+ * page (including the public site), which would defeat the point of letting an operator hide
8
+ * this path from automated CMS-admin scanners. */
9
+ export declare const PANEL_ROUTE: string;
10
+ /** Turns an absolute path into an import specifier relative to `fromDir`, always prefixed
11
+ * with `./` or `../` so it never gets mistaken for a bare package specifier. */
12
+ export declare function relativeImportSpecifier(fromDir: string, toAbsolutePath: string): string;
13
+ export declare function generatedConfigServerPath(): string;
14
+ /** Where the panel/live route templates (common.server.ts) and the generated hooks.server.ts
15
+ * should import the client/server config from — a relative import specifier computed from
16
+ * `fromDir` (the absolute directory of the file doing the importing), so it resolves whether
17
+ * GENERATED_DIR lives under src/lib or elsewhere. One source of truth instead of hardcoding the
18
+ * path (or relying on the $lib alias, which only ever points at src/lib) at each call site. */
19
+ export declare function configImportPaths(fromDir: string): {
7
20
  client: string;
8
21
  server: string;
9
22
  };
10
23
  /** Same idea as configImportPaths — one source of truth for where the generated schema
11
24
  * lives instead of hardcoding the path at each call site. */
12
- export declare function schemaPath(root?: string): string;
25
+ export declare function schemaPath(projectRoot?: string): string;
13
26
  /**
14
27
  * True when `metaUrl` (pass the caller's own `import.meta.url` — this can't be one shared
15
28
  * value, it depends on which file is asking) is running from inside an installed `rimecms`
@@ -1,19 +1,67 @@
1
+ import dotenv from 'dotenv';
1
2
  import path from 'node:path';
2
- export const OUTPUT_DIR = '+rime.generated';
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() {
3
+ import { isValidSlug } from '../../util/string.js';
4
+ // Must run before RIME_CONFIG_DIR is read below. ES import hoisting evaluates this module's
5
+ // top-level code before any *importing* file's own top-level statements — including a
6
+ // dotenv.config() call there — so loading .env here, first, is the only way every entry point
7
+ // (CLI, dev-server, drizzle.config.ts, etc.) reliably sees it. override:true so .env is the
8
+ // explicit source of truth, not whatever a shell plugin (autoenv, direnv, ...) already exported
9
+ // from some other project's .env. The only dotenv.config() call in the codebase — a second one
10
+ // elsewhere would race this one.
11
+ dotenv.config({ override: true });
12
+ const root = process.cwd();
13
+ const SRC_ROOT = path.resolve(root, 'src');
14
+ export const CONFIG_DIR = process.env.RIME_CONFIG_DIR || 'src/+rime';
15
+ export const GENERATED_DIR = `${CONFIG_DIR}.generated`;
16
+ export const INPUT_DIR = path.basename(CONFIG_DIR);
17
+ export const OUTPUT_DIR = `${INPUT_DIR}.generated`;
18
+ /** The panel's URL segment (e.g. "panel" -> /panel/...). Kept as a bare segment, never a
19
+ * PUBLIC_-prefixed env var: SvelteKit ships every PUBLIC_ value into the JS bundle of every
20
+ * page (including the public site), which would defeat the point of letting an operator hide
21
+ * this path from automated CMS-admin scanners. */
22
+ export const PANEL_ROUTE = (process.env.RIME_PANEL_ROUTE || 'panel').replace(/^\/+|\/+$/g, '');
23
+ if (!isValidSlug(PANEL_ROUTE)) {
24
+ throw new Error(`RIME_PANEL_ROUTE must be a single URL segment (letters, numbers, "_"/"-", starting with a letter) — got "${PANEL_ROUTE}"`);
25
+ }
26
+ const RESERVED_DIRS = new Set(['lib', 'routes', 'static', 'params'].map((d) => path.resolve(SRC_ROOT, d)));
27
+ for (const dir of [CONFIG_DIR, GENERATED_DIR]) {
28
+ const resolved = path.resolve(root, dir);
29
+ if (resolved === SRC_ROOT || !resolved.startsWith(SRC_ROOT + path.sep)) {
30
+ throw new Error(`RIME_CONFIG_DIR must resolve to a subdirectory of ${SRC_ROOT} (got "${dir}")`);
31
+ }
32
+ if (RESERVED_DIRS.has(resolved)) {
33
+ throw new Error(`RIME_CONFIG_DIR must not be src/lib, src/routes, src/static, or src/params (got "${dir}")`);
34
+ }
35
+ }
36
+ /** Turns an absolute path into an import specifier relative to `fromDir`, always prefixed
37
+ * with `./` or `../` so it never gets mistaken for a bare package specifier. */
38
+ export function relativeImportSpecifier(fromDir, toAbsolutePath) {
39
+ const rel = path.relative(fromDir, toAbsolutePath).split(path.sep).join('/');
40
+ return rel.startsWith('.') ? rel : `./${rel}`;
41
+ }
42
+ /** Root-relative filesystem path (posix-style) to a file inside the generated config dir —
43
+ * for Vite's ssrLoadModule, which wants a real file path, not an import specifier. */
44
+ function generatedFilePath(file) {
45
+ return path.relative(root, path.resolve(root, GENERATED_DIR, file)).split(path.sep).join('/');
46
+ }
47
+ export function generatedConfigServerPath() {
48
+ return generatedFilePath('rime.config.server.ts');
49
+ }
50
+ /** Where the panel/live route templates (common.server.ts) and the generated hooks.server.ts
51
+ * should import the client/server config from — a relative import specifier computed from
52
+ * `fromDir` (the absolute directory of the file doing the importing), so it resolves whether
53
+ * GENERATED_DIR lives under src/lib or elsewhere. One source of truth instead of hardcoding the
54
+ * path (or relying on the $lib alias, which only ever points at src/lib) at each call site. */
55
+ export function configImportPaths(fromDir) {
8
56
  return {
9
- client: `$lib/${OUTPUT_DIR}/rime.config.js`,
10
- server: `$lib/${OUTPUT_DIR}/rime.config.server.js`
57
+ client: relativeImportSpecifier(fromDir, path.resolve(root, GENERATED_DIR, 'rime.config.js')),
58
+ server: relativeImportSpecifier(fromDir, path.resolve(root, GENERATED_DIR, 'rime.config.server.js'))
11
59
  };
12
60
  }
13
61
  /** Same idea as configImportPaths — one source of truth for where the generated schema
14
62
  * 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');
63
+ export function schemaPath(projectRoot = process.cwd()) {
64
+ return path.resolve(projectRoot, GENERATED_DIR, 'schema.server.ts');
17
65
  }
18
66
  /**
19
67
  * True when `metaUrl` (pass the caller's own `import.meta.url` — this can't be one shared