@flusys/nestjs-core 4.1.0 ā 5.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 +109 -580
- package/cjs/config/env-config.service.js +11 -8
- package/cjs/docs/docs.config.js +20 -0
- package/cjs/index.js +0 -1
- package/config/env-config.service.d.ts +2 -1
- package/docs/docs.config.d.ts +1 -0
- package/fesm/config/env-config.service.js +11 -8
- package/fesm/docs/docs.config.js +20 -0
- package/fesm/index.js +0 -2
- package/index.d.ts +0 -1
- package/interfaces/base-entity.interface.d.ts +0 -3
- package/package.json +1 -1
- package/cjs/seeders/base-seeder.js +0 -76
- package/cjs/seeders/cli.js +0 -291
- package/cjs/seeders/data-generator.js +0 -226
- package/cjs/seeders/entity-reader.js +0 -129
- package/cjs/seeders/field-patterns.js +0 -182
- package/cjs/seeders/index.js +0 -85
- package/cjs/seeders/seed-config.js +0 -62
- package/cjs/seeders/seed-runner.js +0 -281
- package/fesm/seeders/base-seeder.js +0 -66
- package/fesm/seeders/cli.js +0 -249
- package/fesm/seeders/data-generator.js +0 -216
- package/fesm/seeders/entity-reader.js +0 -119
- package/fesm/seeders/field-patterns.js +0 -143
- package/fesm/seeders/index.js +0 -7
- package/fesm/seeders/seed-config.js +0 -35
- package/fesm/seeders/seed-runner.js +0 -263
- package/seeders/base-seeder.d.ts +0 -14
- package/seeders/cli.d.ts +0 -3
- package/seeders/data-generator.d.ts +0 -15
- package/seeders/entity-reader.d.ts +0 -44
- package/seeders/field-patterns.d.ts +0 -12
- package/seeders/index.d.ts +0 -7
- package/seeders/seed-config.d.ts +0 -12
- package/seeders/seed-runner.d.ts +0 -48
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
function _export(target, all) {
|
|
6
|
-
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
_export(exports, {
|
|
12
|
-
get SeedRunner () {
|
|
13
|
-
return SeedRunner;
|
|
14
|
-
},
|
|
15
|
-
get defaultLogger () {
|
|
16
|
-
return defaultLogger;
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
const _entityreader = require("./entity-reader");
|
|
20
|
-
const _datagenerator = require("./data-generator");
|
|
21
|
-
const _baseseeder = require("./base-seeder");
|
|
22
|
-
const _seedconfig = require("./seed-config");
|
|
23
|
-
function _define_property(obj, key, value) {
|
|
24
|
-
if (key in obj) {
|
|
25
|
-
Object.defineProperty(obj, key, {
|
|
26
|
-
value: value,
|
|
27
|
-
enumerable: true,
|
|
28
|
-
configurable: true,
|
|
29
|
-
writable: true
|
|
30
|
-
});
|
|
31
|
-
} else {
|
|
32
|
-
obj[key] = value;
|
|
33
|
-
}
|
|
34
|
-
return obj;
|
|
35
|
-
}
|
|
36
|
-
const defaultLogger = {
|
|
37
|
-
log: (message)=>console.log(message),
|
|
38
|
-
error: (message)=>console.error(message)
|
|
39
|
-
};
|
|
40
|
-
let SeedRunner = class SeedRunner {
|
|
41
|
-
registerCustomSeeder(entityName, seeder) {
|
|
42
|
-
this.customSeeders.set(entityName, seeder);
|
|
43
|
-
}
|
|
44
|
-
unregisterCustomSeeder(entityName) {
|
|
45
|
-
this.customSeeders.delete(entityName);
|
|
46
|
-
}
|
|
47
|
-
hasCustomSeeder(entityName) {
|
|
48
|
-
return this.customSeeders.has(entityName);
|
|
49
|
-
}
|
|
50
|
-
getSeeder(entityName, entity, dataSource, entityInfo, batchSize) {
|
|
51
|
-
if (this.customSeeders.has(entityName)) {
|
|
52
|
-
return this.customSeeders.get(entityName);
|
|
53
|
-
}
|
|
54
|
-
return new GenericSeeder(dataSource, entity, this.dataGenerator, entityInfo, batchSize);
|
|
55
|
-
}
|
|
56
|
-
async runAll(options = {}) {
|
|
57
|
-
const results = [];
|
|
58
|
-
const continueOnError = options.continueOnError ?? true;
|
|
59
|
-
// Get all entities
|
|
60
|
-
const allEntities = this.entityReader.getAllEntities();
|
|
61
|
-
const entityNames = allEntities.map((e)=>e.name).filter((name)=>!(0, _seedconfig.shouldSkipEntity)(name));
|
|
62
|
-
// Get seeding order
|
|
63
|
-
const orderedNames = (0, _seedconfig.getSeedingOrder)(entityNames);
|
|
64
|
-
const mode = options.dryRun ? 'Previewing' : 'Seeding';
|
|
65
|
-
this.logger.log(`\nš± ${mode} ${orderedNames.length} entities...\n`);
|
|
66
|
-
for(let i = 0; i < orderedNames.length; i++){
|
|
67
|
-
const entityName = orderedNames[i];
|
|
68
|
-
// Call progress callback
|
|
69
|
-
options.onProgress?.(i + 1, orderedNames.length, entityName);
|
|
70
|
-
const result = await this.runSingle(entityName, options);
|
|
71
|
-
results.push(result);
|
|
72
|
-
// Log result
|
|
73
|
-
if (result.success) {
|
|
74
|
-
if (result.dryRun) {
|
|
75
|
-
this.logger.log(`ā ${entityName}: ${result.count} records (dry run)`);
|
|
76
|
-
} else if (result.skipped) {
|
|
77
|
-
this.logger.log(`ā ${entityName}: skipped (already has data)`);
|
|
78
|
-
} else {
|
|
79
|
-
this.logger.log(`ā ${entityName}: ${result.count} records`);
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
this.logger.error(`ā ${entityName}: ${result.error}`);
|
|
83
|
-
// Fail-fast if continueOnError is false
|
|
84
|
-
if (!continueOnError) {
|
|
85
|
-
this.logger.error(`\nā Seeding failed at ${entityName}, stopping...\n`);
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
const successCount = results.filter((r)=>r.success).length;
|
|
91
|
-
const failedCount = results.length - successCount;
|
|
92
|
-
if (failedCount > 0) {
|
|
93
|
-
this.logger.log(`\nā ļø Completed with errors: ${successCount}/${results.length} entities seeded (${failedCount} failed)\n`);
|
|
94
|
-
} else {
|
|
95
|
-
this.logger.log(`\nā Completed: ${successCount}/${results.length} entities seeded\n`);
|
|
96
|
-
}
|
|
97
|
-
return results;
|
|
98
|
-
}
|
|
99
|
-
async runSingle(entityName, options = {}) {
|
|
100
|
-
const queryRunner = this.dataSource.createQueryRunner();
|
|
101
|
-
await queryRunner.connect();
|
|
102
|
-
await queryRunner.startTransaction();
|
|
103
|
-
try {
|
|
104
|
-
// Get entity info
|
|
105
|
-
const entityInfo = this.entityReader.getEntityInfo(entityName);
|
|
106
|
-
// Find entity target
|
|
107
|
-
const entity = this.dataSource.entityMetadatas.find((e)=>e.name === entityName)?.target;
|
|
108
|
-
if (!entity) {
|
|
109
|
-
throw new Error(`Entity ${entityName} not found`);
|
|
110
|
-
}
|
|
111
|
-
const seeder = this.getSeeder(entityName, entity, queryRunner.manager.connection, entityInfo, options.batchSize);
|
|
112
|
-
// Dry run mode - preview without executing
|
|
113
|
-
if (options.dryRun) {
|
|
114
|
-
const count = options.count ?? (0, _seedconfig.getEntityCount)(entityName);
|
|
115
|
-
await queryRunner.rollbackTransaction();
|
|
116
|
-
return {
|
|
117
|
-
entity: entityName,
|
|
118
|
-
success: true,
|
|
119
|
-
count,
|
|
120
|
-
dryRun: true
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
// Skip if exists
|
|
124
|
-
if (options.skipIfExists) {
|
|
125
|
-
const existingCount = await seeder.count();
|
|
126
|
-
if (existingCount > 0) {
|
|
127
|
-
await queryRunner.rollbackTransaction();
|
|
128
|
-
return {
|
|
129
|
-
entity: entityName,
|
|
130
|
-
success: true,
|
|
131
|
-
count: 0,
|
|
132
|
-
skipped: true
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// Clear if requested
|
|
137
|
-
if (options.clear) {
|
|
138
|
-
await seeder.clear(options.respectSoftDelete ?? _seedconfig.seedConfig.respectSoftDelete);
|
|
139
|
-
}
|
|
140
|
-
// Generate data
|
|
141
|
-
const count = options.count ?? (0, _seedconfig.getEntityCount)(entityName);
|
|
142
|
-
const entities = await seeder.generate(count);
|
|
143
|
-
// Commit transaction
|
|
144
|
-
await queryRunner.commitTransaction();
|
|
145
|
-
return {
|
|
146
|
-
entity: entityName,
|
|
147
|
-
success: true,
|
|
148
|
-
count: entities.length
|
|
149
|
-
};
|
|
150
|
-
} catch (error) {
|
|
151
|
-
// Rollback on error
|
|
152
|
-
await queryRunner.rollbackTransaction();
|
|
153
|
-
return {
|
|
154
|
-
entity: entityName,
|
|
155
|
-
success: false,
|
|
156
|
-
count: 0,
|
|
157
|
-
error: error instanceof Error ? error.message : String(error)
|
|
158
|
-
};
|
|
159
|
-
} finally{
|
|
160
|
-
await queryRunner.release();
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
async clearAll(hard = false, continueOnError = true) {
|
|
164
|
-
const results = [];
|
|
165
|
-
// Get all entities
|
|
166
|
-
const allEntities = this.entityReader.getAllEntities();
|
|
167
|
-
const entityNames = allEntities.map((e)=>e.name).filter((name)=>!(0, _seedconfig.shouldSkipEntity)(name));
|
|
168
|
-
// Reverse order for clearing (delete children before parents)
|
|
169
|
-
const orderedNames = (0, _seedconfig.getSeedingOrder)(entityNames).reverse();
|
|
170
|
-
const mode = hard ? 'Hard deleting' : 'Clearing';
|
|
171
|
-
this.logger.log(`\nšļø ${mode} ${orderedNames.length} entities...\n`);
|
|
172
|
-
for (const entityName of orderedNames){
|
|
173
|
-
try {
|
|
174
|
-
const entity = this.dataSource.entityMetadatas.find((e)=>e.name === entityName)?.target;
|
|
175
|
-
if (!entity) {
|
|
176
|
-
throw new Error(`Entity ${entityName} not found`);
|
|
177
|
-
}
|
|
178
|
-
const seeder = this.getSeeder(entityName, entity, this.dataSource, this.entityReader.getEntityInfo(entityName));
|
|
179
|
-
const countBefore = await seeder.count(true);
|
|
180
|
-
// Skip if already empty
|
|
181
|
-
if (countBefore === 0) {
|
|
182
|
-
results.push({
|
|
183
|
-
entity: entityName,
|
|
184
|
-
success: true,
|
|
185
|
-
count: 0
|
|
186
|
-
});
|
|
187
|
-
this.logger.log(`ā ${entityName}: already empty`);
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
await seeder.clear(hard);
|
|
191
|
-
const countAfter = await seeder.count(true);
|
|
192
|
-
results.push({
|
|
193
|
-
entity: entityName,
|
|
194
|
-
success: true,
|
|
195
|
-
count: countBefore - countAfter
|
|
196
|
-
});
|
|
197
|
-
this.logger.log(`ā ${entityName}: ${countBefore - countAfter} records cleared`);
|
|
198
|
-
} catch (error) {
|
|
199
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
200
|
-
results.push({
|
|
201
|
-
entity: entityName,
|
|
202
|
-
success: false,
|
|
203
|
-
count: 0,
|
|
204
|
-
error: errorMessage
|
|
205
|
-
});
|
|
206
|
-
this.logger.error(`ā ${entityName}: ${errorMessage}`);
|
|
207
|
-
// Fail-fast if continueOnError is false
|
|
208
|
-
if (!continueOnError) {
|
|
209
|
-
this.logger.error(`\nā Clearing failed at ${entityName}, stopping...\n`);
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
const successCount = results.filter((r)=>r.success).length;
|
|
215
|
-
const failedCount = results.length - successCount;
|
|
216
|
-
if (failedCount > 0) {
|
|
217
|
-
this.logger.log(`\nā ļø Completed with errors: ${successCount}/${results.length} entities cleared (${failedCount} failed)\n`);
|
|
218
|
-
} else {
|
|
219
|
-
this.logger.log(`\nā Completed: ${successCount}/${results.length} entities cleared\n`);
|
|
220
|
-
}
|
|
221
|
-
return results;
|
|
222
|
-
}
|
|
223
|
-
async getStatus() {
|
|
224
|
-
const allEntities = this.entityReader.getAllEntities();
|
|
225
|
-
const status = [];
|
|
226
|
-
for (const metadata of allEntities){
|
|
227
|
-
if ((0, _seedconfig.shouldSkipEntity)(metadata.name)) {
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
const repo = this.dataSource.getRepository(metadata.target);
|
|
231
|
-
const count = await repo.count();
|
|
232
|
-
const isEmpty = count === 0;
|
|
233
|
-
const hasSoftDelete = metadata.deleteDateColumn !== undefined;
|
|
234
|
-
status.push({
|
|
235
|
-
entity: metadata.name,
|
|
236
|
-
tableName: metadata.tableName,
|
|
237
|
-
count,
|
|
238
|
-
isEmpty,
|
|
239
|
-
hasSoftDelete
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
return status;
|
|
243
|
-
}
|
|
244
|
-
constructor(dataSource, logger){
|
|
245
|
-
_define_property(this, "dataSource", void 0);
|
|
246
|
-
_define_property(this, "entityReader", void 0);
|
|
247
|
-
_define_property(this, "dataGenerator", void 0);
|
|
248
|
-
_define_property(this, "logger", void 0);
|
|
249
|
-
_define_property(this, "customSeeders", void 0);
|
|
250
|
-
this.dataSource = dataSource;
|
|
251
|
-
this.customSeeders = new Map();
|
|
252
|
-
this.entityReader = new _entityreader.EntityReader(dataSource);
|
|
253
|
-
this.dataGenerator = new _datagenerator.DataGenerator(_seedconfig.seedConfig.locale);
|
|
254
|
-
this.logger = logger ?? defaultLogger;
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
let GenericSeeder = class GenericSeeder extends _baseseeder.BaseSeeder {
|
|
258
|
-
async generate(count) {
|
|
259
|
-
const allSaved = [];
|
|
260
|
-
// Process in batches to optimize memory usage for large datasets
|
|
261
|
-
for(let offset = 0; offset < count; offset += this.batchSize){
|
|
262
|
-
const currentBatchSize = Math.min(this.batchSize, count - offset);
|
|
263
|
-
const batch = [];
|
|
264
|
-
for(let i = 0; i < currentBatchSize; i++){
|
|
265
|
-
const data = this.generator.generateEntity(this.entityInfo.columns);
|
|
266
|
-
// TypeORM's create method returns T, but TypeScript needs explicit type assertion
|
|
267
|
-
const entity = this.repository.create(data);
|
|
268
|
-
batch.push(entity);
|
|
269
|
-
}
|
|
270
|
-
// TypeORM's save method has overloads for T and T[]
|
|
271
|
-
// We need to explicitly cast to handle the generic type resolution
|
|
272
|
-
const saved = await this.repository.save(batch);
|
|
273
|
-
allSaved.push(...saved);
|
|
274
|
-
}
|
|
275
|
-
return allSaved;
|
|
276
|
-
}
|
|
277
|
-
constructor(dataSource, entity, generator, entityInfo, batchSize){
|
|
278
|
-
super(dataSource, entity), _define_property(this, "generator", void 0), _define_property(this, "entityInfo", void 0), _define_property(this, "batchSize", void 0), this.generator = generator, this.entityInfo = entityInfo;
|
|
279
|
-
this.batchSize = batchSize ?? 1000; // Default batch size
|
|
280
|
-
}
|
|
281
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
function _define_property(obj, key, value) {
|
|
2
|
-
if (key in obj) {
|
|
3
|
-
Object.defineProperty(obj, key, {
|
|
4
|
-
value: value,
|
|
5
|
-
enumerable: true,
|
|
6
|
-
configurable: true,
|
|
7
|
-
writable: true
|
|
8
|
-
});
|
|
9
|
-
} else {
|
|
10
|
-
obj[key] = value;
|
|
11
|
-
}
|
|
12
|
-
return obj;
|
|
13
|
-
}
|
|
14
|
-
export class BaseSeeder {
|
|
15
|
-
async clear(hard = false) {
|
|
16
|
-
const hasSoftDelete = this.metadata.deleteDateColumn !== undefined;
|
|
17
|
-
if (hard || !hasSoftDelete) {
|
|
18
|
-
await this.repository.clear();
|
|
19
|
-
} else {
|
|
20
|
-
await this.repository.createQueryBuilder().softDelete().where('id IS NOT NULL').execute();
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async count(includeDeleted = false) {
|
|
24
|
-
const hasSoftDelete = this.metadata.deleteDateColumn !== undefined;
|
|
25
|
-
if (hasSoftDelete && !includeDeleted) {
|
|
26
|
-
return this.repository.count({
|
|
27
|
-
where: {
|
|
28
|
-
deletedAt: null
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
return this.repository.count();
|
|
33
|
-
}
|
|
34
|
-
async isEmpty() {
|
|
35
|
-
return await this.count() === 0;
|
|
36
|
-
}
|
|
37
|
-
getEntityName() {
|
|
38
|
-
return this.metadata.tableName;
|
|
39
|
-
}
|
|
40
|
-
async withTransaction(operation) {
|
|
41
|
-
const queryRunner = this.dataSource.createQueryRunner();
|
|
42
|
-
await queryRunner.connect();
|
|
43
|
-
await queryRunner.startTransaction();
|
|
44
|
-
try {
|
|
45
|
-
const transactionalRepo = queryRunner.manager.getRepository(this.entity);
|
|
46
|
-
const result = await operation(transactionalRepo);
|
|
47
|
-
await queryRunner.commitTransaction();
|
|
48
|
-
return result;
|
|
49
|
-
} catch (error) {
|
|
50
|
-
await queryRunner.rollbackTransaction();
|
|
51
|
-
throw error;
|
|
52
|
-
} finally{
|
|
53
|
-
await queryRunner.release();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
constructor(dataSource, entity){
|
|
57
|
-
_define_property(this, "dataSource", void 0);
|
|
58
|
-
_define_property(this, "entity", void 0);
|
|
59
|
-
_define_property(this, "repository", void 0);
|
|
60
|
-
_define_property(this, "metadata", void 0);
|
|
61
|
-
this.dataSource = dataSource;
|
|
62
|
-
this.entity = entity;
|
|
63
|
-
this.repository = dataSource.getRepository(entity);
|
|
64
|
-
this.metadata = this.repository.metadata;
|
|
65
|
-
}
|
|
66
|
-
}
|
package/fesm/seeders/cli.js
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Seed CLI Entry Point
|
|
4
|
-
*
|
|
5
|
-
* Commands:
|
|
6
|
-
* - run: Execute seeders for all entities
|
|
7
|
-
* - run:all: Execute seeders for all tenants (multi-tenant mode)
|
|
8
|
-
* - clear: Clear all seeded data
|
|
9
|
-
* - status: Show seeding status
|
|
10
|
-
*/ import * as path from 'path';
|
|
11
|
-
import { DataSource } from 'typeorm';
|
|
12
|
-
import { envConfig } from '../config';
|
|
13
|
-
import { SeedRunner } from './seed-runner';
|
|
14
|
-
function showHelp() {
|
|
15
|
-
console.log(`
|
|
16
|
-
Seed Data CLI
|
|
17
|
-
|
|
18
|
-
Usage:
|
|
19
|
-
npm run seed:<command> [options]
|
|
20
|
-
|
|
21
|
-
Commands:
|
|
22
|
-
run Execute seeders for all entities
|
|
23
|
-
run:all Execute seeders for all tenants (multi-tenant mode)
|
|
24
|
-
clear Clear all seeded data
|
|
25
|
-
status Show seeding status
|
|
26
|
-
|
|
27
|
-
Options:
|
|
28
|
-
--config=<path> Migration config file (default: src/persistence/migration.config.ts)
|
|
29
|
-
--count=<number> Number of records per entity (default: from seed-config)
|
|
30
|
-
--clear Clear existing data before seeding
|
|
31
|
-
--entity=<name> Seed only specific entity
|
|
32
|
-
--hard Hard delete when clearing (ignore soft delete)
|
|
33
|
-
--tenant=<id> Target specific tenant (multi-tenant mode)
|
|
34
|
-
|
|
35
|
-
Examples:
|
|
36
|
-
npm run seed:run
|
|
37
|
-
npm run seed:run -- --count=50 --clear
|
|
38
|
-
npm run seed:run -- --entity=User --count=100
|
|
39
|
-
npm run seed:clear
|
|
40
|
-
npm run seed:clear -- --hard
|
|
41
|
-
npm run seed:status
|
|
42
|
-
npm run seed:run:all
|
|
43
|
-
`);
|
|
44
|
-
}
|
|
45
|
-
function parseArgs() {
|
|
46
|
-
const args = process.argv.slice(2);
|
|
47
|
-
let command;
|
|
48
|
-
let count;
|
|
49
|
-
let clear = false;
|
|
50
|
-
let entity;
|
|
51
|
-
let hard = false;
|
|
52
|
-
let tenant;
|
|
53
|
-
let configPath;
|
|
54
|
-
for (const arg of args){
|
|
55
|
-
if (arg.startsWith('--count=')) {
|
|
56
|
-
count = parseInt(arg.split('=')[1], 10);
|
|
57
|
-
} else if (arg === '--clear') {
|
|
58
|
-
clear = true;
|
|
59
|
-
} else if (arg.startsWith('--entity=')) {
|
|
60
|
-
entity = arg.split('=')[1];
|
|
61
|
-
} else if (arg === '--hard') {
|
|
62
|
-
hard = true;
|
|
63
|
-
} else if (arg.startsWith('--tenant=')) {
|
|
64
|
-
tenant = arg.split('=')[1];
|
|
65
|
-
} else if (arg.startsWith('--config=')) {
|
|
66
|
-
configPath = arg.split('=')[1];
|
|
67
|
-
} else if (!arg.startsWith('-') && !command) {
|
|
68
|
-
command = arg;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
command,
|
|
73
|
-
count,
|
|
74
|
-
clear,
|
|
75
|
-
entity,
|
|
76
|
-
hard,
|
|
77
|
-
tenant,
|
|
78
|
-
configPath
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
let resolvedConfigPath;
|
|
82
|
-
async function resolveConfigPath(explicitPath) {
|
|
83
|
-
if (resolvedConfigPath) return resolvedConfigPath;
|
|
84
|
-
const envConfigPath = envConfig.tryGetValue('MIGRATION_CONFIG');
|
|
85
|
-
const defaultPaths = [
|
|
86
|
-
explicitPath,
|
|
87
|
-
envConfigPath,
|
|
88
|
-
'src/persistence/migration.config.ts',
|
|
89
|
-
'src/persistence/migration.config.js'
|
|
90
|
-
].filter(Boolean);
|
|
91
|
-
for (const tryPath of defaultPaths){
|
|
92
|
-
const absolutePath = path.isAbsolute(tryPath) ? tryPath : path.resolve(process.cwd(), tryPath);
|
|
93
|
-
try {
|
|
94
|
-
await import(absolutePath);
|
|
95
|
-
resolvedConfigPath = absolutePath;
|
|
96
|
-
return absolutePath;
|
|
97
|
-
} catch {
|
|
98
|
-
// Try next path
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
console.error('ā Config not found. Use --config=<path> or set MIGRATION_CONFIG env var');
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
async function loadDataSource(tenantId, configPath) {
|
|
105
|
-
const resolvedPath = await resolveConfigPath(configPath);
|
|
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}` : ''}`);
|
|
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;
|
|
126
|
-
}
|
|
127
|
-
async function getAllTenantIds(configPath) {
|
|
128
|
-
const resolvedPath = await resolveConfigPath(configPath);
|
|
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');
|
|
133
|
-
}
|
|
134
|
-
return migrationConfig.tenants.map((t)=>t.tenantId || t.id);
|
|
135
|
-
}
|
|
136
|
-
async function runCommand(options, configPath) {
|
|
137
|
-
const dataSource = await loadDataSource(undefined, configPath);
|
|
138
|
-
try {
|
|
139
|
-
const runner = new SeedRunner(dataSource);
|
|
140
|
-
if (options.entity) {
|
|
141
|
-
console.log(`š± Seeding entity: ${options.entity}\n`);
|
|
142
|
-
const result = await runner.runSingle(options.entity, options);
|
|
143
|
-
if (result.success) {
|
|
144
|
-
console.log(`\nā ${result.entity}: ${result.count} records created\n`);
|
|
145
|
-
} else {
|
|
146
|
-
console.error(`\nā ${result.entity}: ${result.error}\n`);
|
|
147
|
-
process.exit(1);
|
|
148
|
-
}
|
|
149
|
-
} else {
|
|
150
|
-
const results = await runner.runAll(options);
|
|
151
|
-
if (results.some((r)=>!r.success)) process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
} finally{
|
|
154
|
-
await dataSource.destroy();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
async function runAllTenantsCommand(options, configPath) {
|
|
158
|
-
console.log('š¢ Running seeds for all tenants...\n');
|
|
159
|
-
const tenantIds = await getAllTenantIds(configPath);
|
|
160
|
-
let successCount = 0;
|
|
161
|
-
let failCount = 0;
|
|
162
|
-
for (const tenantId of tenantIds){
|
|
163
|
-
console.log(`\nš¦ Tenant: ${tenantId}`);
|
|
164
|
-
console.log('ā'.repeat(50));
|
|
165
|
-
const dataSource = await loadDataSource(tenantId, configPath);
|
|
166
|
-
try {
|
|
167
|
-
const runner = new SeedRunner(dataSource);
|
|
168
|
-
const results = await runner.runAll(options);
|
|
169
|
-
results.some((r)=>!r.success) ? failCount++ : successCount++;
|
|
170
|
-
} catch (error) {
|
|
171
|
-
console.error(`ā Failed to seed tenant ${tenantId}:`, error);
|
|
172
|
-
failCount++;
|
|
173
|
-
} finally{
|
|
174
|
-
await dataSource.destroy();
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
console.log('\n' + '='.repeat(50));
|
|
178
|
-
console.log(`\nā Completed: ${successCount} tenants seeded`);
|
|
179
|
-
if (failCount > 0) {
|
|
180
|
-
console.log(`ā Failed: ${failCount} tenants\n`);
|
|
181
|
-
process.exit(1);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
async function clearCommand(hard = false, configPath) {
|
|
185
|
-
const dataSource = await loadDataSource(undefined, configPath);
|
|
186
|
-
try {
|
|
187
|
-
await new SeedRunner(dataSource).clearAll(hard);
|
|
188
|
-
} finally{
|
|
189
|
-
await dataSource.destroy();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
async function statusCommand(configPath) {
|
|
193
|
-
const dataSource = await loadDataSource(undefined, configPath);
|
|
194
|
-
try {
|
|
195
|
-
const status = await new SeedRunner(dataSource).getStatus();
|
|
196
|
-
console.log('\nSeed Status:');
|
|
197
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāā¬āāāāāāāāāā');
|
|
198
|
-
console.log('ā Entity ā Records ā Status ā');
|
|
199
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāā¼āāāāāāāāāā¤');
|
|
200
|
-
for (const item of status){
|
|
201
|
-
console.log(`ā ${item.entity.padEnd(26)} ā ${item.count.toString().padEnd(9)} ā ${item.isEmpty ? 'ā Empty' : 'ā Ready '} ā`);
|
|
202
|
-
}
|
|
203
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāā“āāāāāāāāāā\n');
|
|
204
|
-
} finally{
|
|
205
|
-
await dataSource.destroy();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
async function main() {
|
|
209
|
-
const args = parseArgs();
|
|
210
|
-
if (!args.command) {
|
|
211
|
-
showHelp();
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
if (args.configPath) {
|
|
215
|
-
await resolveConfigPath(args.configPath);
|
|
216
|
-
}
|
|
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);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (require.main === module) {
|
|
244
|
-
main().catch((error)=>{
|
|
245
|
-
console.error('Fatal error:', error);
|
|
246
|
-
process.exit(1);
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
export { main as runSeedCli };
|