@objectql/cli 1.8.4 → 1.9.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 (80) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/database-push.d.ts +5 -0
  3. package/dist/commands/database-push.js +15 -0
  4. package/dist/commands/database-push.js.map +1 -0
  5. package/dist/commands/dev.d.ts +2 -0
  6. package/dist/commands/dev.js +94 -6
  7. package/dist/commands/dev.js.map +1 -1
  8. package/dist/commands/doctor.d.ts +4 -0
  9. package/dist/commands/doctor.js +37 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/init.js +31 -30
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/serve.d.ts +2 -0
  14. package/dist/commands/serve.js +122 -46
  15. package/dist/commands/serve.js.map +1 -1
  16. package/dist/commands/start.d.ts +1 -0
  17. package/dist/commands/start.js +15 -0
  18. package/dist/commands/start.js.map +1 -1
  19. package/dist/index.js +173 -210
  20. package/dist/index.js.map +1 -1
  21. package/package.json +13 -7
  22. package/templates/hello-world/.vscode/extensions.json +7 -0
  23. package/templates/hello-world/CHANGELOG.md +41 -0
  24. package/templates/hello-world/README.md +29 -0
  25. package/templates/hello-world/package.json +24 -0
  26. package/templates/hello-world/src/index.ts +58 -0
  27. package/templates/hello-world/tsconfig.json +10 -0
  28. package/templates/starter/.vscode/extensions.json +7 -0
  29. package/{CHANGELOG.md → templates/starter/CHANGELOG.md} +36 -42
  30. package/templates/starter/README.md +17 -0
  31. package/templates/starter/__tests__/projects-hooks-actions.test.ts +490 -0
  32. package/templates/starter/jest.config.js +16 -0
  33. package/templates/starter/package.json +52 -0
  34. package/templates/starter/src/README.pages.md +110 -0
  35. package/templates/starter/src/demo.app.yml +4 -0
  36. package/templates/starter/src/i18n/zh-CN/projects.json +22 -0
  37. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.data.yml +18 -0
  38. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.object.yml +156 -0
  39. package/templates/starter/src/modules/projects/project_approval.workflow.yml +51 -0
  40. package/templates/starter/src/modules/projects/projects.action.ts +472 -0
  41. package/templates/starter/src/modules/projects/projects.data.yml +13 -0
  42. package/templates/starter/src/modules/projects/projects.hook.ts +339 -0
  43. package/templates/starter/src/modules/projects/projects.object.yml +148 -0
  44. package/templates/starter/src/modules/projects/projects.permission.yml +141 -0
  45. package/templates/starter/src/modules/projects/projects.validation.yml +37 -0
  46. package/templates/starter/src/modules/tasks/tasks.data.yml +23 -0
  47. package/templates/starter/src/modules/tasks/tasks.object.yml +34 -0
  48. package/templates/starter/src/modules/tasks/tasks.permission.yml +167 -0
  49. package/templates/starter/src/seed.ts +55 -0
  50. package/templates/starter/src/types/index.ts +3 -0
  51. package/templates/starter/src/types/kitchen_sink.ts +101 -0
  52. package/templates/starter/src/types/projects.ts +49 -0
  53. package/templates/starter/src/types/tasks.ts +33 -0
  54. package/templates/starter/tsconfig.json +11 -0
  55. package/templates/starter/tsconfig.tsbuildinfo +1 -0
  56. package/AI_EXAMPLES.md +0 -154
  57. package/AI_IMPLEMENTATION_SUMMARY.md +0 -509
  58. package/AI_TUTORIAL.md +0 -144
  59. package/IMPLEMENTATION_SUMMARY.md +0 -437
  60. package/USAGE_EXAMPLES.md +0 -951
  61. package/__tests__/commands.test.ts +0 -426
  62. package/jest.config.js +0 -19
  63. package/src/commands/ai.ts +0 -509
  64. package/src/commands/build.ts +0 -98
  65. package/src/commands/dev.ts +0 -23
  66. package/src/commands/format.ts +0 -110
  67. package/src/commands/generate.ts +0 -135
  68. package/src/commands/i18n.ts +0 -303
  69. package/src/commands/init.ts +0 -191
  70. package/src/commands/lint.ts +0 -98
  71. package/src/commands/migrate.ts +0 -314
  72. package/src/commands/new.ts +0 -221
  73. package/src/commands/repl.ts +0 -120
  74. package/src/commands/serve.ts +0 -96
  75. package/src/commands/start.ts +0 -100
  76. package/src/commands/sync.ts +0 -328
  77. package/src/commands/test.ts +0 -98
  78. package/src/index.ts +0 -356
  79. package/tsconfig.json +0 -15
  80. package/tsconfig.tsbuildinfo +0 -1
