@c9up/atlas 0.1.19 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -14
- package/db.darwin-arm64.node +0 -0
- package/db.darwin-x64.node +0 -0
- package/db.linux-arm64-gnu.node +0 -0
- package/db.linux-x64-gnu.node +0 -0
- package/db.win32-x64-msvc.node +0 -0
- package/dist/AtlasProvider.d.ts +66 -7
- package/dist/AtlasProvider.d.ts.map +1 -1
- package/dist/AtlasProvider.js +205 -39
- package/dist/AtlasProvider.js.map +1 -1
- package/dist/BaseEntity.d.ts +181 -5
- package/dist/BaseEntity.d.ts.map +1 -1
- package/dist/BaseEntity.js +339 -33
- package/dist/BaseEntity.js.map +1 -1
- package/dist/BaseModel.d.ts +127 -0
- package/dist/BaseModel.d.ts.map +1 -0
- package/dist/BaseModel.js +250 -0
- package/dist/BaseModel.js.map +1 -0
- package/dist/BaseRepository.d.ts +81 -15
- package/dist/BaseRepository.d.ts.map +1 -1
- package/dist/BaseRepository.js +1433 -387
- package/dist/BaseRepository.js.map +1 -1
- package/dist/ConnectionManager.d.ts +106 -0
- package/dist/ConnectionManager.d.ts.map +1 -0
- package/dist/ConnectionManager.js +228 -0
- package/dist/ConnectionManager.js.map +1 -0
- package/dist/ModelQuery.d.ts +577 -41
- package/dist/ModelQuery.d.ts.map +1 -1
- package/dist/ModelQuery.js +2188 -291
- package/dist/ModelQuery.js.map +1 -1
- package/dist/Transaction.d.ts +71 -1
- package/dist/Transaction.d.ts.map +1 -1
- package/dist/Transaction.js +138 -24
- package/dist/Transaction.js.map +1 -1
- package/dist/adapters/NapiDbAdapter.d.ts +44 -4
- package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
- package/dist/adapters/NapiDbAdapter.js +131 -13
- package/dist/adapters/NapiDbAdapter.js.map +1 -1
- package/dist/console/contract.d.ts +62 -0
- package/dist/console/contract.d.ts.map +1 -0
- package/dist/console/contract.js +38 -0
- package/dist/console/contract.js.map +1 -0
- package/dist/console/factoryCommands.d.ts +23 -0
- package/dist/console/factoryCommands.d.ts.map +1 -0
- package/dist/console/factoryCommands.js +62 -0
- package/dist/console/factoryCommands.js.map +1 -0
- package/dist/console/migrationCommands.d.ts +78 -0
- package/dist/console/migrationCommands.d.ts.map +1 -0
- package/dist/console/migrationCommands.js +327 -0
- package/dist/console/migrationCommands.js.map +1 -0
- package/dist/console/schemaCheckCommand.d.ts +11 -17
- package/dist/console/schemaCheckCommand.d.ts.map +1 -1
- package/dist/console/schemaCheckCommand.js +21 -15
- package/dist/console/schemaCheckCommand.js.map +1 -1
- package/dist/console/schemaDumpCommand.d.ts +30 -0
- package/dist/console/schemaDumpCommand.d.ts.map +1 -0
- package/dist/console/schemaDumpCommand.js +69 -0
- package/dist/console/schemaDumpCommand.js.map +1 -0
- package/dist/console/schemaGenerateCommand.d.ts +100 -0
- package/dist/console/schemaGenerateCommand.d.ts.map +1 -0
- package/dist/console/schemaGenerateCommand.js +246 -0
- package/dist/console/schemaGenerateCommand.js.map +1 -0
- package/dist/console/seederCommands.d.ts +46 -0
- package/dist/console/seederCommands.d.ts.map +1 -0
- package/dist/console/seederCommands.js +136 -0
- package/dist/console/seederCommands.js.map +1 -0
- package/dist/decorators/entity.d.ts +50 -8
- package/dist/decorators/entity.d.ts.map +1 -1
- package/dist/decorators/entity.js +42 -2
- package/dist/decorators/entity.js.map +1 -1
- package/dist/decorators/hooks.d.ts +3 -3
- package/dist/decorators/hooks.d.ts.map +1 -1
- package/dist/decorators/hooks.js.map +1 -1
- package/dist/events.d.ts +69 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +82 -0
- package/dist/events.js.map +1 -0
- package/dist/factories.d.ts +8 -0
- package/dist/factories.d.ts.map +1 -0
- package/dist/factories.js +8 -0
- package/dist/factories.js.map +1 -0
- package/dist/index.d.ts +15 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/lucid-schema.d.ts +15 -0
- package/dist/lucid-schema.d.ts.map +1 -0
- package/dist/lucid-schema.js +16 -0
- package/dist/lucid-schema.js.map +1 -0
- package/dist/metadata-keys.d.ts +3 -2
- package/dist/metadata-keys.d.ts.map +1 -1
- package/dist/naming/NamingStrategy.d.ts +7 -0
- package/dist/naming/NamingStrategy.d.ts.map +1 -1
- package/dist/naming/NamingStrategy.js +16 -0
- package/dist/naming/NamingStrategy.js.map +1 -1
- package/dist/orm.d.ts +14 -0
- package/dist/orm.d.ts.map +1 -0
- package/dist/orm.js +14 -0
- package/dist/orm.js.map +1 -0
- package/dist/query/DatabaseQueryBuilder.d.ts +557 -0
- package/dist/query/DatabaseQueryBuilder.d.ts.map +1 -0
- package/dist/query/DatabaseQueryBuilder.js +1798 -0
- package/dist/query/DatabaseQueryBuilder.js.map +1 -0
- package/dist/query/DmlBuilder.d.ts +62 -0
- package/dist/query/DmlBuilder.d.ts.map +1 -0
- package/dist/query/DmlBuilder.js +84 -0
- package/dist/query/DmlBuilder.js.map +1 -0
- package/dist/query/QueryBuilder.d.ts +5 -0
- package/dist/query/QueryBuilder.d.ts.map +1 -1
- package/dist/query/QueryBuilder.js +7 -0
- package/dist/query/QueryBuilder.js.map +1 -1
- package/dist/query/RawQueryBuilder.d.ts +47 -0
- package/dist/query/RawQueryBuilder.d.ts.map +1 -0
- package/dist/query/RawQueryBuilder.js +138 -0
- package/dist/query/RawQueryBuilder.js.map +1 -0
- package/dist/query/interpolate.d.ts +45 -0
- package/dist/query/interpolate.d.ts.map +1 -0
- package/dist/query/interpolate.js +51 -0
- package/dist/query/interpolate.js.map +1 -0
- package/dist/query/native.d.ts +8 -0
- package/dist/query/native.d.ts.map +1 -1
- package/dist/query/native.js +14 -0
- package/dist/query/native.js.map +1 -1
- package/dist/query/operators.d.ts +4 -0
- package/dist/query/operators.d.ts.map +1 -0
- package/dist/query/operators.js +27 -0
- package/dist/query/operators.js.map +1 -0
- package/dist/schema/Migration.d.ts +50 -3
- package/dist/schema/Migration.d.ts.map +1 -1
- package/dist/schema/Migration.js +60 -24
- package/dist/schema/Migration.js.map +1 -1
- package/dist/schema/MigrationRunner.d.ts +95 -34
- package/dist/schema/MigrationRunner.d.ts.map +1 -1
- package/dist/schema/MigrationRunner.js +523 -68
- package/dist/schema/MigrationRunner.js.map +1 -1
- package/dist/schema/Schema.d.ts +114 -0
- package/dist/schema/Schema.d.ts.map +1 -1
- package/dist/schema/Schema.js +272 -3
- package/dist/schema/Schema.js.map +1 -1
- package/dist/schema/SchemaDumper.d.ts +121 -0
- package/dist/schema/SchemaDumper.d.ts.map +1 -0
- package/dist/schema/SchemaDumper.js +365 -0
- package/dist/schema/SchemaDumper.js.map +1 -0
- package/dist/schema/Seeder.d.ts +13 -0
- package/dist/schema/Seeder.d.ts.map +1 -1
- package/dist/schema/Seeder.js +28 -5
- package/dist/schema/Seeder.js.map +1 -1
- package/dist/schema/TableBuilder.d.ts +274 -16
- package/dist/schema/TableBuilder.d.ts.map +1 -1
- package/dist/schema/TableBuilder.js +656 -52
- package/dist/schema/TableBuilder.js.map +1 -1
- package/dist/schema/catalog.d.ts +71 -0
- package/dist/schema/catalog.d.ts.map +1 -0
- package/dist/schema/catalog.js +158 -0
- package/dist/schema/catalog.js.map +1 -0
- package/dist/schema/introspect.d.ts +1 -1
- package/dist/schema/introspect.d.ts.map +1 -1
- package/dist/schema/introspect.js +26 -11
- package/dist/schema/introspect.js.map +1 -1
- package/dist/schema/types.d.ts +150 -1
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +11 -0
- package/dist/schema/types.js.map +1 -1
- package/dist/seeders.d.ts +7 -0
- package/dist/seeders.d.ts.map +1 -0
- package/dist/seeders.js +7 -0
- package/dist/seeders.js.map +1 -0
- package/dist/services/db.d.ts +122 -5
- package/dist/services/db.d.ts.map +1 -1
- package/dist/services/db.js +187 -14
- package/dist/services/db.js.map +1 -1
- package/dist/testing/DatabaseCleanup.d.ts +33 -7
- package/dist/testing/DatabaseCleanup.d.ts.map +1 -1
- package/dist/testing/DatabaseCleanup.js +54 -24
- package/dist/testing/DatabaseCleanup.js.map +1 -1
- package/dist/testing/DbAssertions.d.ts +48 -0
- package/dist/testing/DbAssertions.d.ts.map +1 -0
- package/dist/testing/DbAssertions.js +80 -0
- package/dist/testing/DbAssertions.js.map +1 -0
- package/dist/testing/Factory.d.ts +166 -15
- package/dist/testing/Factory.d.ts.map +1 -1
- package/dist/testing/Factory.js +497 -23
- package/dist/testing/Factory.js.map +1 -1
- package/dist/testing/TestUtils.d.ts +49 -0
- package/dist/testing/TestUtils.d.ts.map +1 -0
- package/dist/testing/TestUtils.js +79 -0
- package/dist/testing/TestUtils.js.map +1 -0
- package/dist/testing/index.d.ts +3 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types/relations.d.ts +31 -0
- package/dist/types/relations.d.ts.map +1 -0
- package/dist/types/relations.js +20 -0
- package/dist/types/relations.js.map +1 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +32 -2
- package/scripts/guard-publish.mjs +15 -0
- package/src/AtlasProvider.ts +305 -52
- package/src/BaseEntity.ts +460 -41
- package/src/BaseModel.ts +461 -0
- package/src/BaseRepository.ts +1710 -421
- package/src/ConnectionManager.ts +298 -0
- package/src/ModelQuery.ts +2998 -335
- package/src/Transaction.ts +227 -30
- package/src/adapters/NapiDbAdapter.ts +224 -17
- package/src/console/contract.ts +96 -0
- package/src/console/factoryCommands.ts +73 -0
- package/src/console/migrationCommands.ts +448 -0
- package/src/console/schemaCheckCommand.ts +26 -22
- package/src/console/schemaDumpCommand.ts +96 -0
- package/src/console/schemaGenerateCommand.ts +380 -0
- package/src/console/seederCommands.ts +174 -0
- package/src/decorators/entity.ts +71 -8
- package/src/decorators/hooks.ts +5 -3
- package/src/events.ts +117 -0
- package/src/factories.ts +10 -0
- package/src/index.ts +69 -3
- package/src/lucid-schema.ts +22 -0
- package/src/metadata-keys.ts +3 -2
- package/src/naming/NamingStrategy.ts +23 -0
- package/src/orm.ts +13 -0
- package/src/query/DatabaseQueryBuilder.ts +2724 -0
- package/src/query/DmlBuilder.ts +131 -0
- package/src/query/QueryBuilder.ts +8 -0
- package/src/query/RawQueryBuilder.ts +173 -0
- package/src/query/interpolate.ts +73 -0
- package/src/query/native.ts +29 -0
- package/src/query/operators.ts +27 -0
- package/src/schema/Migration.ts +71 -3
- package/src/schema/MigrationRunner.ts +676 -69
- package/src/schema/Schema.ts +359 -3
- package/src/schema/SchemaDumper.ts +518 -0
- package/src/schema/Seeder.ts +47 -10
- package/src/schema/TableBuilder.ts +783 -52
- package/src/schema/catalog.ts +236 -0
- package/src/schema/introspect.ts +26 -9
- package/src/schema/types.ts +137 -2
- package/src/seeders.ts +16 -0
- package/src/services/db.ts +361 -21
- package/src/testing/DatabaseCleanup.ts +78 -25
- package/src/testing/DbAssertions.ts +127 -0
- package/src/testing/Factory.ts +837 -40
- package/src/testing/TestUtils.ts +123 -0
- package/src/testing/index.ts +12 -1
- package/src/types/relations.ts +43 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `schema:dump` — serialise the live database's schema to a `.sql` dump + a
|
|
3
|
+
* `.meta.json` manifest (Adonis Lucid `schema:dump` / `SchemaDumper`), so a
|
|
4
|
+
* fresh database can be rebuilt from the dump via `migration:run --schema-path`
|
|
5
|
+
* instead of replaying every migration.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // commands/schema-dump.ts
|
|
9
|
+
* import { schemaDumpCommand } from '@c9up/atlas'
|
|
10
|
+
* export default schemaDumpCommand({ migrationsDir: 'database/migrations' })
|
|
11
|
+
* // run: <console-entry> schema:dump → database/schema/default-schema.sql
|
|
12
|
+
* // schema:dump --prune → also squashes the migration files
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { SchemaDumper } from "../schema/SchemaDumper.js";
|
|
16
|
+
import { getConnection, getDb } from "../services/db.js";
|
|
17
|
+
import { type AtlasCommandClass, flag } from "./contract.js";
|
|
18
|
+
|
|
19
|
+
export interface SchemaDumpCommandOptions {
|
|
20
|
+
/** Directory the dump + manifest are written to. Default `"database/schema"`. */
|
|
21
|
+
outputDir?: string;
|
|
22
|
+
/** Migration directory — required for `--prune` (the files it collapses). */
|
|
23
|
+
migrationsDir?: string;
|
|
24
|
+
/** Migration bookkeeping table name. Default `"ream_migrations"`. */
|
|
25
|
+
schemaTableName?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Timestamp string for the manifest. `Date.now()`/`new Date()` are unavailable
|
|
28
|
+
* in some atlas contexts, so pass one in when you need a deterministic value.
|
|
29
|
+
*/
|
|
30
|
+
generatedAt?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** `schema:dump` — dump the schema; `--prune` squashes migrations, `--connection` targets one. */
|
|
34
|
+
export function schemaDumpCommand(
|
|
35
|
+
options: SchemaDumpCommandOptions = {},
|
|
36
|
+
): AtlasCommandClass {
|
|
37
|
+
return class SchemaDump {
|
|
38
|
+
static commandName = "schema:dump";
|
|
39
|
+
static description =
|
|
40
|
+
"Dump the database schema to a .sql file + manifest (--prune, --connection, --path)";
|
|
41
|
+
static options = { startApp: true };
|
|
42
|
+
static flags = [
|
|
43
|
+
flag("connection", "string", {
|
|
44
|
+
description: "Named connection to dump (defaults to the primary one)",
|
|
45
|
+
}),
|
|
46
|
+
flag("prune", "boolean", {
|
|
47
|
+
description: "Squash the migrations the dump replaces",
|
|
48
|
+
}),
|
|
49
|
+
flag("path", "string", {
|
|
50
|
+
description: "Destination .sql file (not a directory)",
|
|
51
|
+
}),
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
declare connection?: string;
|
|
55
|
+
declare prune: boolean;
|
|
56
|
+
declare path?: string;
|
|
57
|
+
|
|
58
|
+
async run(): Promise<void> {
|
|
59
|
+
const connName = this.connection;
|
|
60
|
+
const db = connName ? getConnection(connName) : getDb();
|
|
61
|
+
if (!db) {
|
|
62
|
+
console.error(
|
|
63
|
+
connName
|
|
64
|
+
? `[atlas] no connection registered under '${connName}'`
|
|
65
|
+
: "[atlas] no database connection — is AtlasProvider registered?",
|
|
66
|
+
);
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Typed by the kernel now — no string/boolean coercion to redo here.
|
|
71
|
+
const prune = this.prune === true;
|
|
72
|
+
// `--path <file>` is the SQL dump FILE path (Adonis Lucid), not a dir.
|
|
73
|
+
const dumpPath = this.path;
|
|
74
|
+
const dumper = new SchemaDumper(db, {
|
|
75
|
+
connectionName: connName ?? "default",
|
|
76
|
+
dumpPath,
|
|
77
|
+
outputDir: options.outputDir,
|
|
78
|
+
migrationsDir: options.migrationsDir,
|
|
79
|
+
schemaTableName: options.schemaTableName,
|
|
80
|
+
prune,
|
|
81
|
+
generatedAt: options.generatedAt,
|
|
82
|
+
});
|
|
83
|
+
await dumper.run();
|
|
84
|
+
if (dumper.error) {
|
|
85
|
+
console.error(`[atlas] schema:dump failed: ${dumper.error.message}`);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const r = dumper.result;
|
|
90
|
+
console.log(
|
|
91
|
+
`Dumped ${r?.tableCount ?? 0} table(s) → ${r?.dumpPath}` +
|
|
92
|
+
(prune ? " (migrations squashed)" : ""),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `schema:generate` — database-first codegen (Adonis Lucid parity). Introspects
|
|
3
|
+
* the live database and writes a `schema.ts` file with one `BaseModel` subclass
|
|
4
|
+
* per table, mirroring Lucid's `schema:generate` → `database/schema.ts`.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* // commands/schema-generate.ts
|
|
8
|
+
* import { schemaGenerateCommand } from '@c9up/atlas'
|
|
9
|
+
* export default schemaGenerateCommand({ outputPath: 'database/schema.ts' })
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fsp from "node:fs/promises";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import type { AsyncDatabaseConnection } from "../adapters/NapiDbAdapter.js";
|
|
15
|
+
import { listUserTables } from "../schema/catalog.js";
|
|
16
|
+
import {
|
|
17
|
+
type IntrospectedColumn,
|
|
18
|
+
introspectTable,
|
|
19
|
+
} from "../schema/introspect.js";
|
|
20
|
+
import { getConnection, getDb } from "../services/db.js";
|
|
21
|
+
import { type AtlasCommandClass, flag } from "./contract.js";
|
|
22
|
+
|
|
23
|
+
export interface SchemaGenerateOptions {
|
|
24
|
+
/** File to write the generated schema classes to (Lucid `outputPath`). */
|
|
25
|
+
outputPath: string;
|
|
26
|
+
/** Extra tables to skip (framework tables are always skipped). */
|
|
27
|
+
excludeTables?: string[];
|
|
28
|
+
/**
|
|
29
|
+
* When `false`, the `schema:generate` command and post-migration regeneration
|
|
30
|
+
* are both disabled (Adonis Lucid `schemaGeneration.enabled`). Any other value
|
|
31
|
+
* (incl. `undefined`) leaves generation on.
|
|
32
|
+
*/
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Paths to rule modules that customise how columns are emitted (Adonis Lucid
|
|
36
|
+
* `schemaGeneration.rulesPaths`). Each module default-exports a {@link SchemaRules}
|
|
37
|
+
* object; multiple files deep-merge, later paths overriding earlier ones.
|
|
38
|
+
*/
|
|
39
|
+
rulesPaths?: string[];
|
|
40
|
+
/**
|
|
41
|
+
* Emit a denser file — no blank line between a class's `$columns` and its
|
|
42
|
+
* column declarations (Adonis Lucid `--compact-output`). Also settable per-run
|
|
43
|
+
* with the `--compact-output` flag.
|
|
44
|
+
*/
|
|
45
|
+
compact?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* PostgreSQL only — restrict generation to these schemas (Adonis Lucid
|
|
48
|
+
* `schemaGeneration.schemas`). Ignored on sqlite/mysql. NOTE: the runtime
|
|
49
|
+
* behaviour is not yet proven against a live PostgreSQL in atlas's test env.
|
|
50
|
+
*/
|
|
51
|
+
schemas?: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Per-column override for schema generation (Adonis Lucid schema rules). A rule
|
|
56
|
+
* can force the TypeScript `tsType`, the `decorator`, and the `imports` the
|
|
57
|
+
* generated file needs for that type.
|
|
58
|
+
*/
|
|
59
|
+
export interface ColumnRule {
|
|
60
|
+
/** TypeScript type for the property (e.g. `"UserStatus"`). */
|
|
61
|
+
tsType?: string;
|
|
62
|
+
/** Full decorator string (e.g. `"@column()"`, `"@column({ isPrimary: true })"`). */
|
|
63
|
+
decorator?: string;
|
|
64
|
+
/** Imports the `tsType`/`decorator` needs, added to the generated file header. */
|
|
65
|
+
imports?: Array<{ source: string; namedImports: string[] }>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Schema-generation rules (Adonis Lucid). Resolution is most-specific-first:
|
|
70
|
+
* `tables[table].columns[col]` > `columns[col]` > `types[rawType]`.
|
|
71
|
+
*/
|
|
72
|
+
export interface SchemaRules {
|
|
73
|
+
/** Rules keyed by column name, applied to that column on every table. */
|
|
74
|
+
columns?: Record<string, ColumnRule>;
|
|
75
|
+
/** Rules keyed by raw dialect type (lower-cased), applied by column type. */
|
|
76
|
+
types?: Record<string, ColumnRule>;
|
|
77
|
+
/** Table-scoped column rules, the highest-priority match. */
|
|
78
|
+
tables?: Record<string, { columns?: Record<string, ColumnRule> }>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Load + deep-merge the rule modules named by `rulesPaths` (later files win).
|
|
83
|
+
* Each module default-exports a {@link SchemaRules}; a module without a usable
|
|
84
|
+
* default export is skipped.
|
|
85
|
+
*/
|
|
86
|
+
export async function loadSchemaRules(
|
|
87
|
+
rulesPaths: string[] | undefined,
|
|
88
|
+
): Promise<SchemaRules> {
|
|
89
|
+
const merged: SchemaRules = {};
|
|
90
|
+
for (const p of rulesPaths ?? []) {
|
|
91
|
+
const mod = await import(p);
|
|
92
|
+
const rules: unknown = mod.default ?? mod;
|
|
93
|
+
if (rules && typeof rules === "object") {
|
|
94
|
+
mergeRules(merged, rules as SchemaRules);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return merged;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Deep-merge `src` rules into `dst` (column/type/table maps), src winning. */
|
|
101
|
+
function mergeRules(dst: SchemaRules, src: SchemaRules): void {
|
|
102
|
+
dst.columns = { ...dst.columns, ...src.columns };
|
|
103
|
+
dst.types = { ...dst.types, ...src.types };
|
|
104
|
+
dst.tables = { ...dst.tables };
|
|
105
|
+
for (const [table, rule] of Object.entries(src.tables ?? {})) {
|
|
106
|
+
dst.tables[table] = {
|
|
107
|
+
columns: { ...dst.tables[table]?.columns, ...rule.columns },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Resolve the winning rule for a column (table-scoped > column > type), if any. */
|
|
113
|
+
function resolveRule(
|
|
114
|
+
rules: SchemaRules,
|
|
115
|
+
table: string,
|
|
116
|
+
col: IntrospectedColumn,
|
|
117
|
+
): ColumnRule | undefined {
|
|
118
|
+
return (
|
|
119
|
+
rules.tables?.[table]?.columns?.[col.name] ??
|
|
120
|
+
rules.columns?.[col.name] ??
|
|
121
|
+
rules.types?.[col.type.toLowerCase()]
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** `snake_case` / `snake_case_id` → `camelCase`. */
|
|
126
|
+
function toCamel(name: string): string {
|
|
127
|
+
return name.replace(/_([a-z0-9])/g, (_, c: string) => c.toUpperCase());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** `snake_case` table → `PascalCase` class stem. */
|
|
131
|
+
function toPascal(name: string): string {
|
|
132
|
+
const camel = toCamel(name);
|
|
133
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** `camelCase` → `snake_case`, to detect when a DB column needs `columnName`. */
|
|
137
|
+
function toSnake(name: string): string {
|
|
138
|
+
return name.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Is this a date/time column (→ `@column.dateTime` + a Chronos `DateTime`)? */
|
|
142
|
+
function isDateType(rawType: string): boolean {
|
|
143
|
+
return /timestamp|datetime|date|time/.test(rawType.toLowerCase());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Map a NON-date raw dialect column type to a TypeScript type. */
|
|
147
|
+
function toTsType(rawType: string): string {
|
|
148
|
+
const t = rawType.toLowerCase();
|
|
149
|
+
if (/bool/.test(t)) return "boolean";
|
|
150
|
+
if (/int|serial|real|float|double|number/.test(t)) return "number";
|
|
151
|
+
// decimal/numeric hydrate as strings (atlas keeps precision) — Lucid parity.
|
|
152
|
+
if (/decimal|numeric/.test(t)) return "string";
|
|
153
|
+
if (/json/.test(t)) return "unknown";
|
|
154
|
+
if (/bytea|blob|binary/.test(t)) return "Uint8Array";
|
|
155
|
+
return "string"; // varchar / text / char / uuid / everything else
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** `columnName: "..."` fragment when the DB name doesn't round-trip from camelCase. */
|
|
159
|
+
function columnNameOpt(col: IntrospectedColumn, prop: string): string {
|
|
160
|
+
return toSnake(prop) === col.name ? "" : `columnName: "${col.name}"`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** A source → named-imports accumulator, deduped, for the generated file header. */
|
|
164
|
+
type ImportSink = Map<string, Set<string>>;
|
|
165
|
+
|
|
166
|
+
/** Record a rule's imports into the sink. */
|
|
167
|
+
function collectImports(sink: ImportSink, rule: ColumnRule | undefined): void {
|
|
168
|
+
for (const imp of rule?.imports ?? []) {
|
|
169
|
+
const set = sink.get(imp.source) ?? new Set<string>();
|
|
170
|
+
for (const n of imp.namedImports) set.add(n);
|
|
171
|
+
sink.set(imp.source, set);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Render one column declaration line (Lucid decorator + typed property). A
|
|
177
|
+
* matching {@link SchemaRules} entry overrides the decorator and TS type and
|
|
178
|
+
* contributes its imports to `sink`.
|
|
179
|
+
*/
|
|
180
|
+
function renderColumn(
|
|
181
|
+
col: IntrospectedColumn,
|
|
182
|
+
table: string,
|
|
183
|
+
rules: SchemaRules,
|
|
184
|
+
sink: ImportSink,
|
|
185
|
+
): string {
|
|
186
|
+
const prop = toCamel(col.name);
|
|
187
|
+
const cn = columnNameOpt(col, prop);
|
|
188
|
+
const nullable = col.nullable && !col.primaryKey ? " | null" : "";
|
|
189
|
+
|
|
190
|
+
// A rule wins over the built-in mapping (custom enum types, decorators, …).
|
|
191
|
+
const rule = resolveRule(rules, table, col);
|
|
192
|
+
if (rule?.decorator || rule?.tsType) {
|
|
193
|
+
collectImports(sink, rule);
|
|
194
|
+
const decorator =
|
|
195
|
+
rule.decorator ?? (col.primaryKey ? "@PrimaryKey()" : "@Column()");
|
|
196
|
+
const tsType = rule.tsType ?? toTsType(col.type);
|
|
197
|
+
return `\t${decorator} declare ${prop}: ${tsType}${nullable};`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (isDateType(col.type) && !col.primaryKey) {
|
|
201
|
+
const opts = cn ? `{ ${cn} }` : "";
|
|
202
|
+
const dateNullable = col.nullable ? " | null" : "";
|
|
203
|
+
return `\t@column.dateTime(${opts}) declare ${prop}: DateTime${dateNullable};`;
|
|
204
|
+
}
|
|
205
|
+
const opts = cn ? `{ ${cn} }` : "";
|
|
206
|
+
const decorator = col.primaryKey ? "@PrimaryKey()" : `@Column(${opts})`;
|
|
207
|
+
return `\t${decorator} declare ${prop}: ${toTsType(col.type)}${nullable};`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Render one `<Table>Schema extends BaseModel` class (with Lucid `$columns`).
|
|
212
|
+
* A non-default `schema` qualifies `static table` (`"schema.table"` — the Rust
|
|
213
|
+
* compiler quotes it as `"schema"."table"`) and prefixes the class name to keep
|
|
214
|
+
* `public.users` / `tenant.users` distinct.
|
|
215
|
+
*/
|
|
216
|
+
function renderClass(
|
|
217
|
+
table: string,
|
|
218
|
+
columns: IntrospectedColumn[],
|
|
219
|
+
rules: SchemaRules,
|
|
220
|
+
sink: ImportSink,
|
|
221
|
+
compact: boolean,
|
|
222
|
+
schema?: string,
|
|
223
|
+
): string {
|
|
224
|
+
const className = schema
|
|
225
|
+
? `${toPascal(schema)}${toPascal(table)}Schema`
|
|
226
|
+
: `${toPascal(table)}Schema`;
|
|
227
|
+
const qualifiedTable = schema ? `${schema}.${table}` : table;
|
|
228
|
+
const props = columns.map((c) => toCamel(c.name));
|
|
229
|
+
const cols = props.map((p) => `"${p}"`).join(", ");
|
|
230
|
+
const body = columns
|
|
231
|
+
.map((c) => renderColumn(c, table, rules, sink))
|
|
232
|
+
.join("\n");
|
|
233
|
+
// Compact drops the blank line between `$columns` and the column declarations.
|
|
234
|
+
const gap = compact ? "\n" : "\n\n";
|
|
235
|
+
return (
|
|
236
|
+
`export class ${className} extends BaseModel {\n` +
|
|
237
|
+
`\tstatic table = "${qualifiedTable}";\n` +
|
|
238
|
+
`\tstatic $columns = [${cols}] as const;\n` +
|
|
239
|
+
`\t$columns = ${className}.$columns;${gap}${body}\n}`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Emit the deduped `import { a, b } from "source"` lines from a sink. */
|
|
244
|
+
function renderRuleImports(sink: ImportSink): string {
|
|
245
|
+
const lines: string[] = [];
|
|
246
|
+
for (const [source, names] of sink) {
|
|
247
|
+
lines.push(`import { ${[...names].join(", ")} } from "${source}";\n`);
|
|
248
|
+
}
|
|
249
|
+
return lines.join("");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Render the full `schema.ts` source from introspected tables. Pure — no DB
|
|
254
|
+
* access — so it is unit-testable. `rules` (Adonis Lucid `rulesPaths`) override
|
|
255
|
+
* per-column TS types/decorators. Manual edits to the output are lost on
|
|
256
|
+
* regeneration (same contract as Lucid).
|
|
257
|
+
*/
|
|
258
|
+
export function renderSchemaFile(
|
|
259
|
+
tables: ReadonlyArray<{
|
|
260
|
+
table: string;
|
|
261
|
+
columns: IntrospectedColumn[];
|
|
262
|
+
schema?: string;
|
|
263
|
+
}>,
|
|
264
|
+
rules: SchemaRules = {},
|
|
265
|
+
compact = false,
|
|
266
|
+
): string {
|
|
267
|
+
const sink: ImportSink = new Map();
|
|
268
|
+
// Render the classes first so rule imports are fully collected for the header.
|
|
269
|
+
const classes = tables
|
|
270
|
+
.map((t) => renderClass(t.table, t.columns, rules, sink, compact, t.schema))
|
|
271
|
+
.join(compact ? "\n" : "\n\n");
|
|
272
|
+
const hasDate = tables.some((t) =>
|
|
273
|
+
t.columns.some(
|
|
274
|
+
(c) => isDateType(c.type) && !resolveRule(rules, t.table, c)?.tsType,
|
|
275
|
+
),
|
|
276
|
+
);
|
|
277
|
+
const header =
|
|
278
|
+
"// Generated by @c9up/atlas schema:generate — DO NOT EDIT.\n" +
|
|
279
|
+
"// Manual edits are lost on regeneration.\n" +
|
|
280
|
+
'import { BaseModel, Column, column, PrimaryKey } from "@c9up/atlas";\n' +
|
|
281
|
+
(hasDate ? 'import { DateTime } from "@c9up/chronos";\n' : "") +
|
|
282
|
+
renderRuleImports(sink);
|
|
283
|
+
if (tables.length === 0) return `${header}\n// (no tables)\n`;
|
|
284
|
+
return `${header}\n${classes}\n`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Introspect the live database and (re)write the schema file. Returns the number
|
|
289
|
+
* of tables written. Reused by the {@link schemaGenerateCommand} and by the
|
|
290
|
+
* migration commands' auto-regeneration (Adonis Lucid regenerates after
|
|
291
|
+
* migrate/rollback/etc).
|
|
292
|
+
*/
|
|
293
|
+
export async function generateSchemaFile(
|
|
294
|
+
db: AsyncDatabaseConnection,
|
|
295
|
+
options: SchemaGenerateOptions,
|
|
296
|
+
): Promise<number> {
|
|
297
|
+
const exclude = new Set(options.excludeTables ?? []);
|
|
298
|
+
const tables: Array<{
|
|
299
|
+
table: string;
|
|
300
|
+
columns: IntrospectedColumn[];
|
|
301
|
+
schema?: string;
|
|
302
|
+
}> = [];
|
|
303
|
+
|
|
304
|
+
// With `schemas`, list + introspect each named schema's tables against THAT
|
|
305
|
+
// schema (Lucid). Without it, use the current schema/database as before.
|
|
306
|
+
const schemaScopes: Array<string | undefined> =
|
|
307
|
+
options.schemas && options.schemas.length > 0
|
|
308
|
+
? options.schemas
|
|
309
|
+
: [undefined];
|
|
310
|
+
for (const schema of schemaScopes) {
|
|
311
|
+
const names = (
|
|
312
|
+
await listUserTables(db, db.dialect, {
|
|
313
|
+
schemas: schema ? [schema] : undefined,
|
|
314
|
+
})
|
|
315
|
+
)
|
|
316
|
+
.filter((n) => !exclude.has(n))
|
|
317
|
+
.sort();
|
|
318
|
+
for (const table of names) {
|
|
319
|
+
const columns = await introspectTable(db, db.dialect, table, schema);
|
|
320
|
+
if (columns) tables.push({ table, columns, schema });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const rules = await loadSchemaRules(options.rulesPaths);
|
|
325
|
+
const source = renderSchemaFile(tables, rules, options.compact ?? false);
|
|
326
|
+
await fsp.mkdir(path.dirname(options.outputPath), { recursive: true });
|
|
327
|
+
await fsp.writeFile(options.outputPath, source);
|
|
328
|
+
return tables.length;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** `schema:generate` — introspect the DB and (re)write the schema file. */
|
|
332
|
+
export function schemaGenerateCommand(
|
|
333
|
+
options: SchemaGenerateOptions,
|
|
334
|
+
): AtlasCommandClass {
|
|
335
|
+
return class SchemaGenerate {
|
|
336
|
+
static commandName = "schema:generate";
|
|
337
|
+
static description =
|
|
338
|
+
"Introspect the database and generate BaseModel schema classes";
|
|
339
|
+
static options = { startApp: true };
|
|
340
|
+
static flags = [
|
|
341
|
+
flag("connection", "string", {
|
|
342
|
+
description: "Named connection to introspect",
|
|
343
|
+
}),
|
|
344
|
+
flag("compactOutput", "boolean", {
|
|
345
|
+
description: "Override the configured `compact` rendering",
|
|
346
|
+
}),
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
declare connection?: string;
|
|
350
|
+
declare compactOutput: boolean;
|
|
351
|
+
|
|
352
|
+
async run(): Promise<void> {
|
|
353
|
+
// `enabled: false` disables the command too (Adonis Lucid), not just the
|
|
354
|
+
// post-migration regeneration.
|
|
355
|
+
if (options.enabled === false) {
|
|
356
|
+
console.log("[atlas] schema generation is disabled (enabled: false)");
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
// `--connection <name>` targets a specific registered connection (Adonis
|
|
360
|
+
// Lucid); without it, the default connection is used.
|
|
361
|
+
const connName = this.connection;
|
|
362
|
+
const db = connName ? getConnection(connName) : getDb();
|
|
363
|
+
if (!db) {
|
|
364
|
+
console.error(
|
|
365
|
+
connName
|
|
366
|
+
? `[atlas] no connection registered under '${connName}'`
|
|
367
|
+
: "[atlas] no database connection — is AtlasProvider registered?",
|
|
368
|
+
);
|
|
369
|
+
process.exitCode = 1;
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
// `--compact-output` overrides the configured `compact`.
|
|
373
|
+
const compact = this.compactOutput === true ? true : options.compact;
|
|
374
|
+
const n = await generateSchemaFile(db, { ...options, compact });
|
|
375
|
+
console.log(
|
|
376
|
+
`Generated ${options.outputPath} (${n} table${n === 1 ? "" : "s"})`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeder console commands — the Ream-idiomatic CLI for scaffolding and running
|
|
3
|
+
* database seeders (Adonis Lucid `make:seeder` / `db:seed`). Same shape as the
|
|
4
|
+
* migration commands: plain `{ name, description, run }` objects registered in
|
|
5
|
+
* `reamrc.commands` and dispatched by the console kernel.
|
|
6
|
+
*
|
|
7
|
+
* Atlas has no global config registry (Lucid parity — you pass your own paths),
|
|
8
|
+
* so every factory takes the `seedersDir`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // commands/seed.ts
|
|
12
|
+
* import { dbSeedCommand } from '@c9up/atlas'
|
|
13
|
+
* export default dbSeedCommand({ seedersDir: 'database/seeders' })
|
|
14
|
+
* // run: <console-entry> db:seed (or --files=UserSeeder,PostSeeder)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as fsp from "node:fs/promises";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { runSeederDirectory } from "../schema/Seeder.js";
|
|
20
|
+
import { getConnection, getDb } from "../services/db.js";
|
|
21
|
+
import { assertSafeName } from "../utils/safePath.js";
|
|
22
|
+
import { type AtlasCommandClass, argument, flag } from "./contract.js";
|
|
23
|
+
|
|
24
|
+
export interface SeederCommandOptions {
|
|
25
|
+
/** Directory holding the seeder files. */
|
|
26
|
+
seedersDir: string;
|
|
27
|
+
/** Sort files numerically (`2_x` before `10_x`) — Adonis Lucid `naturalSort`. */
|
|
28
|
+
naturalSort?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Current environment (`development`/`testing`/`production`), used to skip a
|
|
31
|
+
* seeder whose `static environment` excludes it. Falls back to `NODE_ENV`;
|
|
32
|
+
* override per-run with `--environment`.
|
|
33
|
+
*/
|
|
34
|
+
environment?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Scaffold body for a fresh seeder (`make:seeder`). */
|
|
38
|
+
const SEEDER_STUB = `import { BaseSeeder } from '@c9up/atlas'
|
|
39
|
+
|
|
40
|
+
export default class extends BaseSeeder {
|
|
41
|
+
async run() {
|
|
42
|
+
// const repo = new BaseRepository(Model, this.db)
|
|
43
|
+
// await repo.upsert([{ /* ... */ }], ['uniqueColumn'], ['columnToUpdate'])
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* `make:seeder <name>` — scaffold a timestamped seeder file in `seedersDir`. The
|
|
50
|
+
* `Date.now()` prefix keeps files in creation order under the runner's
|
|
51
|
+
* lexicographic sort (same convention as migrations). The name is validated (no
|
|
52
|
+
* path separators / traversal) and written with `wx` so an existing seeder is
|
|
53
|
+
* never clobbered.
|
|
54
|
+
*/
|
|
55
|
+
export function makeSeederCommand(
|
|
56
|
+
options: SeederCommandOptions,
|
|
57
|
+
): AtlasCommandClass {
|
|
58
|
+
return class MakeSeeder {
|
|
59
|
+
static commandName = "make:seeder";
|
|
60
|
+
static description = "Scaffold a new seeder file";
|
|
61
|
+
// Filesystem only — no connection needed.
|
|
62
|
+
static options = { startApp: false };
|
|
63
|
+
static args = [argument("name", { description: "Seeder file name" })];
|
|
64
|
+
|
|
65
|
+
declare name: string;
|
|
66
|
+
|
|
67
|
+
async run(): Promise<void> {
|
|
68
|
+
// A missing name never reaches here: the kernel reports the required
|
|
69
|
+
// argument by name before `run()`.
|
|
70
|
+
try {
|
|
71
|
+
assertSafeName(this.name, "SEEDER_INVALID", "seeder");
|
|
72
|
+
} catch {
|
|
73
|
+
console.error(`[atlas] invalid seeder name: ${this.name}`);
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const fileName = `${Date.now()}_${this.name}.ts`;
|
|
78
|
+
const filePath = path.join(options.seedersDir, fileName);
|
|
79
|
+
await fsp.mkdir(options.seedersDir, { recursive: true });
|
|
80
|
+
await fsp.writeFile(filePath, SEEDER_STUB, { flag: "wx" });
|
|
81
|
+
console.log(`Created ${filePath}`);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* `db:seed` — run every seeder in `seedersDir` (Adonis Lucid `db:seed`). Flags:
|
|
88
|
+
* --files=A,B run only the named seeders (Lucid `--files`)
|
|
89
|
+
* --connection=name run against a registered connection (Lucid `--connection`)
|
|
90
|
+
* --compact-output terse output (one summary line, not per-seeder)
|
|
91
|
+
* --interactive accepted for Lucid compat; the console kernel is
|
|
92
|
+
* non-interactive, so it runs every seeder (no prompt)
|
|
93
|
+
*/
|
|
94
|
+
export function dbSeedCommand(
|
|
95
|
+
options: SeederCommandOptions,
|
|
96
|
+
): AtlasCommandClass {
|
|
97
|
+
return class DbSeed {
|
|
98
|
+
static commandName = "db:seed";
|
|
99
|
+
static description =
|
|
100
|
+
"Run database seeders (--files=A,B, --connection=name)";
|
|
101
|
+
static options = { startApp: true };
|
|
102
|
+
static flags = [
|
|
103
|
+
flag("files", "string", {
|
|
104
|
+
description: "Comma-separated seeder names to run",
|
|
105
|
+
}),
|
|
106
|
+
flag("connection", "string", {
|
|
107
|
+
description: "Named connection to seed",
|
|
108
|
+
}),
|
|
109
|
+
flag("compactOutput", "boolean", {
|
|
110
|
+
description: "One summary line instead of per-seeder output",
|
|
111
|
+
}),
|
|
112
|
+
flag("environment", "string", {
|
|
113
|
+
description: "Environment the seeders run under",
|
|
114
|
+
}),
|
|
115
|
+
flag("interactive", "boolean", {
|
|
116
|
+
description: "Accepted for Lucid compatibility; runs every seeder",
|
|
117
|
+
}),
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
declare files?: string;
|
|
121
|
+
declare connection?: string;
|
|
122
|
+
declare compactOutput: boolean;
|
|
123
|
+
declare environment?: string;
|
|
124
|
+
declare interactive: boolean;
|
|
125
|
+
|
|
126
|
+
async run(): Promise<void> {
|
|
127
|
+
const connName = this.connection;
|
|
128
|
+
const db = connName ? getConnection(connName) : getDb();
|
|
129
|
+
if (!db) {
|
|
130
|
+
console.error(
|
|
131
|
+
connName
|
|
132
|
+
? `[atlas] no connection registered under '${connName}'`
|
|
133
|
+
: "[atlas] no database connection — is AtlasProvider registered?",
|
|
134
|
+
);
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (this.interactive === true) {
|
|
139
|
+
console.warn(
|
|
140
|
+
"[atlas] --interactive is not supported in the non-interactive console; running all selected seeders.",
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const files = parseFilesFlag(this.files);
|
|
144
|
+
const environment =
|
|
145
|
+
this.environment ?? options.environment ?? process.env.NODE_ENV;
|
|
146
|
+
const executed = await runSeederDirectory(options.seedersDir, db, {
|
|
147
|
+
files,
|
|
148
|
+
naturalSort: options.naturalSort,
|
|
149
|
+
environment,
|
|
150
|
+
});
|
|
151
|
+
const compact = this.compactOutput === true;
|
|
152
|
+
if (compact) {
|
|
153
|
+
console.log(`Seeded ${executed.length} seeder(s)`);
|
|
154
|
+
} else {
|
|
155
|
+
console.log(
|
|
156
|
+
executed.length
|
|
157
|
+
? `Seeded: ${executed.join(", ")}`
|
|
158
|
+
: "No seeders to run",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Parse `--files=A,B,C` into a name list, or undefined when absent. */
|
|
166
|
+
function parseFilesFlag(
|
|
167
|
+
value: string | boolean | undefined,
|
|
168
|
+
): string[] | undefined {
|
|
169
|
+
if (typeof value !== "string" || value.length === 0) return undefined;
|
|
170
|
+
return value
|
|
171
|
+
.split(",")
|
|
172
|
+
.map((f) => f.trim())
|
|
173
|
+
.filter((f) => f.length > 0);
|
|
174
|
+
}
|