@ooneex/seeds 1.4.0 → 1.4.3
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 +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +70 -70
- package/dist/index.js.map +5 -5
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Database seeding framework for populating initial data, fixtures, and test datas
|
|
|
16
16
|
|
|
17
17
|
✅ **Active/Inactive Control** - Enable or disable individual seeds via the `isActive()` method
|
|
18
18
|
|
|
19
|
-
✅ **Seed Runner** - Execute all active seeds in order with terminal logging via `
|
|
19
|
+
✅ **Seed Runner** - Execute all active seeds in order with terminal logging via `run()`
|
|
20
20
|
|
|
21
21
|
✅ **Seed Scaffolding** - Generate new seed files from a template with `seedCreate()`
|
|
22
22
|
|
|
@@ -95,9 +95,9 @@ export class UserSeed implements ISeed {
|
|
|
95
95
|
### Running Seeds
|
|
96
96
|
|
|
97
97
|
```typescript
|
|
98
|
-
import {
|
|
98
|
+
import { run } from '@ooneex/seeds';
|
|
99
99
|
|
|
100
|
-
await
|
|
100
|
+
await run();
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
### Creating a New Seed
|
|
@@ -136,7 +136,7 @@ interface ISeed {
|
|
|
136
136
|
|
|
137
137
|
### Functions
|
|
138
138
|
|
|
139
|
-
#### `
|
|
139
|
+
#### `run(): Promise<void>`
|
|
140
140
|
|
|
141
141
|
Execute all active seeds with dependency resolution and terminal logging.
|
|
142
142
|
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ declare const decorator: {
|
|
|
9
9
|
seed: (scope?: EContainerScope) => (target: SeedClassType) => void;
|
|
10
10
|
};
|
|
11
11
|
declare const getSeeds: () => ISeed[];
|
|
12
|
+
declare const run: () => Promise<void>;
|
|
12
13
|
declare const seedCreate: (config: {
|
|
13
14
|
name: string;
|
|
14
15
|
seedsDir?: string;
|
|
@@ -18,5 +19,4 @@ declare const seedCreate: (config: {
|
|
|
18
19
|
testPath: string;
|
|
19
20
|
dataPath: string;
|
|
20
21
|
}>;
|
|
21
|
-
|
|
22
|
-
export { seedRun, seedCreate, getSeeds, decorator, SeedClassType, ISeed };
|
|
22
|
+
export { seedCreate, run, getSeeds, decorator, SeedClassType, ISeed };
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,74 @@ var getSeeds = () => {
|
|
|
22
22
|
});
|
|
23
23
|
return seedInstances.filter((seed) => seed.isActive());
|
|
24
24
|
};
|
|
25
|
+
// src/run.ts
|
|
26
|
+
import { container as container3 } from "@ooneex/container";
|
|
27
|
+
import { TerminalLogger } from "@ooneex/logger";
|
|
28
|
+
var runSeed = async (seed) => {
|
|
29
|
+
const data = [];
|
|
30
|
+
const dependencies = await seed.getDependencies();
|
|
31
|
+
for (const dependency of dependencies) {
|
|
32
|
+
const dep = container3.get(dependency);
|
|
33
|
+
data.push(await runSeed(dep));
|
|
34
|
+
}
|
|
35
|
+
await seed.run(data);
|
|
36
|
+
};
|
|
37
|
+
var run = async () => {
|
|
38
|
+
const seeds = getSeeds();
|
|
39
|
+
const logger = new TerminalLogger;
|
|
40
|
+
if (seeds.length === 0) {
|
|
41
|
+
logger.info(`No seeds found
|
|
42
|
+
`, undefined, {
|
|
43
|
+
showTimestamp: false,
|
|
44
|
+
showArrow: false,
|
|
45
|
+
useSymbol: true
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
logger.info(`Running ${seeds.length} seed(s)...
|
|
50
|
+
`, undefined, {
|
|
51
|
+
showTimestamp: false,
|
|
52
|
+
showArrow: false,
|
|
53
|
+
useSymbol: true
|
|
54
|
+
});
|
|
55
|
+
for (const seed of seeds) {
|
|
56
|
+
const seedName = seed.constructor.name;
|
|
57
|
+
if (!seed.isActive()) {
|
|
58
|
+
logger.warn(`Seed ${seedName} is inactive
|
|
59
|
+
`, undefined, {
|
|
60
|
+
showTimestamp: false,
|
|
61
|
+
showArrow: false,
|
|
62
|
+
useSymbol: true
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await runSeed(seed);
|
|
68
|
+
logger.success(`Seed ${seedName} completed
|
|
69
|
+
`, undefined, {
|
|
70
|
+
showTimestamp: false,
|
|
71
|
+
showArrow: false,
|
|
72
|
+
useSymbol: true
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
logger.error(`Seed ${seedName} failed
|
|
76
|
+
`, undefined, {
|
|
77
|
+
showTimestamp: false,
|
|
78
|
+
showArrow: false,
|
|
79
|
+
useSymbol: true
|
|
80
|
+
});
|
|
81
|
+
logger.error(error);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
logger.success(`
|
|
86
|
+
All seeds completed successfully
|
|
87
|
+
`, undefined, {
|
|
88
|
+
showTimestamp: false,
|
|
89
|
+
showArrow: false,
|
|
90
|
+
useSymbol: true
|
|
91
|
+
});
|
|
92
|
+
};
|
|
25
93
|
// src/seedCreate.ts
|
|
26
94
|
import { join } from "path";
|
|
27
95
|
import { toKebabCase, toPascalCase } from "@ooneex/utils";
|
|
@@ -111,79 +179,11 @@ var seedCreate = async (config) => {
|
|
|
111
179
|
dataPath: join(seedsDir, `${dataFile}.yml`)
|
|
112
180
|
};
|
|
113
181
|
};
|
|
114
|
-
// src/seedRun.ts
|
|
115
|
-
import { container as container3 } from "@ooneex/container";
|
|
116
|
-
import { TerminalLogger } from "@ooneex/logger";
|
|
117
|
-
var run = async (seed) => {
|
|
118
|
-
const data = [];
|
|
119
|
-
const dependencies = await seed.getDependencies();
|
|
120
|
-
for (const dependency of dependencies) {
|
|
121
|
-
const dep = container3.get(dependency);
|
|
122
|
-
data.push(await run(dep));
|
|
123
|
-
}
|
|
124
|
-
await seed.run(data);
|
|
125
|
-
};
|
|
126
|
-
var seedRun = async () => {
|
|
127
|
-
const seeds = getSeeds();
|
|
128
|
-
const logger = new TerminalLogger;
|
|
129
|
-
if (seeds.length === 0) {
|
|
130
|
-
logger.info(`No seeds found
|
|
131
|
-
`, undefined, {
|
|
132
|
-
showTimestamp: false,
|
|
133
|
-
showArrow: false,
|
|
134
|
-
useSymbol: true
|
|
135
|
-
});
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
logger.info(`Running ${seeds.length} seed(s)...
|
|
139
|
-
`, undefined, {
|
|
140
|
-
showTimestamp: false,
|
|
141
|
-
showArrow: false,
|
|
142
|
-
useSymbol: true
|
|
143
|
-
});
|
|
144
|
-
for (const seed of seeds) {
|
|
145
|
-
const seedName = seed.constructor.name;
|
|
146
|
-
if (!seed.isActive()) {
|
|
147
|
-
logger.warn(`Seed ${seedName} is inactive
|
|
148
|
-
`, undefined, {
|
|
149
|
-
showTimestamp: false,
|
|
150
|
-
showArrow: false,
|
|
151
|
-
useSymbol: true
|
|
152
|
-
});
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
try {
|
|
156
|
-
await run(seed);
|
|
157
|
-
logger.success(`Seed ${seedName} completed
|
|
158
|
-
`, undefined, {
|
|
159
|
-
showTimestamp: false,
|
|
160
|
-
showArrow: false,
|
|
161
|
-
useSymbol: true
|
|
162
|
-
});
|
|
163
|
-
} catch (error) {
|
|
164
|
-
logger.error(`Seed ${seedName} failed
|
|
165
|
-
`, undefined, {
|
|
166
|
-
showTimestamp: false,
|
|
167
|
-
showArrow: false,
|
|
168
|
-
useSymbol: true
|
|
169
|
-
});
|
|
170
|
-
logger.error(error);
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
logger.success(`
|
|
175
|
-
All seeds completed successfully
|
|
176
|
-
`, undefined, {
|
|
177
|
-
showTimestamp: false,
|
|
178
|
-
showArrow: false,
|
|
179
|
-
useSymbol: true
|
|
180
|
-
});
|
|
181
|
-
};
|
|
182
182
|
export {
|
|
183
|
-
seedRun,
|
|
184
183
|
seedCreate,
|
|
184
|
+
run,
|
|
185
185
|
getSeeds,
|
|
186
186
|
decorator
|
|
187
187
|
};
|
|
188
188
|
|
|
189
|
-
//# debugId=
|
|
189
|
+
//# debugId=A5EDB9BE5A95E76164756E2164756E21
|
package/dist/index.js.map
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["src/decorators.ts", "src/container.ts", "src/getSeeds.ts", "src/
|
|
3
|
+
"sources": ["src/decorators.ts", "src/container.ts", "src/getSeeds.ts", "src/run.ts", "src/seedCreate.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { container, EContainerScope } from \"@ooneex/container\";\nimport { SEEDS_CONTAINER } from \"./container\";\nimport type { SeedClassType } from \"./types\";\n\nexport const decorator = {\n seed: (scope: EContainerScope = EContainerScope.Singleton) => {\n return (target: SeedClassType): void => {\n container.add(target, scope);\n SEEDS_CONTAINER.push(target);\n };\n },\n};\n",
|
|
6
6
|
"import type { SeedClassType } from \"./types\";\n\nexport const SEEDS_CONTAINER: SeedClassType[] = [];\n",
|
|
7
7
|
"import { container } from \"@ooneex/container\";\nimport { SEEDS_CONTAINER } from \"./container\";\nimport type { ISeed } from \"./types\";\n\nexport const getSeeds = (): ISeed[] => {\n const seedInstances = SEEDS_CONTAINER.map((SeedClass) => {\n return container.get(SeedClass);\n });\n\n return seedInstances.filter((seed) => seed.isActive());\n};\n",
|
|
8
|
-
"import {
|
|
9
|
-
"import {
|
|
8
|
+
"import { container } from \"@ooneex/container\";\nimport type { IException } from \"@ooneex/exception\";\nimport { TerminalLogger } from \"@ooneex/logger\";\nimport { getSeeds } from \"./getSeeds\";\nimport type { ISeed } from \"./types\";\n\nconst runSeed = async (seed: ISeed): Promise<void> => {\n const data = [];\n\n const dependencies = await seed.getDependencies();\n\n for (const dependency of dependencies) {\n const dep = container.get(dependency);\n data.push(await runSeed(dep));\n }\n\n await seed.run(data);\n};\n\nexport const run = async (): Promise<void> => {\n const seeds = getSeeds();\n const logger = new TerminalLogger();\n\n if (seeds.length === 0) {\n logger.info(\"No seeds found\\n\", undefined, {\n showTimestamp: false,\n showArrow: false,\n useSymbol: true,\n });\n return;\n }\n\n logger.info(`Running ${seeds.length} seed(s)...\\n`, undefined, {\n showTimestamp: false,\n showArrow: false,\n useSymbol: true,\n });\n\n for (const seed of seeds) {\n const seedName = seed.constructor.name;\n\n if (!seed.isActive()) {\n logger.warn(`Seed ${seedName} is inactive\\n`, undefined, {\n showTimestamp: false,\n showArrow: false,\n useSymbol: true,\n });\n continue;\n }\n\n try {\n await runSeed(seed);\n logger.success(`Seed ${seedName} completed\\n`, undefined, {\n showTimestamp: false,\n showArrow: false,\n useSymbol: true,\n });\n } catch (error) {\n logger.error(`Seed ${seedName} failed\\n`, undefined, {\n showTimestamp: false,\n showArrow: false,\n useSymbol: true,\n });\n logger.error(error as IException);\n process.exit(1);\n }\n }\n\n logger.success(\"\\nAll seeds completed successfully\\n\", undefined, {\n showTimestamp: false,\n showArrow: false,\n useSymbol: true,\n });\n};\n",
|
|
9
|
+
"import { join } from \"node:path\";\nimport { toKebabCase, toPascalCase } from \"@ooneex/utils\";\nimport { Glob } from \"bun\";\nimport testTemplate from \"./seed.test.txt\";\nimport template from \"./seed.txt\";\n\nexport const seedCreate = async (config: {\n name: string;\n seedsDir?: string;\n testsDir?: string;\n}): Promise<{ seedPath: string; testPath: string; dataPath: string }> => {\n const name = toPascalCase(config.name).replace(/Seed$/, \"\");\n const seedName = `${name}Seed`;\n const dataFile = toKebabCase(seedName);\n const seedsDir = config.seedsDir || \"seeds\";\n const testsDir = config.testsDir || join(\"tests\", \"seeds\");\n\n const seedContent = template.replaceAll(\"{{ name }}\", seedName).replaceAll(\"{{ dataFile }}\", dataFile);\n await Bun.write(join(process.cwd(), seedsDir, `${seedName}.ts`), seedContent);\n\n await Bun.write(join(process.cwd(), seedsDir, `${dataFile}.yml`), \"# Seed data\\n\");\n\n const testContent = testTemplate.replace(/\\{\\{NAME\\}\\}/g, name).replace(/\\{\\{DATA_FILE\\}\\}/g, dataFile);\n await Bun.write(join(process.cwd(), testsDir, `${seedName}.spec.ts`), testContent);\n\n const imports: string[] = [];\n const glob = new Glob(\"**/*Seed.ts\");\n for await (const file of glob.scan(join(process.cwd(), seedsDir))) {\n const seedClassName = file.replace(/\\.ts$/, \"\");\n imports.push(`export { ${seedClassName} } from './${seedClassName}';`);\n }\n\n await Bun.write(join(process.cwd(), seedsDir, \"seeds.ts\"), `${imports.sort().join(\"\\n\")}\\n`);\n\n return {\n seedPath: join(seedsDir, `${seedName}.ts`),\n testPath: join(testsDir, `${seedName}.spec.ts`),\n dataPath: join(seedsDir, `${dataFile}.yml`),\n };\n};\n"
|
|
10
10
|
],
|
|
11
|
-
"mappings": ";;AAAA;;;ACEO,IAAM,kBAAmC,CAAC;;;ADE1C,IAAM,YAAY;AAAA,EACvB,MAAM,CAAC,QAAyB,gBAAgB,cAAc;AAAA,IAC5D,OAAO,CAAC,WAAgC;AAAA,MACtC,UAAU,IAAI,QAAQ,KAAK;AAAA,MAC3B,gBAAgB,KAAK,MAAM;AAAA;AAAA;AAGjC;;AEXA,sBAAS;AAIF,IAAM,WAAW,MAAe;AAAA,EACrC,MAAM,gBAAgB,gBAAgB,IAAI,CAAC,cAAc;AAAA,IACvD,OAAO,WAAU,IAAI,SAAS;AAAA,GAC/B;AAAA,EAED,OAAO,cAAc,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AAAA;;ACTvD
|
|
12
|
-
"debugId": "
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "A5EDB9BE5A95E76164756E2164756E21",
|
|
13
13
|
"names": []
|
|
14
14
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ooneex/seeds",
|
|
3
3
|
"description": "Database seeding framework for populating initial data, fixtures, and test datasets with execution logging and idempotent operations",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"npm:publish": "bun publish --tolerate-republish --force --production --access public"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@ooneex/container": "1.3.
|
|
32
|
-
"@ooneex/logger": "1.2.
|
|
33
|
-
"@ooneex/utils": "0.4.
|
|
31
|
+
"@ooneex/container": "1.3.2",
|
|
32
|
+
"@ooneex/logger": "1.2.10",
|
|
33
|
+
"@ooneex/utils": "0.4.4"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@ooneex/exception": "1.2.
|
|
36
|
+
"@ooneex/exception": "1.2.3"
|
|
37
37
|
},
|
|
38
38
|
"keywords": [
|
|
39
39
|
"bun",
|