rimecms 0.28.0 → 0.28.1

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.
@@ -1,10 +1,12 @@
1
1
  import { isAuthConfig } from '../../collections/auth/util.js';
2
2
  import cache from '../../dev/cache/index.server.js';
3
3
  import { isFormField } from '../../fields/util.js';
4
+ import { logger } from '../../logger/index.server';
4
5
  import { isBlocksFieldRaw } from '../../../fields/blocks/index.js';
5
6
  import { isGroupFieldRaw } from '../../../fields/group/index.js';
7
+ import { isRelationField } from '../../../fields/relation';
6
8
  import { isSelectField } from '../../../fields/select/index.js';
7
- import { isTabsFieldRaw } from '../../../fields/tabs/index.js';
9
+ import { isTabsFieldRaw } from '../../../fields/tabs';
8
10
  import { isTreeFieldRaw } from '../../../fields/tree/index.js';
9
11
  function hasDuplicates(arr) {
10
12
  return [...new Set(arr.filter((e, i, a) => a.indexOf(e) !== i))];
@@ -45,11 +47,11 @@ function hasUsersSlug(config) {
45
47
  const validateFields = (config) => {
46
48
  let errors = [];
47
49
  for (const collection of config.collections || []) {
48
- const collectionErrors = validateDocumentFields(collection);
50
+ const collectionErrors = validateDocumentFields(collection, config);
49
51
  errors = [...errors, ...collectionErrors];
50
52
  }
51
53
  for (const area of config.areas || []) {
52
- const collectionErrors = validateDocumentFields(area);
54
+ const collectionErrors = validateDocumentFields(area, config);
53
55
  errors = [...errors, ...collectionErrors];
54
56
  }
55
57
  return errors;
@@ -57,12 +59,12 @@ const validateFields = (config) => {
57
59
  /**
58
60
  *
59
61
  */
60
- const validateDocumentFields = (config) => {
62
+ const validateDocumentFields = (documentConfig, config) => {
61
63
  const errors = [];
62
- const isCollection = (config) => config.type === 'collection';
63
- const isAuth = isCollection(config) && isAuthConfig(config);
64
+ const isCollection = (documentConfig) => documentConfig.type === 'collection';
65
+ const isAuth = isCollection(documentConfig) && isAuthConfig(documentConfig);
64
66
  const registeredBlocks = {};
65
- const fieldsCompiled = config.fields.map((f) => f.compile());
67
+ const fieldsCompiled = documentConfig.fields.map((f) => f.compile());
66
68
  if (isAuth) {
67
69
  const rolesField = fieldsCompiled
68
70
  .filter(isFormField)
@@ -73,28 +75,37 @@ const validateDocumentFields = (config) => {
73
75
  .filter(isFormField)
74
76
  .find((f) => f.name === 'email' && f.type === 'email');
75
77
  if (!rolesField)
76
- errors.push(`Field roles is missing in collection ${config.slug}`);
77
- if (!emailField && config.auth.type !== 'apiKey')
78
- errors.push(`Field email is missing in collection ${config.slug}`);
78
+ errors.push(`Field roles is missing in collection ${documentConfig.slug}`);
79
+ if (!emailField && documentConfig.auth.type !== 'apiKey')
80
+ errors.push(`Field email is missing in collection ${documentConfig.slug}`);
79
81
  if (!nameField)
80
- errors.push(`Field name is missing in collection ${config.slug}`);
82
+ errors.push(`Field name is missing in collection ${documentConfig.slug}`);
81
83
  if (!rolesField.many)
82
84
  errors.push(`Field roles must have "many" enabled : select('roles').options(...).many(), even with a single option`);
83
85
  }
84
86
  const validateBlockField = (fields, blockType) => {
85
87
  const reserved = ['path', 'type', 'ownerId', 'position', 'locale'];
86
88
  for (const key of reserved) {
87
- if (fields.map((f) => f.name).filter((name) => name === key).length > 1) {
89
+ if (fields
90
+ .filter(isFormField)
91
+ .map((f) => f.name)
92
+ .filter((name) => name === key).length > 1) {
88
93
  errors.push(`${key} is a reserved field in blocks (block ${blockType})`);
89
94
  }
90
95
  }
91
96
  };
92
- const validateFormFields = (fields) => {
97
+ const validateRelationField = (field) => {
98
+ const collectionsSlugs = (config.collections || []).map((c) => c.slug);
99
+ if (!collectionsSlugs.includes(field.relationTo)) {
100
+ errors.push(`Relation field ${field.name} references unknown collection ${field.relationTo}, in ${documentConfig.type} ${documentConfig.slug}`);
101
+ }
102
+ };
103
+ const validateFields = (fields) => {
93
104
  // Check for field name duplication at this level
94
- const duplicates = hasDuplicates(fields.map((f) => f.name));
105
+ const duplicates = hasDuplicates(fields.filter(isFormField).map((f) => f.name));
95
106
  if (duplicates.length) {
96
107
  for (const duplicate of duplicates) {
97
- errors.push(`Duplicate field '${duplicate}' in ${config.type} '${config.slug}'`);
108
+ errors.push(`Duplicate field '${duplicate}' in ${documentConfig.type} '${documentConfig.slug}'`);
98
109
  }
99
110
  }
100
111
  function validateFieldName(name) {
@@ -104,16 +115,33 @@ const validateDocumentFields = (config) => {
104
115
  // Check if string matches pattern and doesn't contain spaces or hyphens
105
116
  return pattern.test(name) && !name.includes('-') && !name.includes(' ');
106
117
  }
118
+ function validateTabs(field) {
119
+ const duplicates = hasDuplicates(field.tabs.map((t) => t.name));
120
+ if (duplicates.length) {
121
+ errors.push(`Dupplicate tab name ${duplicates} in ${documentConfig.slug}`);
122
+ }
123
+ }
107
124
  for (const field of fields) {
125
+ // Recursive check first into Tabs since tabs are not Formfields
126
+ if (isTabsFieldRaw(field)) {
127
+ validateTabs(field);
128
+ for (const tab of field.tabs) {
129
+ validateFields(tab.fields);
130
+ }
131
+ }
132
+ // If field is not a Formfield eg. Separator then continue
133
+ if (!isFormField(field)) {
134
+ continue;
135
+ }
108
136
  // Check that a field wich has field._root = true is not localized
109
137
  if ('_root' in field && field._root && field.localized) {
110
- errors.push(`Field ${field.name} of ${config.type} ${config.slug} with _root = true, can't be localized`);
138
+ errors.push(`Field ${field.name} of ${documentConfig.type} ${documentConfig.slug} with _root = true, can't be localized`);
111
139
  }
112
140
  // Check for malformed field.name
113
141
  if (!validateFieldName(field.name)) {
114
- errors.push(`Field ${field.name} of ${config.type} ${config.slug} should be camelCase`);
142
+ errors.push(`Field ${field.name} of ${documentConfig.type} ${documentConfig.slug} should be camelCase`);
115
143
  }
116
- // Recursive check into Blocks Groups and Tabs
144
+ // Recursive check into Blocks
117
145
  if (isBlocksFieldRaw(field)) {
118
146
  for (const block of field.blocks) {
119
147
  if (block.name in registeredBlocks) {
@@ -125,24 +153,25 @@ const validateDocumentFields = (config) => {
125
153
  else {
126
154
  registeredBlocks[block.name] = block;
127
155
  }
128
- validateFormFields(block.fields.filter(isFormField));
156
+ validateFields(block.fields.filter(isFormField));
129
157
  validateBlockField(block.fields.filter(isFormField), block.name);
130
158
  }
159
+ // Recursive check into Tree
131
160
  }
132
161
  else if (isTreeFieldRaw(field)) {
133
- validateFormFields(field.fields.filter(isFormField));
134
- }
135
- else if (isTabsFieldRaw(field)) {
136
- for (const tab of field.tabs) {
137
- validateFormFields(tab.fields.filter(isFormField));
138
- }
162
+ validateFields(field.fields.filter(isFormField));
163
+ // Recursive check into Tabs
139
164
  }
140
165
  else if (isGroupFieldRaw(field)) {
141
- validateFormFields(field.fields.filter(isFormField));
166
+ validateFields(field.fields.filter(isFormField));
167
+ // Check relation field
168
+ }
169
+ else if (isRelationField(field)) {
170
+ validateRelationField(field);
142
171
  }
143
172
  }
144
173
  };
145
- validateFormFields(config.fields.map((f) => f.compile()).filter(isFormField));
174
+ validateFields(documentConfig.fields.map((f) => f.compile()));
146
175
  return errors;
147
176
  };
148
177
  const hasDatabase = (config) => {
@@ -163,6 +192,7 @@ function validateAuthCollections(config) {
163
192
  return errors;
164
193
  }
165
194
  function validate(config) {
195
+ logger.debug('Validating config...');
166
196
  const validateFunctions = [
167
197
  hasDuplicateSlug,
168
198
  hasUsersSlug,
@@ -174,9 +204,11 @@ function validate(config) {
174
204
  const errors = isValid(config);
175
205
  if (errors.length) {
176
206
  cache.clear();
177
- throw new Error('Config error : ' + errors[0]);
207
+ errors.map((err) => logger.error(err));
208
+ return false;
178
209
  }
179
210
  }
211
+ logger.debug('Config is valid');
180
212
  return true;
181
213
  }
182
214
  export default validate;
@@ -1 +1 @@
1
- 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\npolka()\n\t.use(serve)\n\t.use(handler)\n\t.listen(3000, () => {\n\t\tconsole.log(\"server running\");\n\t});\n";
1
+ 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";
@@ -4,10 +4,13 @@ import { handler } from './build/handler.js';
4
4
 
5
5
  const serve = serveStatic('./static');
6
6
 
7
+ const port = process.env.PORT || 3000;
8
+ const host = process.env.HOST || '127.0.0.1';
9
+
7
10
  polka()
8
11
  .use(serve)
9
12
  .use(handler)
10
- .listen(3000, () => {
11
- console.log("server running");
13
+ .listen(port, host, () => {
14
+ console.log(\`server running on \${host}:\${port}\`);
12
15
  });
13
16
  `;
@@ -1,4 +1,3 @@
1
- import cache from '../../cache/index.server.js';
2
1
  import { sanitize } from '../../generate/sanitize/index.server.js';
3
2
  import { ensureGeneratedConfig, ensureUserConfigExist } from '../../../ensure.server.js';
4
3
  import { logger } from '../../../logger/index.server.js';
@@ -62,18 +61,23 @@ export const generate = async (args) => {
62
61
  ensureUserConfigExist();
63
62
  await sanitizeConfig();
64
63
  const importPathJS = ensureGeneratedConfig();
65
- logger.info('Generate');
64
+ logger.info('Starting vite server...');
66
65
  const vite = await createServer();
67
- await vite.ssrLoadModule(importPathJS);
68
- while (cache.get('.init')) {
69
- await new Promise((resolve) => setTimeout(resolve, 200));
66
+ try {
67
+ const mod = await vite.ssrLoadModule(importPathJS);
68
+ // The generated config's default export is the createRime() promise;
69
+ // ssrLoadModule only awaits the module's synchronous evaluation, so we
70
+ // must await it directly to observe init failures (e.g. config validation).
71
+ await mod.default;
72
+ logger.info('[✓] Generation completed successfully');
73
+ }
74
+ finally {
75
+ await vite.close();
70
76
  }
71
- await vite.close();
72
- logger.info('[✓] Generation completed successfully');
73
77
  }
74
78
  const [error] = await trycatch(run);
75
79
  if (error) {
76
- logger.error('Error during generation:', error);
80
+ logger.error('[✗] Error during generation:', error);
77
81
  throw error;
78
82
  }
79
83
  };
@@ -30,8 +30,13 @@ program
30
30
  .option('-f, --force', 'Force generation, ignore cache, overwrite routes', false)
31
31
  .action(async (args) => {
32
32
  const generate = await import('./generate/index.server.js').then((m) => m.generate);
33
- generate({
34
- force: args.force
35
- });
33
+ try {
34
+ await generate({
35
+ force: args.force
36
+ });
37
+ }
38
+ catch {
39
+ process.exitCode = 1;
40
+ }
36
41
  });
37
42
  program.parse(process.argv);
@@ -36,15 +36,19 @@ export function rime() {
36
36
  }
37
37
  catch (error) {
38
38
  logger.error('Error while sanitizing config:', error.message);
39
- throw error;
39
+ return;
40
40
  }
41
41
  // Trigger generation
42
42
  try {
43
- await server.ssrLoadModule(`src/lib/${OUTPUT_DIR}/rime.config.server.ts`);
43
+ const mod = await server.ssrLoadModule(`src/lib/${OUTPUT_DIR}/rime.config.server.ts`);
44
+ // The config's default export is the createRime() promise; ssrLoadModule
45
+ // only awaits the module's synchronous evaluation, so we must await it
46
+ // directly to observe init errors (e.g. invalid config) here instead of
47
+ // letting them become unhandled rejections that crash the dev server.
48
+ await mod.default;
44
49
  }
45
50
  catch (error) {
46
51
  logger.error('Failed to reload the config', error.message);
47
- throw error;
48
52
  }
49
53
  }
50
54
  });
@@ -76,14 +76,7 @@ export declare function createRime<const C extends Config>(config: BuildConfig<C
76
76
  appName: string;
77
77
  baseURL: string;
78
78
  trustedOrigins: string[];
79
- isTrustedOrigin: (url: string, settings
80
- /**
81
- * Get a collection api
82
- * @example
83
- *
84
- * rime.collection('pages').find({ query: 'where[isHome][equals]=true' })
85
- */
86
- ?: {
79
+ isTrustedOrigin: (url: string, settings?: {
87
80
  allowRelativePaths: boolean;
88
81
  }) => boolean;
89
82
  oauthConfig: {
@@ -2089,14 +2082,7 @@ export declare function createRime<const C extends Config>(config: BuildConfig<C
2089
2082
  appName: string;
2090
2083
  baseURL: string;
2091
2084
  trustedOrigins: string[];
2092
- isTrustedOrigin: (url: string, settings
2093
- /**
2094
- * Get a collection api
2095
- * @example
2096
- *
2097
- * rime.collection('pages').find({ query: 'where[isHome][equals]=true' })
2098
- */
2099
- ?: {
2085
+ isTrustedOrigin: (url: string, settings?: {
2100
2086
  allowRelativePaths: boolean;
2101
2087
  }) => boolean;
2102
2088
  oauthConfig: {
@@ -1,6 +1,5 @@
1
1
  // import type { Adapter } from '../adapter-sqlite/index.server.js';
2
2
  import { dev } from '$app/environment';
3
- import devCache from './dev/cache/index.server.js';
4
3
  import { betterAuth } from 'better-auth';
5
4
  import { AreaAPI } from './areas/local-api.server.js';
6
5
  import { CollectionAPI } from './collections/local-api.server.js';
@@ -29,13 +28,18 @@ export async function createRime(config) {
29
28
  // Generate schema, types, routes
30
29
  if (dev) {
31
30
  const changed = writeMemo(config);
32
- const valid = changed && validate(config);
33
- if (changed && !valid)
31
+ const valid = validate(config);
32
+ if (!valid) {
34
33
  throw new RimeError('Config not valid');
35
- devCache.set('.init', new Date().toISOString());
36
- generateRoutes(config);
37
- await generateSchema(config);
38
- await generateTypes(config);
34
+ }
35
+ if (changed) {
36
+ generateRoutes(config);
37
+ await generateSchema(config);
38
+ await generateTypes(config);
39
+ }
40
+ else {
41
+ logger.debug('Nothing to generate');
42
+ }
39
43
  }
40
44
  // Create adapter, consume the generated schema
41
45
  const adapter = await createAdapter(configCtx);
@@ -53,9 +57,6 @@ export async function createRime(config) {
53
57
  // Register dictionaries for panel Language
54
58
  const dictionnaries = await registerTranslation(config.panel.language);
55
59
  i18n.init(dictionnaries);
56
- if (dev) {
57
- devCache.delete('.init');
58
- }
59
60
  /**
60
61
  * Function that define the locale to use in a request event
61
62
  * based on this priority, high to low :
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rimecms",
3
- "version": "0.28.0",
3
+ "version": "0.28.1",
4
4
  "homepage": "https://github.com/bienbiendev/rime",
5
5
  "scripts": {
6
6
  "dev": "vite dev",