@objectql/cli 1.8.4 → 1.9.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.
Files changed (98) 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 +15 -338
  20. package/dist/index.js.map +1 -1
  21. package/dist/register/ai.d.ts +2 -0
  22. package/dist/register/ai.js +24 -0
  23. package/dist/register/ai.js.map +1 -0
  24. package/dist/register/database.d.ts +2 -0
  25. package/dist/register/database.js +64 -0
  26. package/dist/register/database.js.map +1 -0
  27. package/dist/register/i18n.d.ts +2 -0
  28. package/dist/register/i18n.js +52 -0
  29. package/dist/register/i18n.js.map +1 -0
  30. package/dist/register/lifecycle.d.ts +2 -0
  31. package/dist/register/lifecycle.js +68 -0
  32. package/dist/register/lifecycle.js.map +1 -0
  33. package/dist/register/scaffold.d.ts +2 -0
  34. package/dist/register/scaffold.js +48 -0
  35. package/dist/register/scaffold.js.map +1 -0
  36. package/dist/register/tools.d.ts +2 -0
  37. package/dist/register/tools.js +49 -0
  38. package/dist/register/tools.js.map +1 -0
  39. package/package.json +13 -7
  40. package/templates/hello-world/.vscode/extensions.json +7 -0
  41. package/templates/hello-world/CHANGELOG.md +49 -0
  42. package/templates/hello-world/README.md +29 -0
  43. package/templates/hello-world/package.json +24 -0
  44. package/templates/hello-world/src/index.ts +58 -0
  45. package/templates/hello-world/tsconfig.json +10 -0
  46. package/templates/starter/.vscode/extensions.json +7 -0
  47. package/{CHANGELOG.md → templates/starter/CHANGELOG.md} +46 -42
  48. package/templates/starter/README.md +17 -0
  49. package/templates/starter/__tests__/projects-hooks-actions.test.ts +490 -0
  50. package/templates/starter/jest.config.js +16 -0
  51. package/templates/starter/package.json +52 -0
  52. package/templates/starter/src/README.pages.md +110 -0
  53. package/templates/starter/src/demo.app.yml +4 -0
  54. package/templates/starter/src/i18n/zh-CN/projects.json +22 -0
  55. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.data.yml +18 -0
  56. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.object.yml +156 -0
  57. package/templates/starter/src/modules/projects/project_approval.workflow.yml +51 -0
  58. package/templates/starter/src/modules/projects/projects.action.ts +472 -0
  59. package/templates/starter/src/modules/projects/projects.data.yml +13 -0
  60. package/templates/starter/src/modules/projects/projects.hook.ts +339 -0
  61. package/templates/starter/src/modules/projects/projects.object.yml +148 -0
  62. package/templates/starter/src/modules/projects/projects.permission.yml +141 -0
  63. package/templates/starter/src/modules/projects/projects.validation.yml +37 -0
  64. package/templates/starter/src/modules/tasks/tasks.data.yml +23 -0
  65. package/templates/starter/src/modules/tasks/tasks.object.yml +34 -0
  66. package/templates/starter/src/modules/tasks/tasks.permission.yml +167 -0
  67. package/templates/starter/src/seed.ts +55 -0
  68. package/templates/starter/src/types/index.ts +3 -0
  69. package/templates/starter/src/types/kitchen_sink.ts +101 -0
  70. package/templates/starter/src/types/projects.ts +49 -0
  71. package/templates/starter/src/types/tasks.ts +33 -0
  72. package/templates/starter/tsconfig.json +11 -0
  73. package/templates/starter/tsconfig.tsbuildinfo +1 -0
  74. package/AI_EXAMPLES.md +0 -154
  75. package/AI_IMPLEMENTATION_SUMMARY.md +0 -509
  76. package/AI_TUTORIAL.md +0 -144
  77. package/IMPLEMENTATION_SUMMARY.md +0 -437
  78. package/USAGE_EXAMPLES.md +0 -951
  79. package/__tests__/commands.test.ts +0 -426
  80. package/jest.config.js +0 -19
  81. package/src/commands/ai.ts +0 -509
  82. package/src/commands/build.ts +0 -98
  83. package/src/commands/dev.ts +0 -23
  84. package/src/commands/format.ts +0 -110
  85. package/src/commands/generate.ts +0 -135
  86. package/src/commands/i18n.ts +0 -303
  87. package/src/commands/init.ts +0 -191
  88. package/src/commands/lint.ts +0 -98
  89. package/src/commands/migrate.ts +0 -314
  90. package/src/commands/new.ts +0 -221
  91. package/src/commands/repl.ts +0 -120
  92. package/src/commands/serve.ts +0 -96
  93. package/src/commands/start.ts +0 -100
  94. package/src/commands/sync.ts +0 -328
  95. package/src/commands/test.ts +0 -98
  96. package/src/index.ts +0 -356
  97. package/tsconfig.json +0 -15
  98. package/tsconfig.tsbuildinfo +0 -1
