@flusys/nestjs-core 1.0.0-rc ā 2.0.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 +482 -73
- package/cjs/docs/docs.config.js +77 -3
- package/cjs/docs/index.js +0 -1
- package/cjs/migration/migration.runner.js +37 -65
- package/cjs/seeders/cli.js +65 -172
- package/cjs/seeders/data-generator.js +91 -139
- package/cjs/seeders/field-patterns.js +172 -0
- package/cjs/seeders/index.js +13 -4
- package/cjs/seeders/seed-runner.js +8 -14
- package/docs/docs.config.d.ts +7 -0
- package/docs/index.d.ts +0 -1
- package/fesm/docs/docs.config.js +68 -0
- package/fesm/docs/index.js +0 -1
- package/fesm/migration/migration.runner.js +37 -65
- package/fesm/seeders/cli.js +65 -182
- package/fesm/seeders/data-generator.js +91 -146
- package/fesm/seeders/field-patterns.js +143 -0
- package/fesm/seeders/index.js +1 -1
- package/fesm/seeders/seed-runner.js +8 -14
- package/package.json +1 -1
- package/seeders/data-generator.d.ts +1 -1
- package/seeders/field-patterns.d.ts +12 -0
- package/seeders/index.d.ts +1 -1
- package/seeders/seed-runner.d.ts +1 -0
- package/cjs/docs/docs.setup.js +0 -14
- package/cjs/seeders/template-generator.js +0 -297
- package/docs/docs.setup.d.ts +0 -3
- package/fesm/docs/docs.setup.js +0 -4
- package/fesm/seeders/template-generator.js +0 -257
- package/seeders/template-generator.d.ts +0 -16
package/fesm/docs/docs.config.js
CHANGED
|
@@ -84,6 +84,65 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|
|
84
84
|
paths: filteredPaths
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Filter OpenAPI document to exclude examples from endpoint responses
|
|
89
|
+
*/ function filterExamples(document, exclusions) {
|
|
90
|
+
if (!exclusions?.length || !document.paths) {
|
|
91
|
+
return document;
|
|
92
|
+
}
|
|
93
|
+
const filteredPaths = {};
|
|
94
|
+
for (const [path, pathItem] of Object.entries(document.paths)){
|
|
95
|
+
const filteredPathItem = {};
|
|
96
|
+
for (const [method, operation] of Object.entries(pathItem)){
|
|
97
|
+
// Skip non-operation properties
|
|
98
|
+
if (!operation || typeof operation !== 'object') {
|
|
99
|
+
filteredPathItem[method] = operation;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// Check if this endpoint matches any exclusion pattern
|
|
103
|
+
const matchingExclusions = exclusions.filter((exclusion)=>{
|
|
104
|
+
const pathMatches = pathMatchesPattern(path, exclusion.pathPattern);
|
|
105
|
+
const methodMatches = !exclusion.method || exclusion.method === method;
|
|
106
|
+
return pathMatches && methodMatches;
|
|
107
|
+
});
|
|
108
|
+
if (matchingExclusions.length > 0) {
|
|
109
|
+
// Collect all examples to exclude
|
|
110
|
+
const examplesToExclude = new Set();
|
|
111
|
+
matchingExclusions.forEach((exclusion)=>{
|
|
112
|
+
exclusion.examples.forEach((example)=>examplesToExclude.add(example));
|
|
113
|
+
});
|
|
114
|
+
// Deep clone the operation to avoid mutating original
|
|
115
|
+
const filteredOperation = JSON.parse(JSON.stringify(operation));
|
|
116
|
+
// Filter examples from responses
|
|
117
|
+
if (filteredOperation.responses) {
|
|
118
|
+
for (const [statusCode, response] of Object.entries(filteredOperation.responses)){
|
|
119
|
+
const responseObj = response;
|
|
120
|
+
if (responseObj?.content) {
|
|
121
|
+
const content = responseObj.content;
|
|
122
|
+
for (const [mediaType, mediaTypeObj] of Object.entries(content)){
|
|
123
|
+
const media = mediaTypeObj;
|
|
124
|
+
if (media?.examples && typeof media.examples === 'object') {
|
|
125
|
+
const examples = media.examples;
|
|
126
|
+
for (const exampleName of examplesToExclude){
|
|
127
|
+
delete examples[exampleName];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
filteredPathItem[method] = filteredOperation;
|
|
135
|
+
} else {
|
|
136
|
+
filteredPathItem[method] = operation;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
filteredPaths[path] = filteredPathItem;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
...document,
|
|
143
|
+
paths: filteredPaths
|
|
144
|
+
};
|
|
145
|
+
}
|
|
87
146
|
/**
|
|
88
147
|
* Check if a path matches a pattern (supports wildcards)
|
|
89
148
|
*/ function pathMatchesPattern(path, pattern) {
|
|
@@ -179,6 +238,15 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|
|
179
238
|
if (config.excludeQueryParameters?.length) {
|
|
180
239
|
document = filterQueryParameters(document, config.excludeQueryParameters);
|
|
181
240
|
}
|
|
241
|
+
// Filter out excluded examples
|
|
242
|
+
if (config.excludeExamples?.length) {
|
|
243
|
+
document = filterExamples(document, config.excludeExamples);
|
|
244
|
+
}
|
|
182
245
|
SwaggerModule.setup(config.path, app, document);
|
|
183
246
|
});
|
|
184
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Setup Swagger docs with variadic module configs
|
|
250
|
+
*/ export function setupSwaggerDocs(app, ...modules) {
|
|
251
|
+
setupModuleSwaggerDocs(app, modules);
|
|
252
|
+
}
|
package/fesm/docs/index.js
CHANGED
|
@@ -4,6 +4,36 @@ import * as path from 'path';
|
|
|
4
4
|
import { envConfig } from '../config';
|
|
5
5
|
import { getActiveTenants, getDatabaseForTenant, getMigrationsFolderPath } from '../utils';
|
|
6
6
|
import { initializeDataSource } from './datasource.factory';
|
|
7
|
+
function getLabel(tenantId) {
|
|
8
|
+
return tenantId ? `[${tenantId}]` : '[default]';
|
|
9
|
+
}
|
|
10
|
+
async function withDataSource(config, tenantId, operation) {
|
|
11
|
+
const { database } = getDatabaseForTenant(config, tenantId);
|
|
12
|
+
const label = getLabel(tenantId);
|
|
13
|
+
try {
|
|
14
|
+
const ds = await initializeDataSource({
|
|
15
|
+
config,
|
|
16
|
+
tenantId
|
|
17
|
+
});
|
|
18
|
+
const partial = await operation(ds, label);
|
|
19
|
+
await ds.destroy();
|
|
20
|
+
return {
|
|
21
|
+
tenantId,
|
|
22
|
+
database,
|
|
23
|
+
success: true,
|
|
24
|
+
...partial
|
|
25
|
+
};
|
|
26
|
+
} catch (error) {
|
|
27
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
28
|
+
console.error(`${label} ā Failed: ${errorMessage}`);
|
|
29
|
+
return {
|
|
30
|
+
tenantId,
|
|
31
|
+
database,
|
|
32
|
+
success: false,
|
|
33
|
+
error: errorMessage
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
7
37
|
export function ensureMigrationsFolder(basePath, tenantId) {
|
|
8
38
|
const folder = getMigrationsFolderPath(basePath, tenantId);
|
|
9
39
|
if (!fs.existsSync(folder)) {
|
|
@@ -45,15 +75,8 @@ function findDatasourcePath() {
|
|
|
45
75
|
throw new Error('Could not find datasource file. Specify with --datasource=<path>');
|
|
46
76
|
}
|
|
47
77
|
export async function runMigrations(config, tenantId) {
|
|
48
|
-
|
|
49
|
-
const label = tenantId ? `[${tenantId}]` : '[default]';
|
|
50
|
-
try {
|
|
51
|
-
const ds = await initializeDataSource({
|
|
52
|
-
config,
|
|
53
|
-
tenantId
|
|
54
|
-
});
|
|
78
|
+
return withDataSource(config, tenantId, async (ds, label)=>{
|
|
55
79
|
const migrations = await ds.runMigrations();
|
|
56
|
-
await ds.destroy();
|
|
57
80
|
if (migrations.length === 0) {
|
|
58
81
|
console.log(`${label} No pending migrations.`);
|
|
59
82
|
} else {
|
|
@@ -61,78 +84,27 @@ export async function runMigrations(config, tenantId) {
|
|
|
61
84
|
migrations.forEach((m)=>console.log(` - ${m.name}`));
|
|
62
85
|
}
|
|
63
86
|
return {
|
|
64
|
-
tenantId,
|
|
65
|
-
database,
|
|
66
|
-
success: true,
|
|
67
87
|
migrationsRun: migrations.map((m)=>m.name)
|
|
68
88
|
};
|
|
69
|
-
}
|
|
70
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
71
|
-
console.error(`${label} ā Failed: ${errorMessage}`);
|
|
72
|
-
return {
|
|
73
|
-
tenantId,
|
|
74
|
-
database,
|
|
75
|
-
success: false,
|
|
76
|
-
error: errorMessage
|
|
77
|
-
};
|
|
78
|
-
}
|
|
89
|
+
});
|
|
79
90
|
}
|
|
80
91
|
export async function revertMigration(config, tenantId) {
|
|
81
|
-
|
|
82
|
-
const label = tenantId ? `[${tenantId}]` : '[default]';
|
|
83
|
-
try {
|
|
84
|
-
const ds = await initializeDataSource({
|
|
85
|
-
config,
|
|
86
|
-
tenantId
|
|
87
|
-
});
|
|
92
|
+
return withDataSource(config, tenantId, async (ds, label)=>{
|
|
88
93
|
await ds.undoLastMigration();
|
|
89
|
-
await ds.destroy();
|
|
90
94
|
console.log(`${label} ā Reverted!`);
|
|
91
|
-
return {
|
|
92
|
-
|
|
93
|
-
database,
|
|
94
|
-
success: true
|
|
95
|
-
};
|
|
96
|
-
} catch (error) {
|
|
97
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
98
|
-
console.error(`${label} ā Failed: ${errorMessage}`);
|
|
99
|
-
return {
|
|
100
|
-
tenantId,
|
|
101
|
-
database,
|
|
102
|
-
success: false,
|
|
103
|
-
error: errorMessage
|
|
104
|
-
};
|
|
105
|
-
}
|
|
95
|
+
return {};
|
|
96
|
+
});
|
|
106
97
|
}
|
|
107
98
|
export async function migrationStatus(config, tenantId) {
|
|
108
|
-
|
|
109
|
-
const label = tenantId ? `[${tenantId}]` : '[default]';
|
|
110
|
-
try {
|
|
111
|
-
const ds = await initializeDataSource({
|
|
112
|
-
config,
|
|
113
|
-
tenantId
|
|
114
|
-
});
|
|
99
|
+
return withDataSource(config, tenantId, async (ds, label)=>{
|
|
115
100
|
const hasPending = await ds.showMigrations();
|
|
116
|
-
await ds.destroy();
|
|
117
101
|
console.log(`${label} Pending: ${hasPending ? 'Yes' : 'No'}`);
|
|
118
102
|
return {
|
|
119
|
-
tenantId,
|
|
120
|
-
database,
|
|
121
|
-
success: true,
|
|
122
103
|
migrationsRun: [
|
|
123
104
|
hasPending ? 'Has pending' : 'Up to date'
|
|
124
105
|
]
|
|
125
106
|
};
|
|
126
|
-
}
|
|
127
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
128
|
-
console.error(`${label} ā Failed: ${errorMessage}`);
|
|
129
|
-
return {
|
|
130
|
-
tenantId,
|
|
131
|
-
database,
|
|
132
|
-
success: false,
|
|
133
|
-
error: errorMessage
|
|
134
|
-
};
|
|
135
|
-
}
|
|
107
|
+
});
|
|
136
108
|
}
|
|
137
109
|
export async function runForAllTenants(config, command) {
|
|
138
110
|
const tenants = getActiveTenants(config);
|
package/fesm/seeders/cli.js
CHANGED
|
@@ -2,31 +2,16 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Seed CLI Entry Point
|
|
4
4
|
*
|
|
5
|
-
* Command-line interface for seed data generation.
|
|
6
|
-
*
|
|
7
5
|
* Commands:
|
|
8
|
-
* - generate: Generate seeder template files
|
|
9
6
|
* - run: Execute seeders for all entities
|
|
10
7
|
* - run:all: Execute seeders for all tenants (multi-tenant mode)
|
|
11
8
|
* - clear: Clear all seeded data
|
|
12
9
|
* - status: Show seeding status
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* npm run seed:generate
|
|
16
|
-
* npm run seed:run
|
|
17
|
-
* npm run seed:run -- --count=50 --clear
|
|
18
|
-
* npm run seed:clear
|
|
19
|
-
* npm run seed:status
|
|
20
10
|
*/ import * as path from 'path';
|
|
21
11
|
import { DataSource } from 'typeorm';
|
|
22
12
|
import { envConfig } from '../config';
|
|
23
|
-
import { EntityReader } from './entity-reader';
|
|
24
|
-
import { TemplateGenerator } from './template-generator';
|
|
25
13
|
import { SeedRunner } from './seed-runner';
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Show help message
|
|
29
|
-
*/ function showHelp() {
|
|
14
|
+
function showHelp() {
|
|
30
15
|
console.log(`
|
|
31
16
|
Seed Data CLI
|
|
32
17
|
|
|
@@ -34,7 +19,6 @@ Usage:
|
|
|
34
19
|
npm run seed:<command> [options]
|
|
35
20
|
|
|
36
21
|
Commands:
|
|
37
|
-
generate Generate seeder template files
|
|
38
22
|
run Execute seeders for all entities
|
|
39
23
|
run:all Execute seeders for all tenants (multi-tenant mode)
|
|
40
24
|
clear Clear all seeded data
|
|
@@ -48,24 +32,17 @@ Options:
|
|
|
48
32
|
--hard Hard delete when clearing (ignore soft delete)
|
|
49
33
|
--tenant=<id> Target specific tenant (multi-tenant mode)
|
|
50
34
|
|
|
51
|
-
Environment:
|
|
52
|
-
MIGRATION_CONFIG Default config path (overrides auto-detection)
|
|
53
|
-
|
|
54
35
|
Examples:
|
|
55
|
-
npm run seed:generate
|
|
56
36
|
npm run seed:run
|
|
57
37
|
npm run seed:run -- --count=50 --clear
|
|
58
38
|
npm run seed:run -- --entity=User --count=100
|
|
59
|
-
npm run seed:run -- --config=src/persistence/migration.config.ts
|
|
60
39
|
npm run seed:clear
|
|
61
40
|
npm run seed:clear -- --hard
|
|
62
41
|
npm run seed:status
|
|
63
42
|
npm run seed:run:all
|
|
64
43
|
`);
|
|
65
44
|
}
|
|
66
|
-
|
|
67
|
-
* Parse command-line arguments
|
|
68
|
-
*/ function parseArgs() {
|
|
45
|
+
function parseArgs() {
|
|
69
46
|
const args = process.argv.slice(2);
|
|
70
47
|
let command;
|
|
71
48
|
let count;
|
|
@@ -74,8 +51,7 @@ Examples:
|
|
|
74
51
|
let hard = false;
|
|
75
52
|
let tenant;
|
|
76
53
|
let configPath;
|
|
77
|
-
for(
|
|
78
|
-
const arg = args[i];
|
|
54
|
+
for (const arg of args){
|
|
79
55
|
if (arg.startsWith('--count=')) {
|
|
80
56
|
count = parseInt(arg.split('=')[1], 10);
|
|
81
57
|
} else if (arg === '--clear') {
|
|
@@ -102,21 +78,15 @@ Examples:
|
|
|
102
78
|
configPath
|
|
103
79
|
};
|
|
104
80
|
}
|
|
105
|
-
// Resolved config path (set once during CLI initialization)
|
|
106
81
|
let resolvedConfigPath;
|
|
107
|
-
|
|
108
|
-
* Resolve migration config path with auto-detection
|
|
109
|
-
*/ async function resolveConfigPath(explicitPath) {
|
|
82
|
+
async function resolveConfigPath(explicitPath) {
|
|
110
83
|
if (resolvedConfigPath) return resolvedConfigPath;
|
|
111
|
-
// Try paths in order: explicit, env var, then common defaults
|
|
112
84
|
const envConfigPath = envConfig.tryGetValue('MIGRATION_CONFIG');
|
|
113
85
|
const defaultPaths = [
|
|
114
86
|
explicitPath,
|
|
115
87
|
envConfigPath,
|
|
116
88
|
'src/persistence/migration.config.ts',
|
|
117
|
-
'src/persistence/migration.config.js'
|
|
118
|
-
'persistence/migration.config.ts',
|
|
119
|
-
'persistence/migration.config.js'
|
|
89
|
+
'src/persistence/migration.config.js'
|
|
120
90
|
].filter(Boolean);
|
|
121
91
|
for (const tryPath of defaultPaths){
|
|
122
92
|
const absolutePath = path.isAbsolute(tryPath) ? tryPath : path.resolve(process.cwd(), tryPath);
|
|
@@ -129,97 +99,41 @@ let resolvedConfigPath;
|
|
|
129
99
|
}
|
|
130
100
|
}
|
|
131
101
|
console.error('ā Config not found. Use --config=<path> or set MIGRATION_CONFIG env var');
|
|
132
|
-
console.error(' Tried paths:', defaultPaths.join(', '));
|
|
133
102
|
process.exit(1);
|
|
134
103
|
}
|
|
135
|
-
|
|
136
|
-
* Load DataSource configuration
|
|
137
|
-
*/ async function loadDataSource(tenantId, configPath) {
|
|
104
|
+
async function loadDataSource(tenantId, configPath) {
|
|
138
105
|
const resolvedPath = await resolveConfigPath(configPath);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!dbConfig) {
|
|
145
|
-
throw new Error(`Database configuration not found${tenantId ? ` for tenant: ${tenantId}` : ''}`);
|
|
146
|
-
}
|
|
147
|
-
// Get entities - handle both function and array
|
|
148
|
-
const entities = typeof migrationConfig.entities === 'function' ? migrationConfig.entities() : migrationConfig.entities || [];
|
|
149
|
-
// Create DataSource
|
|
150
|
-
const dataSource = new DataSource({
|
|
151
|
-
type: dbConfig.type || 'mysql',
|
|
152
|
-
host: dbConfig.host,
|
|
153
|
-
port: dbConfig.port,
|
|
154
|
-
username: dbConfig.username,
|
|
155
|
-
password: dbConfig.password,
|
|
156
|
-
database: dbConfig.database,
|
|
157
|
-
entities,
|
|
158
|
-
synchronize: false,
|
|
159
|
-
logging: false
|
|
160
|
-
});
|
|
161
|
-
await dataSource.initialize();
|
|
162
|
-
return dataSource;
|
|
163
|
-
} catch (error) {
|
|
164
|
-
console.error('ā Failed to load DataSource configuration');
|
|
165
|
-
console.error(' Error:', error instanceof Error ? error.message : String(error));
|
|
166
|
-
throw error;
|
|
106
|
+
const module1 = await import(resolvedPath);
|
|
107
|
+
const migrationConfig = module1.migrationConfig || module1.default?.migrationConfig || module1;
|
|
108
|
+
const dbConfig = tenantId ? migrationConfig.tenants?.find((t)=>t.tenantId === tenantId || t.id === tenantId) : migrationConfig.defaultDatabaseConfig || migrationConfig.database;
|
|
109
|
+
if (!dbConfig) {
|
|
110
|
+
throw new Error(`Database configuration not found${tenantId ? ` for tenant: ${tenantId}` : ''}`);
|
|
167
111
|
}
|
|
112
|
+
const entities = typeof migrationConfig.entities === 'function' ? migrationConfig.entities() : migrationConfig.entities || [];
|
|
113
|
+
const dataSource = new DataSource({
|
|
114
|
+
type: dbConfig.type || 'mysql',
|
|
115
|
+
host: dbConfig.host,
|
|
116
|
+
port: dbConfig.port,
|
|
117
|
+
username: dbConfig.username,
|
|
118
|
+
password: dbConfig.password,
|
|
119
|
+
database: dbConfig.database,
|
|
120
|
+
entities,
|
|
121
|
+
synchronize: false,
|
|
122
|
+
logging: false
|
|
123
|
+
});
|
|
124
|
+
await dataSource.initialize();
|
|
125
|
+
return dataSource;
|
|
168
126
|
}
|
|
169
|
-
|
|
170
|
-
* Get all tenant IDs from configuration
|
|
171
|
-
*/ async function getAllTenantIds(configPath) {
|
|
127
|
+
async function getAllTenantIds(configPath) {
|
|
172
128
|
const resolvedPath = await resolveConfigPath(configPath);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
throw new Error('Multi-tenant mode is not configured');
|
|
178
|
-
}
|
|
179
|
-
return migrationConfig.tenants.map((t)=>t.tenantId || t.id);
|
|
180
|
-
} catch (error) {
|
|
181
|
-
console.error('ā Failed to load tenant configuration');
|
|
182
|
-
throw error;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Generate seeder template files
|
|
187
|
-
*/ async function generateCommand(configPath) {
|
|
188
|
-
console.log('š Discovering entities...\n');
|
|
189
|
-
const dataSource = await loadDataSource(undefined, configPath);
|
|
190
|
-
try {
|
|
191
|
-
const reader = new EntityReader(dataSource);
|
|
192
|
-
const generator = new TemplateGenerator();
|
|
193
|
-
const entities = reader.getAllEntities();
|
|
194
|
-
const outputDir = path.resolve(process.cwd(), 'src/seeders/generators');
|
|
195
|
-
console.log(`š Generating seeder files in ${outputDir}\n`);
|
|
196
|
-
let generatedCount = 0;
|
|
197
|
-
const entityInfos = [];
|
|
198
|
-
for (const metadata of entities){
|
|
199
|
-
if (shouldSkipEntity(metadata.name)) {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
const entityInfo = reader.getEntityInfo(metadata.name);
|
|
204
|
-
entityInfos.push(entityInfo);
|
|
205
|
-
const filePath = generator.generateSeederFile(entityInfo, outputDir);
|
|
206
|
-
console.log(`ā Generated: ${path.basename(filePath)}`);
|
|
207
|
-
generatedCount++;
|
|
208
|
-
} catch (error) {
|
|
209
|
-
console.error(`ā Failed to generate ${metadata.name}:`, error instanceof Error ? error.message : String(error));
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
// Generate index file
|
|
213
|
-
const indexPath = generator.generateIndexFile(entityInfos, outputDir);
|
|
214
|
-
console.log(`ā Generated: ${path.basename(indexPath)}`);
|
|
215
|
-
console.log(`\nā Generated ${generatedCount} seeder files\n`);
|
|
216
|
-
} finally{
|
|
217
|
-
await dataSource.destroy();
|
|
129
|
+
const module1 = await import(resolvedPath);
|
|
130
|
+
const migrationConfig = module1.migrationConfig || module1.default?.migrationConfig || module1;
|
|
131
|
+
if (migrationConfig.bootstrapAppConfig?.databaseMode !== 'multi-tenant' || !migrationConfig.tenants) {
|
|
132
|
+
throw new Error('Multi-tenant mode is not configured');
|
|
218
133
|
}
|
|
134
|
+
return migrationConfig.tenants.map((t)=>t.tenantId || t.id);
|
|
219
135
|
}
|
|
220
|
-
|
|
221
|
-
* Run seeds for single database
|
|
222
|
-
*/ async function runCommand(options, configPath) {
|
|
136
|
+
async function runCommand(options, configPath) {
|
|
223
137
|
const dataSource = await loadDataSource(undefined, configPath);
|
|
224
138
|
try {
|
|
225
139
|
const runner = new SeedRunner(dataSource);
|
|
@@ -234,18 +148,13 @@ let resolvedConfigPath;
|
|
|
234
148
|
}
|
|
235
149
|
} else {
|
|
236
150
|
const results = await runner.runAll(options);
|
|
237
|
-
|
|
238
|
-
if (failedCount > 0) {
|
|
239
|
-
process.exit(1);
|
|
240
|
-
}
|
|
151
|
+
if (results.some((r)=>!r.success)) process.exit(1);
|
|
241
152
|
}
|
|
242
153
|
} finally{
|
|
243
154
|
await dataSource.destroy();
|
|
244
155
|
}
|
|
245
156
|
}
|
|
246
|
-
|
|
247
|
-
* Run seeds for all tenants
|
|
248
|
-
*/ async function runAllTenantsCommand(options, configPath) {
|
|
157
|
+
async function runAllTenantsCommand(options, configPath) {
|
|
249
158
|
console.log('š¢ Running seeds for all tenants...\n');
|
|
250
159
|
const tenantIds = await getAllTenantIds(configPath);
|
|
251
160
|
let successCount = 0;
|
|
@@ -257,12 +166,7 @@ let resolvedConfigPath;
|
|
|
257
166
|
try {
|
|
258
167
|
const runner = new SeedRunner(dataSource);
|
|
259
168
|
const results = await runner.runAll(options);
|
|
260
|
-
|
|
261
|
-
if (failed === 0) {
|
|
262
|
-
successCount++;
|
|
263
|
-
} else {
|
|
264
|
-
failCount++;
|
|
265
|
-
}
|
|
169
|
+
results.some((r)=>!r.success) ? failCount++ : successCount++;
|
|
266
170
|
} catch (error) {
|
|
267
171
|
console.error(`ā Failed to seed tenant ${tenantId}:`, error);
|
|
268
172
|
failCount++;
|
|
@@ -277,86 +181,65 @@ let resolvedConfigPath;
|
|
|
277
181
|
process.exit(1);
|
|
278
182
|
}
|
|
279
183
|
}
|
|
280
|
-
|
|
281
|
-
* Clear all seeded data
|
|
282
|
-
*/ async function clearCommand(hard = false, configPath) {
|
|
184
|
+
async function clearCommand(hard = false, configPath) {
|
|
283
185
|
const dataSource = await loadDataSource(undefined, configPath);
|
|
284
186
|
try {
|
|
285
|
-
|
|
286
|
-
await runner.clearAll(hard);
|
|
187
|
+
await new SeedRunner(dataSource).clearAll(hard);
|
|
287
188
|
} finally{
|
|
288
189
|
await dataSource.destroy();
|
|
289
190
|
}
|
|
290
191
|
}
|
|
291
|
-
|
|
292
|
-
* Show seeding status
|
|
293
|
-
*/ async function statusCommand(configPath) {
|
|
192
|
+
async function statusCommand(configPath) {
|
|
294
193
|
const dataSource = await loadDataSource(undefined, configPath);
|
|
295
194
|
try {
|
|
296
|
-
const
|
|
297
|
-
const status = await runner.getStatus();
|
|
195
|
+
const status = await new SeedRunner(dataSource).getStatus();
|
|
298
196
|
console.log('\nSeed Status:');
|
|
299
197
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāā¬āāāāāāāāāā');
|
|
300
198
|
console.log('ā Entity ā Records ā Status ā');
|
|
301
199
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāā¼āāāāāāāāāā¤');
|
|
302
200
|
for (const item of status){
|
|
303
|
-
|
|
304
|
-
const count = item.count.toString().padEnd(9);
|
|
305
|
-
const statusIcon = item.isEmpty ? 'ā Empty' : 'ā Ready ';
|
|
306
|
-
console.log(`ā ${entityName} ā ${count} ā ${statusIcon} ā`);
|
|
201
|
+
console.log(`ā ${item.entity.padEnd(26)} ā ${item.count.toString().padEnd(9)} ā ${item.isEmpty ? 'ā Empty' : 'ā Ready '} ā`);
|
|
307
202
|
}
|
|
308
203
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāā“āāāāāāāāāā\n');
|
|
309
204
|
} finally{
|
|
310
205
|
await dataSource.destroy();
|
|
311
206
|
}
|
|
312
207
|
}
|
|
313
|
-
|
|
314
|
-
* Main CLI entry point
|
|
315
|
-
*/ async function main() {
|
|
208
|
+
async function main() {
|
|
316
209
|
const args = parseArgs();
|
|
317
210
|
if (!args.command) {
|
|
318
211
|
showHelp();
|
|
319
212
|
return;
|
|
320
213
|
}
|
|
321
|
-
// Initialize config path for all commands
|
|
322
214
|
if (args.configPath) {
|
|
323
215
|
await resolveConfigPath(args.configPath);
|
|
324
216
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
default:
|
|
350
|
-
console.error(`ā Unknown command: ${args.command}`);
|
|
351
|
-
showHelp();
|
|
352
|
-
process.exit(1);
|
|
353
|
-
}
|
|
354
|
-
} catch (error) {
|
|
355
|
-
console.error('\nā CLI Error:', error instanceof Error ? error.message : String(error));
|
|
356
|
-
process.exit(1);
|
|
217
|
+
switch(args.command){
|
|
218
|
+
case 'run':
|
|
219
|
+
await runCommand({
|
|
220
|
+
count: args.count,
|
|
221
|
+
clear: args.clear,
|
|
222
|
+
entity: args.entity
|
|
223
|
+
}, args.configPath);
|
|
224
|
+
break;
|
|
225
|
+
case 'run:all':
|
|
226
|
+
await runAllTenantsCommand({
|
|
227
|
+
count: args.count,
|
|
228
|
+
clear: args.clear
|
|
229
|
+
}, args.configPath);
|
|
230
|
+
break;
|
|
231
|
+
case 'clear':
|
|
232
|
+
await clearCommand(args.hard, args.configPath);
|
|
233
|
+
break;
|
|
234
|
+
case 'status':
|
|
235
|
+
await statusCommand(args.configPath);
|
|
236
|
+
break;
|
|
237
|
+
default:
|
|
238
|
+
console.error(`ā Unknown command: ${args.command}`);
|
|
239
|
+
showHelp();
|
|
240
|
+
process.exit(1);
|
|
357
241
|
}
|
|
358
242
|
}
|
|
359
|
-
// Run CLI if called directly
|
|
360
243
|
if (require.main === module) {
|
|
361
244
|
main().catch((error)=>{
|
|
362
245
|
console.error('Fatal error:', error);
|