@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.
- package/README.md +2 -2
- package/dist/commands/database-push.d.ts +5 -0
- package/dist/commands/database-push.js +15 -0
- package/dist/commands/database-push.js.map +1 -0
- package/dist/commands/dev.d.ts +2 -0
- package/dist/commands/dev.js +94 -6
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.js +37 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +31 -30
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/serve.d.ts +2 -0
- package/dist/commands/serve.js +122 -46
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/start.d.ts +1 -0
- package/dist/commands/start.js +15 -0
- package/dist/commands/start.js.map +1 -1
- package/dist/index.js +173 -210
- package/dist/index.js.map +1 -1
- package/package.json +13 -7
- package/templates/hello-world/.vscode/extensions.json +7 -0
- package/templates/hello-world/CHANGELOG.md +41 -0
- package/templates/hello-world/README.md +29 -0
- package/templates/hello-world/package.json +24 -0
- package/templates/hello-world/src/index.ts +58 -0
- package/templates/hello-world/tsconfig.json +10 -0
- package/templates/starter/.vscode/extensions.json +7 -0
- package/{CHANGELOG.md → templates/starter/CHANGELOG.md} +36 -42
- package/templates/starter/README.md +17 -0
- package/templates/starter/__tests__/projects-hooks-actions.test.ts +490 -0
- package/templates/starter/jest.config.js +16 -0
- package/templates/starter/package.json +52 -0
- package/templates/starter/src/README.pages.md +110 -0
- package/templates/starter/src/demo.app.yml +4 -0
- package/templates/starter/src/i18n/zh-CN/projects.json +22 -0
- package/templates/starter/src/modules/kitchen-sink/kitchen_sink.data.yml +18 -0
- package/templates/starter/src/modules/kitchen-sink/kitchen_sink.object.yml +156 -0
- package/templates/starter/src/modules/projects/project_approval.workflow.yml +51 -0
- package/templates/starter/src/modules/projects/projects.action.ts +472 -0
- package/templates/starter/src/modules/projects/projects.data.yml +13 -0
- package/templates/starter/src/modules/projects/projects.hook.ts +339 -0
- package/templates/starter/src/modules/projects/projects.object.yml +148 -0
- package/templates/starter/src/modules/projects/projects.permission.yml +141 -0
- package/templates/starter/src/modules/projects/projects.validation.yml +37 -0
- package/templates/starter/src/modules/tasks/tasks.data.yml +23 -0
- package/templates/starter/src/modules/tasks/tasks.object.yml +34 -0
- package/templates/starter/src/modules/tasks/tasks.permission.yml +167 -0
- package/templates/starter/src/seed.ts +55 -0
- package/templates/starter/src/types/index.ts +3 -0
- package/templates/starter/src/types/kitchen_sink.ts +101 -0
- package/templates/starter/src/types/projects.ts +49 -0
- package/templates/starter/src/types/tasks.ts +33 -0
- package/templates/starter/tsconfig.json +11 -0
- package/templates/starter/tsconfig.tsbuildinfo +1 -0
- package/AI_EXAMPLES.md +0 -154
- package/AI_IMPLEMENTATION_SUMMARY.md +0 -509
- package/AI_TUTORIAL.md +0 -144
- package/IMPLEMENTATION_SUMMARY.md +0 -437
- package/USAGE_EXAMPLES.md +0 -951
- package/__tests__/commands.test.ts +0 -426
- package/jest.config.js +0 -19
- package/src/commands/ai.ts +0 -509
- package/src/commands/build.ts +0 -98
- package/src/commands/dev.ts +0 -23
- package/src/commands/format.ts +0 -110
- package/src/commands/generate.ts +0 -135
- package/src/commands/i18n.ts +0 -303
- package/src/commands/init.ts +0 -191
- package/src/commands/lint.ts +0 -98
- package/src/commands/migrate.ts +0 -314
- package/src/commands/new.ts +0 -221
- package/src/commands/repl.ts +0 -120
- package/src/commands/serve.ts +0 -96
- package/src/commands/start.ts +0 -100
- package/src/commands/sync.ts +0 -328
- package/src/commands/test.ts +0 -98
- package/src/index.ts +0 -356
- package/tsconfig.json +0 -15
- package/tsconfig.tsbuildinfo +0 -1
package/src/commands/init.ts
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { exec } from 'child_process';
|
|
4
|
-
import { promisify } from 'util';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
|
|
7
|
-
const execAsync = promisify(exec);
|
|
8
|
-
|
|
9
|
-
interface InitOptions {
|
|
10
|
-
template?: string;
|
|
11
|
-
name?: string;
|
|
12
|
-
dir?: string;
|
|
13
|
-
skipInstall?: boolean;
|
|
14
|
-
skipGit?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const TEMPLATES = {
|
|
18
|
-
basic: '@objectql/starter-basic',
|
|
19
|
-
'express-api': '@objectql/starter-express-api',
|
|
20
|
-
enterprise: '@objectql/starter-enterprise'
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export async function initProject(options: InitOptions) {
|
|
24
|
-
const projectName = options.name || 'my-objectql-project';
|
|
25
|
-
const targetDir = options.dir || path.join(process.cwd(), projectName);
|
|
26
|
-
const template = (options.template || 'basic') as keyof typeof TEMPLATES;
|
|
27
|
-
|
|
28
|
-
if (!TEMPLATES[template]) {
|
|
29
|
-
console.error(chalk.red(`❌ Unknown template: ${template}`));
|
|
30
|
-
console.log(chalk.gray(`Available templates: ${Object.keys(TEMPLATES).join(', ')}`));
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
console.log(chalk.blue(`\n🚀 Initializing ObjectQL project: ${chalk.bold(projectName)}`));
|
|
35
|
-
console.log(chalk.gray(`Template: ${template}`));
|
|
36
|
-
console.log(chalk.gray(`Directory: ${targetDir}\n`));
|
|
37
|
-
|
|
38
|
-
// Check if directory exists
|
|
39
|
-
if (fs.existsSync(targetDir)) {
|
|
40
|
-
console.error(chalk.red(`❌ Directory already exists: ${targetDir}`));
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Create project directory
|
|
45
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
// Copy template files from starters
|
|
49
|
-
const templatePath = path.join(__dirname, '../../../../starters', template);
|
|
50
|
-
|
|
51
|
-
// Check if we're in the monorepo (for development)
|
|
52
|
-
if (fs.existsSync(templatePath)) {
|
|
53
|
-
console.log(chalk.gray('Copying template files...'));
|
|
54
|
-
await copyDirectory(templatePath, targetDir, ['node_modules', 'dist', '.git']);
|
|
55
|
-
} else {
|
|
56
|
-
// Try to use the published package
|
|
57
|
-
console.log(chalk.gray(`Installing template from npm: ${TEMPLATES[template]}...`));
|
|
58
|
-
await execAsync(`npm create ${TEMPLATES[template]} ${targetDir}`, {
|
|
59
|
-
cwd: process.cwd()
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Update package.json with project name
|
|
64
|
-
const pkgPath = path.join(targetDir, 'package.json');
|
|
65
|
-
if (fs.existsSync(pkgPath)) {
|
|
66
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
67
|
-
pkg.name = projectName;
|
|
68
|
-
pkg.version = '0.1.0';
|
|
69
|
-
// Convert workspace dependencies to actual versions for standalone project
|
|
70
|
-
if (pkg.dependencies) {
|
|
71
|
-
for (const dep of Object.keys(pkg.dependencies)) {
|
|
72
|
-
if (pkg.dependencies[dep] === 'workspace:*') {
|
|
73
|
-
pkg.dependencies[dep] = '^1.0.0'; // Use latest published version
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (pkg.devDependencies) {
|
|
78
|
-
for (const dep of Object.keys(pkg.devDependencies)) {
|
|
79
|
-
if (pkg.devDependencies[dep] === 'workspace:*') {
|
|
80
|
-
pkg.devDependencies[dep] = '^1.0.0';
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Initialize git repository
|
|
88
|
-
if (!options.skipGit) {
|
|
89
|
-
try {
|
|
90
|
-
console.log(chalk.gray('\nInitializing git repository...'));
|
|
91
|
-
await execAsync('git init', { cwd: targetDir });
|
|
92
|
-
|
|
93
|
-
// Create .gitignore if it doesn't exist
|
|
94
|
-
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
95
|
-
if (!fs.existsSync(gitignorePath)) {
|
|
96
|
-
const gitignore = `node_modules/
|
|
97
|
-
dist/
|
|
98
|
-
*.log
|
|
99
|
-
.DS_Store
|
|
100
|
-
*.sqlite3
|
|
101
|
-
.env
|
|
102
|
-
.env.local
|
|
103
|
-
`;
|
|
104
|
-
fs.writeFileSync(gitignorePath, gitignore);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
console.log(chalk.green('✓ Git repository initialized'));
|
|
108
|
-
} catch (err) {
|
|
109
|
-
console.log(chalk.yellow('⚠ Git initialization skipped (git not available)'));
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Install dependencies
|
|
114
|
-
if (!options.skipInstall) {
|
|
115
|
-
console.log(chalk.gray('\nInstalling dependencies...'));
|
|
116
|
-
console.log(chalk.gray('This might take a few minutes...\n'));
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
// Try pnpm first, fall back to npm
|
|
120
|
-
const hasNpm = await checkCommand('pnpm');
|
|
121
|
-
const packageManager = hasNpm ? 'pnpm' : 'npm';
|
|
122
|
-
|
|
123
|
-
await execAsync(`${packageManager} install`, {
|
|
124
|
-
cwd: targetDir,
|
|
125
|
-
// Show output in real-time would be better, but this is simpler
|
|
126
|
-
});
|
|
127
|
-
console.log(chalk.green(`✓ Dependencies installed with ${packageManager}`));
|
|
128
|
-
} catch (err: any) {
|
|
129
|
-
console.log(chalk.yellow(`⚠ Failed to install dependencies: ${err.message}`));
|
|
130
|
-
console.log(chalk.gray(`You can install them manually by running:`));
|
|
131
|
-
console.log(chalk.gray(` cd ${projectName} && npm install`));
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Success message
|
|
136
|
-
console.log(chalk.green('\n✅ Project created successfully!\n'));
|
|
137
|
-
console.log(chalk.bold('Next steps:'));
|
|
138
|
-
console.log(chalk.gray(` cd ${projectName}`));
|
|
139
|
-
|
|
140
|
-
if (options.skipInstall) {
|
|
141
|
-
console.log(chalk.gray(' pnpm install # or npm install'));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
console.log(chalk.gray(' pnpm run build'));
|
|
145
|
-
console.log(chalk.gray(' pnpm run repl\n'));
|
|
146
|
-
|
|
147
|
-
console.log(chalk.blue('📚 Documentation: https://github.com/objectql/objectql'));
|
|
148
|
-
|
|
149
|
-
} catch (error: any) {
|
|
150
|
-
console.error(chalk.red(`\n❌ Failed to create project: ${error.message}`));
|
|
151
|
-
// Clean up
|
|
152
|
-
if (fs.existsSync(targetDir)) {
|
|
153
|
-
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
154
|
-
}
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function copyDirectory(src: string, dest: string, ignore: string[] = []) {
|
|
160
|
-
// Create destination directory
|
|
161
|
-
if (!fs.existsSync(dest)) {
|
|
162
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
166
|
-
|
|
167
|
-
for (const entry of entries) {
|
|
168
|
-
const srcPath = path.join(src, entry.name);
|
|
169
|
-
const destPath = path.join(dest, entry.name);
|
|
170
|
-
|
|
171
|
-
// Skip ignored directories
|
|
172
|
-
if (ignore.includes(entry.name)) {
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (entry.isDirectory()) {
|
|
177
|
-
await copyDirectory(srcPath, destPath, ignore);
|
|
178
|
-
} else {
|
|
179
|
-
fs.copyFileSync(srcPath, destPath);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async function checkCommand(cmd: string): Promise<boolean> {
|
|
185
|
-
try {
|
|
186
|
-
await execAsync(`${cmd} --version`);
|
|
187
|
-
return true;
|
|
188
|
-
} catch {
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
}
|
package/src/commands/lint.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { ObjectQL } from '@objectql/core';
|
|
2
|
-
import { ObjectLoader } from '@objectql/platform-node';
|
|
3
|
-
import { ObjectConfig, FieldConfig } from '@objectql/types';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
|
|
7
|
-
// Naming convention regex
|
|
8
|
-
const VALID_NAME_REGEX = /^[a-z][a-z0-9_]*$/;
|
|
9
|
-
|
|
10
|
-
interface LintOptions {
|
|
11
|
-
dir?: string;
|
|
12
|
-
fix?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Lint command - validates metadata files for correctness and best practices
|
|
17
|
-
*/
|
|
18
|
-
export async function lint(options: LintOptions) {
|
|
19
|
-
console.log(chalk.blue('🔍 Linting ObjectQL metadata files...\n'));
|
|
20
|
-
|
|
21
|
-
const rootDir = path.resolve(process.cwd(), options.dir || '.');
|
|
22
|
-
let hasErrors = false;
|
|
23
|
-
let hasWarnings = false;
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const app = new ObjectQL({ datasources: {} });
|
|
27
|
-
const loader = new ObjectLoader(app.metadata);
|
|
28
|
-
|
|
29
|
-
console.log(chalk.cyan('Loading metadata files...'));
|
|
30
|
-
loader.load(rootDir);
|
|
31
|
-
|
|
32
|
-
const objects = app.metadata.list('object');
|
|
33
|
-
|
|
34
|
-
console.log(chalk.green(`✅ Found ${objects.length} object(s)\n`));
|
|
35
|
-
|
|
36
|
-
// Validate each object
|
|
37
|
-
for (const obj of objects) {
|
|
38
|
-
const objectConfig = obj as ObjectConfig;
|
|
39
|
-
const name = objectConfig.name;
|
|
40
|
-
console.log(chalk.cyan(`Checking object: ${name}`));
|
|
41
|
-
|
|
42
|
-
// Check naming convention (lowercase with underscores)
|
|
43
|
-
if (!VALID_NAME_REGEX.test(name)) {
|
|
44
|
-
console.log(chalk.red(` ❌ Invalid name format: "${name}" should be lowercase with underscores`));
|
|
45
|
-
hasErrors = true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Check if label exists
|
|
49
|
-
if (!objectConfig.label) {
|
|
50
|
-
console.log(chalk.yellow(` ⚠️ Missing label for object "${name}"`));
|
|
51
|
-
hasWarnings = true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Check fields
|
|
55
|
-
const fieldCount = Object.keys(objectConfig.fields || {}).length;
|
|
56
|
-
if (fieldCount === 0) {
|
|
57
|
-
console.log(chalk.yellow(` ⚠️ Object "${name}" has no fields defined`));
|
|
58
|
-
hasWarnings = true;
|
|
59
|
-
} else {
|
|
60
|
-
console.log(chalk.gray(` ℹ️ ${fieldCount} field(s) defined`));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Validate field names
|
|
64
|
-
for (const [fieldName, field] of Object.entries(objectConfig.fields || {})) {
|
|
65
|
-
if (!VALID_NAME_REGEX.test(fieldName)) {
|
|
66
|
-
console.log(chalk.red(` ❌ Invalid field name: "${fieldName}" should be lowercase with underscores`));
|
|
67
|
-
hasErrors = true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const fieldConfig = field as FieldConfig;
|
|
71
|
-
// Check for required label on fields
|
|
72
|
-
if (!fieldConfig.label) {
|
|
73
|
-
console.log(chalk.yellow(` ⚠️ Field "${fieldName}" missing label`));
|
|
74
|
-
hasWarnings = true;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.log('');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Summary
|
|
82
|
-
if (hasErrors) {
|
|
83
|
-
console.log(chalk.red.bold('❌ Linting failed with errors\n'));
|
|
84
|
-
process.exit(1);
|
|
85
|
-
} else if (hasWarnings) {
|
|
86
|
-
console.log(chalk.yellow.bold('⚠️ Linting completed with warnings\n'));
|
|
87
|
-
} else {
|
|
88
|
-
console.log(chalk.green.bold('✅ Linting passed - no issues found!\n'));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
} catch (e: any) {
|
|
92
|
-
console.error(chalk.red('❌ Linting failed:'), e.message);
|
|
93
|
-
if (e.stack) {
|
|
94
|
-
console.error(chalk.gray(e.stack));
|
|
95
|
-
}
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
}
|
package/src/commands/migrate.ts
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
|
|
5
|
-
interface MigrateOptions {
|
|
6
|
-
config?: string;
|
|
7
|
-
dir?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface MigrateCreateOptions {
|
|
11
|
-
name: string;
|
|
12
|
-
dir?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface MigrateStatusOptions {
|
|
16
|
-
config?: string;
|
|
17
|
-
dir?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const MIGRATION_TEMPLATE = `import { ObjectQL } from '@objectql/core';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Migration: {{name}}
|
|
24
|
-
* Created: {{timestamp}}
|
|
25
|
-
*/
|
|
26
|
-
export async function up(app: ObjectQL) {
|
|
27
|
-
// TODO: Implement migration logic
|
|
28
|
-
console.log('Running migration: {{name}}');
|
|
29
|
-
|
|
30
|
-
// Example: Add a new field to an object
|
|
31
|
-
// const tasks = app.getObject('tasks');
|
|
32
|
-
// await tasks.updateSchema({
|
|
33
|
-
// fields: {
|
|
34
|
-
// new_field: { type: 'text', label: 'New Field' }
|
|
35
|
-
// }
|
|
36
|
-
// });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function down(app: ObjectQL) {
|
|
40
|
-
// TODO: Implement rollback logic
|
|
41
|
-
console.log('Rolling back migration: {{name}}');
|
|
42
|
-
|
|
43
|
-
// Example: Remove the field
|
|
44
|
-
// const tasks = app.getObject('tasks');
|
|
45
|
-
// await tasks.updateSchema({
|
|
46
|
-
// fields: {
|
|
47
|
-
// new_field: undefined
|
|
48
|
-
// }
|
|
49
|
-
// });
|
|
50
|
-
}
|
|
51
|
-
`;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Run pending migrations
|
|
55
|
-
*/
|
|
56
|
-
export async function migrate(options: MigrateOptions) {
|
|
57
|
-
const migrationsDir = path.resolve(process.cwd(), options.dir || './migrations');
|
|
58
|
-
|
|
59
|
-
console.log(chalk.blue('🔄 Running migrations...'));
|
|
60
|
-
console.log(chalk.gray(`Migrations directory: ${migrationsDir}\n`));
|
|
61
|
-
|
|
62
|
-
if (!fs.existsSync(migrationsDir)) {
|
|
63
|
-
console.log(chalk.yellow('⚠ No migrations directory found'));
|
|
64
|
-
console.log(chalk.gray('Create one with: objectql migrate:create --name init'));
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
// Load ObjectQL instance from config
|
|
70
|
-
const app = await loadObjectQLInstance(options.config);
|
|
71
|
-
|
|
72
|
-
// Get list of migration files
|
|
73
|
-
const files = fs.readdirSync(migrationsDir)
|
|
74
|
-
.filter(f => f.endsWith('.ts') || f.endsWith('.js'))
|
|
75
|
-
.sort();
|
|
76
|
-
|
|
77
|
-
if (files.length === 0) {
|
|
78
|
-
console.log(chalk.yellow('⚠ No migration files found'));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Get already run migrations
|
|
83
|
-
const migrations = app.getObject('_migrations');
|
|
84
|
-
let runMigrations: string[] = [];
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const result = await migrations.find({
|
|
88
|
-
fields: ['name'],
|
|
89
|
-
sort: [['created_at', 'asc']]
|
|
90
|
-
});
|
|
91
|
-
runMigrations = result.records.map((r: any) => r.name);
|
|
92
|
-
} catch (err) {
|
|
93
|
-
// Migrations table doesn't exist yet, create it
|
|
94
|
-
console.log(chalk.gray('Creating migrations tracking table...'));
|
|
95
|
-
await createMigrationsTable(app);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Run pending migrations
|
|
99
|
-
let ranCount = 0;
|
|
100
|
-
for (const file of files) {
|
|
101
|
-
const migrationName = file.replace(/\.(ts|js)$/, '');
|
|
102
|
-
|
|
103
|
-
if (runMigrations.includes(migrationName)) {
|
|
104
|
-
console.log(chalk.gray(`⊘ ${migrationName} (already run)`));
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
console.log(chalk.blue(`▶ ${migrationName}`));
|
|
109
|
-
|
|
110
|
-
const migrationPath = path.join(migrationsDir, file);
|
|
111
|
-
const migration = require(migrationPath);
|
|
112
|
-
|
|
113
|
-
if (!migration.up) {
|
|
114
|
-
console.log(chalk.red(` ✗ No 'up' function found`));
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
await migration.up(app);
|
|
120
|
-
|
|
121
|
-
// Record migration
|
|
122
|
-
await migrations.insert({
|
|
123
|
-
name: migrationName,
|
|
124
|
-
run_at: new Date()
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
console.log(chalk.green(` ✓ Complete`));
|
|
128
|
-
ranCount++;
|
|
129
|
-
} catch (error: any) {
|
|
130
|
-
console.log(chalk.red(` ✗ Failed: ${error.message}`));
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
console.log(chalk.green(`\n✅ Ran ${ranCount} migration(s)`));
|
|
136
|
-
|
|
137
|
-
} catch (error: any) {
|
|
138
|
-
console.error(chalk.red(`❌ Migration failed: ${error.message}`));
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Create a new migration file with boilerplate code
|
|
145
|
-
*/
|
|
146
|
-
export async function migrateCreate(options: MigrateCreateOptions) {
|
|
147
|
-
const { name } = options;
|
|
148
|
-
const migrationsDir = path.resolve(process.cwd(), options.dir || './migrations');
|
|
149
|
-
|
|
150
|
-
// Validate name
|
|
151
|
-
if (!name || !/^[a-z][a-z0-9_]*$/.test(name)) {
|
|
152
|
-
console.error(chalk.red('❌ Invalid migration name. Use lowercase with underscores (e.g., add_status_field)'));
|
|
153
|
-
process.exit(1);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Create migrations directory if it doesn't exist
|
|
157
|
-
if (!fs.existsSync(migrationsDir)) {
|
|
158
|
-
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
159
|
-
console.log(chalk.gray(`Created directory: ${migrationsDir}`));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Generate filename with timestamp
|
|
163
|
-
const timestamp = new Date().toISOString().replace(/[:\-T.]/g, '').slice(0, 14);
|
|
164
|
-
const filename = `${timestamp}_${name}.ts`;
|
|
165
|
-
const filePath = path.join(migrationsDir, filename);
|
|
166
|
-
|
|
167
|
-
// Create migration file from template
|
|
168
|
-
const content = MIGRATION_TEMPLATE
|
|
169
|
-
.replace(/\{\{name\}\}/g, name)
|
|
170
|
-
.replace(/\{\{timestamp\}\}/g, new Date().toISOString());
|
|
171
|
-
|
|
172
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
173
|
-
|
|
174
|
-
console.log(chalk.green(`✅ Created migration: ${filename}`));
|
|
175
|
-
console.log(chalk.gray(`Path: ${filePath}`));
|
|
176
|
-
console.log(chalk.gray(`\nNext steps:`));
|
|
177
|
-
console.log(chalk.gray(` 1. Edit the migration file to add your changes`));
|
|
178
|
-
console.log(chalk.gray(` 2. Run: objectql migrate`));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Show migration status - displays pending and completed migrations
|
|
183
|
-
* @param options - Configuration options including config path and migrations directory
|
|
184
|
-
*/
|
|
185
|
-
export async function migrateStatus(options: MigrateStatusOptions) {
|
|
186
|
-
const migrationsDir = path.resolve(process.cwd(), options.dir || './migrations');
|
|
187
|
-
|
|
188
|
-
console.log(chalk.blue('📊 Migration Status\n'));
|
|
189
|
-
|
|
190
|
-
if (!fs.existsSync(migrationsDir)) {
|
|
191
|
-
console.log(chalk.yellow('⚠ No migrations directory found'));
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
// Load ObjectQL instance
|
|
197
|
-
const app = await loadObjectQLInstance(options.config);
|
|
198
|
-
|
|
199
|
-
// Get list of migration files
|
|
200
|
-
const files = fs.readdirSync(migrationsDir)
|
|
201
|
-
.filter(f => f.endsWith('.ts') || f.endsWith('.js'))
|
|
202
|
-
.sort();
|
|
203
|
-
|
|
204
|
-
if (files.length === 0) {
|
|
205
|
-
console.log(chalk.gray('No migration files found'));
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Get run migrations
|
|
210
|
-
const migrations = app.getObject('_migrations');
|
|
211
|
-
let runMigrations: string[] = [];
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
const result = await migrations.find({
|
|
215
|
-
fields: ['name', 'run_at'],
|
|
216
|
-
sort: [['run_at', 'asc']]
|
|
217
|
-
});
|
|
218
|
-
runMigrations = result.records.map((r: any) => r.name);
|
|
219
|
-
} catch (err) {
|
|
220
|
-
// Migrations table doesn't exist
|
|
221
|
-
runMigrations = [];
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Display status
|
|
225
|
-
let pendingCount = 0;
|
|
226
|
-
for (const file of files) {
|
|
227
|
-
const migrationName = file.replace(/\.(ts|js)$/, '');
|
|
228
|
-
const isRun = runMigrations.includes(migrationName);
|
|
229
|
-
|
|
230
|
-
if (isRun) {
|
|
231
|
-
console.log(chalk.green(`✓ ${migrationName}`));
|
|
232
|
-
} else {
|
|
233
|
-
console.log(chalk.yellow(`○ ${migrationName} (pending)`));
|
|
234
|
-
pendingCount++;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
console.log(chalk.blue(`\n📊 Summary:`));
|
|
239
|
-
console.log(chalk.gray(`Total migrations: ${files.length}`));
|
|
240
|
-
console.log(chalk.gray(`Run: ${files.length - pendingCount}`));
|
|
241
|
-
console.log(chalk.gray(`Pending: ${pendingCount}`));
|
|
242
|
-
|
|
243
|
-
} catch (error: any) {
|
|
244
|
-
console.error(chalk.red(`❌ Failed to get status: ${error.message}`));
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async function loadObjectQLInstance(configPath?: string): Promise<any> {
|
|
250
|
-
const cwd = process.cwd();
|
|
251
|
-
|
|
252
|
-
// Try to load from config file
|
|
253
|
-
let configFile = configPath;
|
|
254
|
-
if (!configFile) {
|
|
255
|
-
const potentialFiles = ['objectql.config.ts', 'objectql.config.js'];
|
|
256
|
-
for (const file of potentialFiles) {
|
|
257
|
-
if (fs.existsSync(path.join(cwd, file))) {
|
|
258
|
-
configFile = file;
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (!configFile) {
|
|
265
|
-
throw new Error('No configuration file found (objectql.config.ts/js)');
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Register ts-node for TypeScript support
|
|
269
|
-
try {
|
|
270
|
-
require('ts-node').register({
|
|
271
|
-
transpileOnly: true,
|
|
272
|
-
compilerOptions: {
|
|
273
|
-
module: 'commonjs'
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
} catch (err) {
|
|
277
|
-
// ts-node not available, try to load JS directly
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const configModule = require(path.join(cwd, configFile));
|
|
281
|
-
const app = configModule.default || configModule.app || configModule.objectql || configModule.db;
|
|
282
|
-
|
|
283
|
-
if (!app) {
|
|
284
|
-
throw new Error('Config file must export an ObjectQL instance');
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
await app.init();
|
|
288
|
-
return app;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
async function createMigrationsTable(app: any) {
|
|
292
|
-
// Create a system object to track migrations
|
|
293
|
-
app.metadata.register('object', {
|
|
294
|
-
name: '_migrations',
|
|
295
|
-
label: 'Migrations',
|
|
296
|
-
system: true,
|
|
297
|
-
fields: {
|
|
298
|
-
name: {
|
|
299
|
-
type: 'text',
|
|
300
|
-
label: 'Migration Name',
|
|
301
|
-
required: true,
|
|
302
|
-
unique: true
|
|
303
|
-
},
|
|
304
|
-
run_at: {
|
|
305
|
-
type: 'datetime',
|
|
306
|
-
label: 'Run At',
|
|
307
|
-
required: true
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
// Sync to database
|
|
313
|
-
await app.getObject('_migrations').sync();
|
|
314
|
-
}
|