@@ -1,426 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { newMetadata } from '../src/commands/new';
4
- import { i18nExtract, i18nInit, i18nValidate } from '../src/commands/i18n';
5
- import { syncDatabase } from '../src/commands/sync';
6
- import { build } from '../src/commands/build';
7
- import { lint } from '../src/commands/lint';
8
- import { format } from '../src/commands/format';
9
- import { ObjectQL } from '@objectql/core';
10
- import { SqlDriver } from '@objectql/driver-sql';
11
- import * as yaml from 'js-yaml';
12
-
13
- describe('CLI Commands', () => {
14
- const testDir = path.join(__dirname, '__test_output__');
15
-
16
- beforeEach(() => {
17
- // Create test directory
18
- if (!fs.existsSync(testDir)) {
19
- fs.mkdirSync(testDir, { recursive: true });
20
- }
21
- });
22
-
23
- afterEach(() => {
24
- // Clean up test directory
25
- if (fs.existsSync(testDir)) {
26
- fs.rmSync(testDir, { recursive: true, force: true });
27
- }
28
- });
29
-
30
- describe('new command', () => {
31
- it('should create an object file', async () => {
32
- await newMetadata({
33
- type: 'object',
34
- name: 'test_users',
35
- dir: testDir
36
- });
37
-
38
- const filePath = path.join(testDir, 'test_users.object.yml');
39
- expect(fs.existsSync(filePath)).toBe(true);
40
-
41
- const content = fs.readFileSync(filePath, 'utf-8');
42
- expect(content).toContain('label: Test Users');
43
- expect(content).toContain('type: text');
44
- });
45
-
46
- it('should create action with both yml and ts files', async () => {
47
- await newMetadata({
48
- type: 'action',
49
- name: 'test_action',
50
- dir: testDir
51
- });
52
-
53
- const ymlPath = path.join(testDir, 'test_action.action.yml');
54
- const tsPath = path.join(testDir, 'test_action.action.ts');
55
-
56
- expect(fs.existsSync(ymlPath)).toBe(true);
57
- expect(fs.existsSync(tsPath)).toBe(true);
58
-
59
- const tsContent = fs.readFileSync(tsPath, 'utf-8');
60
- expect(tsContent).toContain('action_test_action');
61
- expect(tsContent).toContain('ActionContext');
62
- });
63
-
64
- it('should create hook with both yml and ts files', async () => {
65
- await newMetadata({
66
- type: 'hook',
67
- name: 'test_hook',
68
- dir: testDir
69
- });
70
-
71
- const ymlPath = path.join(testDir, 'test_hook.hook.yml');
72
- const tsPath = path.join(testDir, 'test_hook.hook.ts');
73
-
74
- expect(fs.existsSync(ymlPath)).toBe(true);
75
- expect(fs.existsSync(tsPath)).toBe(true);
76
-
77
- const tsContent = fs.readFileSync(tsPath, 'utf-8');
78
- expect(tsContent).toContain('beforeInsert');
79
- expect(tsContent).toContain('afterInsert');
80
- });
81
-
82
- // Skip this test as it calls process.exit which causes test failures
83
- it.skip('should validate object name format', async () => {
84
- await expect(
85
- newMetadata({
86
- type: 'object',
87
- name: 'InvalidName', // Should be lowercase
88
- dir: testDir
89
- })
90
- ).rejects.toThrow();
91
- });
92
- });
93
-
94
- describe('i18n commands', () => {
95
- beforeEach(async () => {
96
- // Create a test object file
97
- await newMetadata({
98
- type: 'object',
99
- name: 'test_users',
100
- dir: testDir
101
- });
102
- });
103
-
104
- it('should extract translatable strings', async () => {
105
- await i18nExtract({
106
- source: testDir,
107
- output: path.join(testDir, 'i18n'),
108
- lang: 'en'
109
- });
110
-
111
- const i18nFile = path.join(testDir, 'i18n/en/test_users.json');
112
- expect(fs.existsSync(i18nFile)).toBe(true);
113
-
114
- const content = JSON.parse(fs.readFileSync(i18nFile, 'utf-8'));
115
- expect(content.label).toBe('Test Users');
116
- expect(content.fields.name.label).toBe('Name');
117
- });
118
-
119
- it('should initialize new language', async () => {
120
- await i18nInit({
121
- lang: 'zh-CN',
122
- baseDir: path.join(testDir, 'i18n')
123
- });
124
-
125
- const langDir = path.join(testDir, 'i18n/zh-CN');
126
- expect(fs.existsSync(langDir)).toBe(true);
127
-
128
- const commonFile = path.join(langDir, 'common.json');
129
- expect(fs.existsSync(commonFile)).toBe(true);
130
- });
131
-
132
- it('should validate translations', async () => {
133
- // Extract for base language
134
- await i18nExtract({
135
- source: testDir,
136
- output: path.join(testDir, 'i18n'),
137
- lang: 'en'
138
- });
139
-
140
- // Extract for target language
141
- await i18nInit({
142
- lang: 'zh-CN',
143
- baseDir: path.join(testDir, 'i18n')
144
- });
145
- await i18nExtract({
146
- source: testDir,
147
- output: path.join(testDir, 'i18n'),
148
- lang: 'zh-CN'
149
- });
150
-
151
- // Should not throw - validation passes
152
- await expect(
153
- i18nValidate({
154
- lang: 'zh-CN',
155
- baseDir: path.join(testDir, 'i18n'),
156
- baseLang: 'en'
157
- })
158
- ).resolves.not.toThrow();
159
- });
160
- });
161
-
162
- describe('sync command', () => {
163
- let app: ObjectQL;
164
- let configPath: string;
165
-
166
- beforeEach(async () => {
167
- // Create a test SQLite database with sample schema
168
- const dbPath = path.join(testDir, `test_${Date.now()}.db`);
169
- const driver = new SqlDriver({
170
- client: 'sqlite3',
171
- connection: { filename: dbPath },
172
- useNullAsDefault: true,
173
- pool: {
174
- min: 1,
175
- max: 1 // Single connection for test
176
- }
177
- });
178
-
179
- app = new ObjectQL({
180
- datasources: { default: driver }
181
- });
182
-
183
- // Register sample objects
184
- app.registerObject({
185
- name: 'users',
186
- label: 'Users',
187
- fields: {
188
- username: { type: 'string', required: true, unique: true },
189
- email: { type: 'email', required: true },
190
- is_active: { type: 'boolean', defaultValue: true }
191
- }
192
- });
193
-
194
- app.registerObject({
195
- name: 'posts',
196
- label: 'Posts',
197
- fields: {
198
- title: { type: 'text', required: true },
199
- content: { type: 'textarea' },
200
- author_id: { type: 'lookup', reference_to: 'users' },
201
- published_at: { type: 'datetime' }
202
- }
203
- });
204
-
205
- await app.init();
206
- });
207
-
208
- afterEach(async () => {
209
- try {
210
- if (app) await app.close();
211
- } catch (e) {
212
- // Ignore if already closed
213
- }
214
- });
215
-
216
- it('should introspect database and generate .object.yml files', async () => {
217
- const outputDir = path.join(testDir, 'objects');
218
-
219
- await syncDatabase({
220
- app: app,
221
- output: outputDir
222
- });
223
-
224
- // Check that files were created
225
- expect(fs.existsSync(path.join(outputDir, 'users.object.yml'))).toBe(true);
226
- expect(fs.existsSync(path.join(outputDir, 'posts.object.yml'))).toBe(true);
227
-
228
- // Verify users.object.yml content
229
- const usersContent = fs.readFileSync(path.join(outputDir, 'users.object.yml'), 'utf-8');
230
- const usersObj = yaml.load(usersContent) as any;
231
-
232
- expect(usersObj.name).toBe('users');
233
- expect(usersObj.label).toBe('Users');
234
- expect(usersObj.fields.username).toBeDefined();
235
- expect(usersObj.fields.username.type).toBe('text');
236
- expect(usersObj.fields.username.required).toBe(true);
237
- expect(usersObj.fields.username.unique).toBe(true);
238
- expect(usersObj.fields.email).toBeDefined();
239
- expect(usersObj.fields.email.type).toBe('text');
240
-
241
- // Verify posts.object.yml content
242
- const postsContent = fs.readFileSync(path.join(outputDir, 'posts.object.yml'), 'utf-8');
243
- const postsObj = yaml.load(postsContent) as any;
244
-
245
- expect(postsObj.name).toBe('posts');
246
- expect(postsObj.label).toBe('Posts');
247
- expect(postsObj.fields.title).toBeDefined();
248
- expect(postsObj.fields.content).toBeDefined();
249
- // Foreign key should be detected as lookup
250
- expect(postsObj.fields.author_id).toBeDefined();
251
- expect(postsObj.fields.author_id.type).toBe('lookup');
252
- expect(postsObj.fields.author_id.reference_to).toBe('users');
253
- });
254
-
255
- it('should support selective table syncing', async () => {
256
- const outputDir = path.join(testDir, 'objects_selective');
257
-
258
- await syncDatabase({
259
- app: app,
260
- output: outputDir,
261
- tables: ['users']
262
- });
263
-
264
- // Only users.object.yml should be created
265
- expect(fs.existsSync(path.join(outputDir, 'users.object.yml'))).toBe(true);
266
- expect(fs.existsSync(path.join(outputDir, 'posts.object.yml'))).toBe(false);
267
- });
268
-
269
- it('should skip existing files without --force flag', async () => {
270
- const outputDir = path.join(testDir, 'objects_skip');
271
-
272
- // First sync
273
- await syncDatabase({
274
- app: app,
275
- output: outputDir
276
- });
277
-
278
- // Modify an existing file
279
- const usersPath = path.join(outputDir, 'users.object.yml');
280
- const originalContent = fs.readFileSync(usersPath, 'utf-8');
281
- fs.writeFileSync(usersPath, '# Modified content\n' + originalContent, 'utf-8');
282
-
283
- // Second sync without force - should skip
284
- await syncDatabase({
285
- app: app,
286
- output: outputDir
287
- });
288
-
289
- const modifiedContent = fs.readFileSync(usersPath, 'utf-8');
290
- expect(modifiedContent).toContain('# Modified content');
291
- });
292
-
293
- it('should overwrite files with --force flag', async () => {
294
- const outputDir = path.join(testDir, 'objects_force');
295
-
296
- // First sync
297
- await syncDatabase({
298
- app: app,
299
- output: outputDir
300
- });
301
-
302
- // Modify an existing file
303
- const usersPath = path.join(outputDir, 'users.object.yml');
304
- fs.writeFileSync(usersPath, '# Modified content\nname: users', 'utf-8');
305
-
306
- // Second sync with force - should overwrite
307
- await syncDatabase({
308
- app: app,
309
- output: outputDir,
310
- force: true
311
- });
312
-
313
- const newContent = fs.readFileSync(usersPath, 'utf-8');
314
- expect(newContent).not.toContain('# Modified content');
315
- expect(newContent).toContain('name: users');
316
- expect(newContent).toContain('fields:');
317
- });
318
- });
319
-
320
- describe('build command', () => {
321
- beforeEach(async () => {
322
- // Create test object files
323
- await newMetadata({
324
- type: 'object',
325
- name: 'test_project',
326
- dir: testDir
327
- });
328
- });
329
-
330
- it('should validate metadata files', async () => {
331
- await expect(
332
- build({
333
- dir: testDir,
334
- output: path.join(testDir, 'dist'),
335
- types: false
336
- })
337
- ).resolves.not.toThrow();
338
- });
339
-
340
- it('should copy metadata files to dist', async () => {
341
- const distDir = path.join(testDir, 'dist');
342
-
343
- await build({
344
- dir: testDir,
345
- output: distDir,
346
- types: false
347
- });
348
-
349
- expect(fs.existsSync(path.join(distDir, 'test_project.object.yml'))).toBe(true);
350
- });
351
- });
352
-
353
- describe('lint command', () => {
354
- beforeEach(async () => {
355
- // Create test object files
356
- await newMetadata({
357
- type: 'object',
358
- name: 'valid_object',
359
- dir: testDir
360
- });
361
- });
362
-
363
- it('should pass validation for valid objects', async () => {
364
- await expect(
365
- lint({ dir: testDir })
366
- ).resolves.not.toThrow();
367
- });
368
-
369
- it('should detect invalid naming convention', async () => {
370
- // Create object with invalid name (uppercase)
371
- const invalidPath = path.join(testDir, 'InvalidObject.object.yml');
372
- fs.writeFileSync(invalidPath, yaml.dump({
373
- label: 'Invalid Object',
374
- fields: {
375
- name: { type: 'text' }
376
- }
377
- }), 'utf-8');
378
-
379
- // Mock process.exit to prevent actual exit
380
- const mockExit = jest.spyOn(process, 'exit').mockImplementation((code?: number) => {
381
- throw new Error(`Process exited with code ${code}`);
382
- });
383
-
384
- try {
385
- await lint({ dir: testDir });
386
- } catch (e: any) {
387
- expect(e.message).toContain('Process exited with code 1');
388
- }
389
-
390
- mockExit.mockRestore();
391
- });
392
- });
393
-
394
- describe('format command', () => {
395
- beforeEach(async () => {
396
- // Create test object files
397
- await newMetadata({
398
- type: 'object',
399
- name: 'test_format',
400
- dir: testDir
401
- });
402
- });
403
-
404
- it.skip('should format YAML files', async () => {
405
- // Skipped: Prettier dynamic import has issues in Jest environment
406
- // This functionality is tested manually
407
- const testPath = path.join(testDir, 'format_test.object.yml');
408
- fs.writeFileSync(testPath, yaml.dump({
409
- label: 'Format Test',
410
- fields: {
411
- name: { type: 'text', label: 'Name' }
412
- }
413
- }), 'utf-8');
414
-
415
- expect(fs.existsSync(testPath)).toBe(true);
416
- });
417
-
418
- it.skip('should check without modifying when --check flag is used', async () => {
419
- // Skipped: Prettier dynamic import has issues in Jest environment
420
- // This functionality is tested manually
421
- const testPath = path.join(testDir, 'test_format.object.yml');
422
- const originalContent = fs.readFileSync(testPath, 'utf-8');
423
- expect(originalContent).toBeDefined();
424
- });
425
- });
426
- });
package/jest.config.js DELETED
@@ -1,19 +0,0 @@
1
- module.exports = {
2
- preset: 'ts-jest',
3
- testEnvironment: 'node',
4
- testMatch: ['**/__tests__/**/*.test.ts'],
5
- moduleNameMapper: {
6
- '^@objectql/types$': '<rootDir>/../../foundation/types/src',
7
- '^@objectql/core$': '<rootDir>/../../foundation/core/src',
8
- '^@objectql/driver-sql$': '<rootDir>/../../drivers/sql/src',
9
- '^@objectql/driver-mongo$': '<rootDir>/../../drivers/mongo/src',
10
- '^@objectql/sdk$': '<rootDir>/../../drivers/sdk/src',
11
- '^@objectql/platform-node$': '<rootDir>/../../foundation/platform-node/src',
12
- '^@objectql/server$': '<rootDir>/../../runtime/server/src',
13
- },
14
- transform: {
15
- '^.+\\.ts$': ['ts-jest', {
16
- isolatedModules: true,
17
- }],
18
- },
19
- };