@@ -1,110 +0,0 @@
1
- import * as path from 'path';
2
- import * as fs from 'fs';
3
- import chalk from 'chalk';
4
- import * as yaml from 'js-yaml';
5
- import glob from 'fast-glob';
6
-
7
- // Naming convention regex
8
- const VALID_NAME_REGEX = /^[a-z][a-z0-9_]*$/;
9
-
10
- interface FormatOptions {
11
- dir?: string;
12
- check?: boolean;
13
- }
14
-
15
- /**
16
- * Format command - formats metadata files using Prettier
17
- */
18
- export async function format(options: FormatOptions) {
19
- console.log(chalk.blue('šŸŽØ Formatting ObjectQL metadata files...\n'));
20
-
21
- const rootDir = path.resolve(process.cwd(), options.dir || '.');
22
- let formattedCount = 0;
23
- let unchangedCount = 0;
24
- let errorCount = 0;
25
-
26
- // Load Prettier once at the start
27
- let prettier: any;
28
- try {
29
- prettier = await import('prettier');
30
- } catch (e) {
31
- console.error(chalk.red('āŒ Prettier is not installed. Install it with: npm install --save-dev prettier'));
32
- process.exit(1);
33
- }
34
-
35
- try {
36
- const files = await glob(['**/*.yml', '**/*.yaml'], {
37
- cwd: rootDir,
38
- ignore: ['node_modules/**', 'dist/**', 'build/**']
39
- });
40
-
41
- console.log(chalk.cyan(`Found ${files.length} YAML file(s)\n`));
42
-
43
- for (const file of files) {
44
- const filePath = path.join(rootDir, file);
45
-
46
- try {
47
- const content = fs.readFileSync(filePath, 'utf-8');
48
-
49
- // Parse to validate YAML
50
- yaml.load(content);
51
-
52
- // Format with Prettier
53
- const formatted = await prettier.format(content, {
54
- parser: 'yaml',
55
- printWidth: 80,
56
- tabWidth: 2,
57
- singleQuote: true
58
- });
59
-
60
- if (content !== formatted) {
61
- if (options.check) {
62
- console.log(chalk.yellow(` āš ļø ${file} needs formatting`));
63
- formattedCount++;
64
- } else {
65
- fs.writeFileSync(filePath, formatted, 'utf-8');
66
- console.log(chalk.green(` āœ… ${file}`));
67
- formattedCount++;
68
- }
69
- } else {
70
- unchangedCount++;
71
- if (!options.check) {
72
- console.log(chalk.gray(` āœ“ ${file}`));
73
- }
74
- }
75
- } catch (e: any) {
76
- console.error(chalk.red(` āŒ ${file}: ${e.message}`));
77
- errorCount++;
78
- }
79
- }
80
-
81
- console.log('');
82
-
83
- // Summary
84
- if (options.check) {
85
- if (formattedCount > 0) {
86
- console.log(chalk.yellow.bold(`āš ļø ${formattedCount} file(s) need formatting`));
87
- console.log(chalk.gray('Run without --check to format files\n'));
88
- process.exit(1);
89
- } else {
90
- console.log(chalk.green.bold('āœ… All files are properly formatted!\n'));
91
- }
92
- } else {
93
- console.log(chalk.cyan('Summary:'));
94
- console.log(chalk.green(` āœ… Formatted: ${formattedCount}`));
95
- console.log(chalk.gray(` āœ“ Unchanged: ${unchangedCount}`));
96
- if (errorCount > 0) {
97
- console.log(chalk.red(` āŒ Errors: ${errorCount}`));
98
- }
99
- console.log('');
100
-
101
- if (errorCount > 0) {
102
- process.exit(1);
103
- }
104
- }
105
-
106
- } catch (e: any) {
107
- console.error(chalk.red('āŒ Format failed:'), e.message);
108
- process.exit(1);
109
- }
110
- }
@@ -1,135 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { loadObjectConfigs } from '@objectql/platform-node';
4
- import { ObjectConfig, FieldConfig } from '@objectql/types';
5
-
6
- export async function generateTypes(sourceDir: string, outputDir: string) {
7
- console.log(`Searching for objects in ${sourceDir}...`);
8
-
9
- // Use Loader to get Merged Objects (Flat list)
10
- let schemas: Record<string, ObjectConfig>;
11
- try {
12
- schemas = loadObjectConfigs(sourceDir);
13
- } catch (e) {
14
- console.error('Failed to load object configs:', e);
15
- return;
16
- }
17
-
18
- if (Object.keys(schemas).length === 0) {
19
- console.log('No object files found.');
20
- return;
21
- }
22
-
23
- // Ensure output dir exists
24
- if (!fs.existsSync(outputDir)) {
25
- fs.mkdirSync(outputDir, { recursive: true });
26
- }
27
-
28
- const indexContent: string[] = [];
29
-
30
- for (const [name, schema] of Object.entries(schemas)) {
31
- try {
32
- const typeName = toPascalCase(name);
33
- const typeDefinition = generateInterface(typeName, schema);
34
-
35
- // Generate flat files based on Object Name
36
- // e.g. User.ts, CrmContact.ts
37
- // We lose original directory structure but gain correct merged types.
38
- // This is arguably better for "Generated Types" which are usually a flat library.
39
-
40
- const fileName = `${name}.ts`;
41
- const outPath = path.join(outputDir, fileName);
42
- fs.writeFileSync(outPath, typeDefinition);
43
- console.log(`Generated ${fileName}`);
44
-
45
- indexContent.push(`export * from './${name}';`);
46
- } catch (e) {
47
- console.error(`Failed to generate type for ${name}:`, e);
48
- }
49
- }
50
-
51
- // Generate index.ts
52
- fs.writeFileSync(path.join(outputDir, 'index.ts'), indexContent.join('\n'));
53
- console.log(`Generated types in ${outputDir}`);
54
- }
55
-
56
- function generateInterface(typeName: string, schema: ObjectConfig): string {
57
- const fields = schema.fields || {};
58
- const lines = [
59
- `// Auto-generated by ObjectQL. DO NOT EDIT.`,
60
- `import { ObjectDoc } from '@objectql/types';`, // Assuming a base type exists or we define it
61
- ``,
62
- `export interface ${typeName} extends ObjectDoc {`
63
- ];
64
-
65
- for (const [key, field] of Object.entries(fields)) {
66
- const fieldName = field.name || key;
67
- const isOptional = !field.required;
68
- const tsType = mapFieldTypeToTs(field);
69
-
70
- // Add JSDoc
71
- if (field.label || field.description) {
72
- lines.push(` /**`);
73
- if (field.label) lines.push(` * ${field.label}`);
74
- if (field.description) lines.push(` * ${field.description}`);
75
- lines.push(` */`);
76
- }
77
-
78
- lines.push(` ${fieldName}${isOptional ? '?' : ''}: ${tsType};`);
79
- }
80
-
81
- lines.push(`}`);
82
- lines.push(``);
83
- return lines.join('\n');
84
- }
85
-
86
- function mapFieldTypeToTs(field: FieldConfig): string {
87
- switch (field.type) {
88
- case 'text':
89
- case 'textarea':
90
- case 'markdown':
91
- case 'html':
92
- case 'email':
93
- case 'phone':
94
- case 'url':
95
- case 'password':
96
- case 'select': // Could be stricter if options are strings
97
- return 'string';
98
-
99
- case 'number':
100
- case 'currency':
101
- case 'percent':
102
- case 'auto_number':
103
- return 'number';
104
-
105
- case 'boolean':
106
- return 'boolean';
107
-
108
- case 'date':
109
- case 'datetime':
110
- case 'time':
111
- return 'Date | string';
112
-
113
- case 'vector':
114
- return 'number[]';
115
-
116
- case 'file':
117
- case 'image':
118
- return field.multiple ? 'any[]' : 'any'; // Simplified for now
119
-
120
- case 'object':
121
- case 'location':
122
- return 'any';
123
-
124
- case 'lookup':
125
- case 'master_detail':
126
- return 'string | number'; // The ID
127
-
128
- default:
129
- return 'any';
130
- }
131
- }
132
-
133
- function toPascalCase(str: string): string {
134
- return str.replace(/(^\w|_\w)/g, m => m.replace('_', '').toUpperCase());
135
- }
@@ -1,303 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import chalk from 'chalk';
4
- import * as yaml from 'js-yaml';
5
- import glob from 'fast-glob';
6
-
7
- interface I18nExtractOptions {
8
- source?: string;
9
- output?: string;
10
- lang?: string;
11
- }
12
-
13
- interface I18nInitOptions {
14
- lang: string;
15
- baseDir?: string;
16
- }
17
-
18
- interface I18nValidateOptions {
19
- lang: string;
20
- baseDir?: string;
21
- baseLang?: string;
22
- }
23
-
24
- /**
25
- * Extract translatable strings from metadata files and create i18n files
26
- */
27
- export async function i18nExtract(options: I18nExtractOptions) {
28
- const sourceDir = path.resolve(process.cwd(), options.source || '.');
29
- const outputDir = path.resolve(process.cwd(), options.output || './src/i18n');
30
- const lang = options.lang || 'en';
31
-
32
- console.log(chalk.blue('🌐 Extracting translatable strings...'));
33
- console.log(chalk.gray(`Source: ${sourceDir}`));
34
- console.log(chalk.gray(`Output: ${outputDir}/${lang}\n`));
35
-
36
- try {
37
- // Find all metadata files
38
- const files = await glob('**/*.{object,view,form,page,action,permission,validation,workflow,report,menu}.yml', {
39
- cwd: sourceDir,
40
- ignore: ['node_modules/**', 'dist/**', 'i18n/**']
41
- });
42
-
43
- console.log(chalk.gray(`Found ${files.length} metadata files`));
44
-
45
- const translations: Record<string, any> = {};
46
-
47
- for (const file of files) {
48
- const filePath = path.join(sourceDir, file);
49
- const content = fs.readFileSync(filePath, 'utf-8');
50
- const data = yaml.load(content) as any;
51
-
52
- if (!data) continue;
53
-
54
- // Extract object name from filename
55
- const objectName = path.basename(file).split('.')[0];
56
-
57
- // Extract translatable fields
58
- const objectTranslations: any = {};
59
-
60
- if (data.label) {
61
- objectTranslations.label = data.label;
62
- }
63
-
64
- if (data.description) {
65
- objectTranslations.description = data.description;
66
- }
67
-
68
- // Extract field labels
69
- if (data.fields) {
70
- objectTranslations.fields = {};
71
- for (const [fieldName, fieldConfig] of Object.entries(data.fields) as any) {
72
- const fieldTrans: any = {};
73
-
74
- if (fieldConfig.label) {
75
- fieldTrans.label = fieldConfig.label;
76
- }
77
-
78
- if (fieldConfig.description) {
79
- fieldTrans.description = fieldConfig.description;
80
- }
81
-
82
- if (fieldConfig.help_text) {
83
- fieldTrans.help_text = fieldConfig.help_text;
84
- }
85
-
86
- // Extract select options
87
- if (fieldConfig.options && Array.isArray(fieldConfig.options)) {
88
- fieldTrans.options = {};
89
- for (const option of fieldConfig.options) {
90
- if (option.value && option.label) {
91
- fieldTrans.options[option.value] = option.label;
92
- }
93
- }
94
- }
95
-
96
- if (Object.keys(fieldTrans).length > 0) {
97
- objectTranslations.fields[fieldName] = fieldTrans;
98
- }
99
- }
100
- }
101
-
102
- // Extract action labels
103
- if (data.actions) {
104
- objectTranslations.actions = {};
105
- for (const [actionName, actionConfig] of Object.entries(data.actions) as any) {
106
- const actionTrans: any = {};
107
- if (actionConfig.label) {
108
- actionTrans.label = actionConfig.label;
109
- }
110
- if (actionConfig.confirm_text) {
111
- actionTrans.confirm_text = actionConfig.confirm_text;
112
- }
113
- if (Object.keys(actionTrans).length > 0) {
114
- objectTranslations.actions[actionName] = actionTrans;
115
- }
116
- }
117
- }
118
-
119
- // Extract validation messages
120
- if (data.validation?.rules) {
121
- objectTranslations.validation = {};
122
- for (const rule of data.validation.rules) {
123
- if (rule.name && rule.message) {
124
- objectTranslations.validation[rule.name] = rule.message;
125
- }
126
- }
127
- }
128
-
129
- if (Object.keys(objectTranslations).length > 0) {
130
- translations[objectName] = objectTranslations;
131
- }
132
- }
133
-
134
- // Write translation files
135
- const langDir = path.join(outputDir, lang);
136
- if (!fs.existsSync(langDir)) {
137
- fs.mkdirSync(langDir, { recursive: true });
138
- }
139
-
140
- // Write one file per object
141
- for (const [objectName, objectTranslations] of Object.entries(translations)) {
142
- const outputFile = path.join(langDir, `${objectName}.json`);
143
- fs.writeFileSync(outputFile, JSON.stringify(objectTranslations, null, 4), 'utf-8');
144
- console.log(chalk.green(`āœ“ ${objectName}.json`));
145
- }
146
-
147
- console.log(chalk.green(`\nāœ… Extracted translations to ${langDir}`));
148
- console.log(chalk.gray(`Total: ${Object.keys(translations).length} files`));
149
-
150
- } catch (error: any) {
151
- console.error(chalk.red(`āŒ Failed to extract translations: ${error.message}`));
152
- process.exit(1);
153
- }
154
- }
155
-
156
- /**
157
- * Initialize i18n structure for a new language
158
- */
159
- export async function i18nInit(options: I18nInitOptions) {
160
- const baseDir = path.resolve(process.cwd(), options.baseDir || './src/i18n');
161
- const { lang } = options;
162
-
163
- console.log(chalk.blue(`🌐 Initializing i18n for language: ${lang}`));
164
-
165
- // Validate language code
166
- if (!/^[a-z]{2}(-[A-Z]{2})?$/.test(lang)) {
167
- console.error(chalk.red('āŒ Invalid language code. Use format: en, zh-CN, etc.'));
168
- process.exit(1);
169
- }
170
-
171
- const langDir = path.join(baseDir, lang);
172
-
173
- if (fs.existsSync(langDir)) {
174
- console.error(chalk.red(`āŒ Language directory already exists: ${langDir}`));
175
- process.exit(1);
176
- }
177
-
178
- try {
179
- fs.mkdirSync(langDir, { recursive: true });
180
-
181
- // Create a sample translation file
182
- const sampleTranslation = {
183
- _meta: {
184
- language: lang,
185
- created: new Date().toISOString()
186
- }
187
- };
188
-
189
- const sampleFile = path.join(langDir, 'common.json');
190
- fs.writeFileSync(sampleFile, JSON.stringify(sampleTranslation, null, 4), 'utf-8');
191
-
192
- console.log(chalk.green(`āœ… Initialized i18n for ${lang}`));
193
- console.log(chalk.gray(`Directory: ${langDir}`));
194
- console.log(chalk.gray(`\nNext steps:`));
195
- console.log(chalk.gray(` 1. Run: objectql i18n extract --lang ${lang}`));
196
- console.log(chalk.gray(` 2. Translate the JSON files in ${langDir}`));
197
-
198
- } catch (error: any) {
199
- console.error(chalk.red(`āŒ Failed to initialize i18n: ${error.message}`));
200
- process.exit(1);
201
- }
202
- }
203
-
204
- /**
205
- * Validate translation completeness
206
- */
207
- export async function i18nValidate(options: I18nValidateOptions) {
208
- const baseDir = path.resolve(process.cwd(), options.baseDir || './src/i18n');
209
- const { lang, baseLang = 'en' } = options;
210
-
211
- console.log(chalk.blue(`🌐 Validating translations for ${lang} against ${baseLang}...\n`));
212
-
213
- const baseLangDir = path.join(baseDir, baseLang);
214
- const targetLangDir = path.join(baseDir, lang);
215
-
216
- if (!fs.existsSync(baseLangDir)) {
217
- console.error(chalk.red(`āŒ Base language directory not found: ${baseLangDir}`));
218
- process.exit(1);
219
- }
220
-
221
- if (!fs.existsSync(targetLangDir)) {
222
- console.error(chalk.red(`āŒ Target language directory not found: ${targetLangDir}`));
223
- process.exit(1);
224
- }
225
-
226
- try {
227
- const baseFiles = fs.readdirSync(baseLangDir).filter(f => f.endsWith('.json'));
228
- const targetFiles = fs.readdirSync(targetLangDir).filter(f => f.endsWith('.json'));
229
-
230
- let totalMissing = 0;
231
- let totalFiles = 0;
232
-
233
- for (const file of baseFiles) {
234
- totalFiles++;
235
- const basePath = path.join(baseLangDir, file);
236
- const targetPath = path.join(targetLangDir, file);
237
-
238
- if (!fs.existsSync(targetPath)) {
239
- console.log(chalk.red(`āœ— ${file} - Missing file`));
240
- totalMissing++;
241
- continue;
242
- }
243
-
244
- const baseData = JSON.parse(fs.readFileSync(basePath, 'utf-8'));
245
- const targetData = JSON.parse(fs.readFileSync(targetPath, 'utf-8'));
246
-
247
- const missing = findMissingKeys(baseData, targetData);
248
-
249
- if (missing.length > 0) {
250
- console.log(chalk.yellow(`⚠ ${file} - ${missing.length} missing keys:`));
251
- for (const key of missing) {
252
- console.log(chalk.gray(` - ${key}`));
253
- }
254
- totalMissing += missing.length;
255
- } else {
256
- console.log(chalk.green(`āœ“ ${file} - Complete`));
257
- }
258
- }
259
-
260
- // Check for extra files in target
261
- const extraFiles = targetFiles.filter(f => !baseFiles.includes(f));
262
- if (extraFiles.length > 0) {
263
- console.log(chalk.yellow(`\n⚠ Extra files in ${lang}:`));
264
- for (const file of extraFiles) {
265
- console.log(chalk.gray(` - ${file}`));
266
- }
267
- }
268
-
269
- console.log(chalk.blue(`\nšŸ“Š Summary:`));
270
- console.log(chalk.gray(`Total files: ${totalFiles}`));
271
- console.log(totalMissing > 0
272
- ? chalk.yellow(`Missing translations: ${totalMissing}`)
273
- : chalk.green('All translations complete āœ“')
274
- );
275
-
276
- } catch (error: any) {
277
- console.error(chalk.red(`āŒ Failed to validate translations: ${error.message}`));
278
- process.exit(1);
279
- }
280
- }
281
-
282
- function findMissingKeys(base: any, target: any, prefix = ''): string[] {
283
- const missing: string[] = [];
284
-
285
- for (const key in base) {
286
- const fullKey = prefix ? `${prefix}.${key}` : key;
287
-
288
- if (!(key in target)) {
289
- missing.push(fullKey);
290
- continue;
291
- }
292
-
293
- if (typeof base[key] === 'object' && base[key] !== null && !Array.isArray(base[key])) {
294
- if (typeof target[key] === 'object' && target[key] !== null) {
295
- missing.push(...findMissingKeys(base[key], target[key], fullKey));
296
- } else {
297
- missing.push(fullKey);
298
- }
299
- }
300
- }
301
-
302
- return missing;
303
- }