ts-prorm-orm 1.1.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/CHANGELOG.md +1111 -0
- package/LICENSE +21 -0
- package/README.md +573 -0
- package/dist/audit/history-query.js +180 -0
- package/dist/audit/index.js +23 -0
- package/dist/audit/logger.js +236 -0
- package/dist/cache/cache-manager.js +84 -0
- package/dist/cache/index.js +16 -0
- package/dist/cache/redis-cluster-cache.js +557 -0
- package/dist/cli.js +2200 -0
- package/dist/compliance/audit-trail.js +68 -0
- package/dist/compliance/backup-verification.js +685 -0
- package/dist/compliance/breach-detector.js +505 -0
- package/dist/compliance/consent-record.js +227 -0
- package/dist/compliance/consent-versioning.js +331 -0
- package/dist/compliance/cross-border-log.js +530 -0
- package/dist/compliance/data-classifier.js +258 -0
- package/dist/compliance/data-lineage.js +565 -0
- package/dist/compliance/data-masker.js +303 -0
- package/dist/compliance/data-portability.js +265 -0
- package/dist/compliance/data-retention.js +195 -0
- package/dist/compliance/dsar-workflow.js +342 -0
- package/dist/compliance/field-encryption.js +223 -0
- package/dist/compliance/immutable-record.js +148 -0
- package/dist/compliance/index.js +260 -0
- package/dist/compliance/privacy-impact-assessment.js +364 -0
- package/dist/compliance/pseudonymization.js +269 -0
- package/dist/compliance/query-firewall.js +1182 -0
- package/dist/compliance/rate-limiter.js +580 -0
- package/dist/compliance/right-to-erasure.js +117 -0
- package/dist/compliance/row-level-security.js +246 -0
- package/dist/compliance/security-decorator.js +574 -0
- package/dist/compliance/security-monitor.js +525 -0
- package/dist/compliance/sensitive-data-discovery.js +476 -0
- package/dist/compliance/session-isolation.js +472 -0
- package/dist/compliance/tls-enforcer.js +108 -0
- package/dist/compliance/worm-storage.js +712 -0
- package/dist/connection-manager.js +198 -0
- package/dist/connection-pool.js +519 -0
- package/dist/decorators/audit.js +136 -0
- package/dist/decorators/belongs-to-many.js +115 -0
- package/dist/decorators/belongs-to.js +115 -0
- package/dist/decorators/check.js +435 -0
- package/dist/decorators/collate.js +329 -0
- package/dist/decorators/comment.js +205 -0
- package/dist/decorators/database-settings.js +236 -0
- package/dist/decorators/default.js +244 -0
- package/dist/decorators/encryption.js +235 -0
- package/dist/decorators/engine.js +97 -0
- package/dist/decorators/fk-constraints.js +594 -0
- package/dist/decorators/foreign-table.js +136 -0
- package/dist/decorators/generated.js +274 -0
- package/dist/decorators/has-many.js +127 -0
- package/dist/decorators/has-one.js +116 -0
- package/dist/decorators/hstore.js +129 -0
- package/dist/decorators/index.js +355 -0
- package/dist/decorators/json-column.js +83 -0
- package/dist/decorators/jsonb.js +101 -0
- package/dist/decorators/orm-decorators.js +425 -0
- package/dist/decorators/permissions.js +294 -0
- package/dist/decorators/procedure.js +210 -0
- package/dist/decorators/query-options.js +556 -0
- package/dist/decorators/range.js +167 -0
- package/dist/decorators/set-column.js +83 -0
- package/dist/decorators/spatial.js +114 -0
- package/dist/decorators/storage.js +580 -0
- package/dist/decorators/timezone.js +512 -0
- package/dist/decorators/trigger.js +90 -0
- package/dist/decorators/uuid.js +135 -0
- package/dist/decorators/view.js +258 -0
- package/dist/diagrams/chen-diagram.js +354 -0
- package/dist/diagrams/class-diagram.js +384 -0
- package/dist/diagrams/dependency-diagram.js +432 -0
- package/dist/diagrams/er-diagram.js +605 -0
- package/dist/diagrams/flow-diagram.js +394 -0
- package/dist/diagrams/gantt-diagram.js +411 -0
- package/dist/diagrams/index-diagram.js +353 -0
- package/dist/diagrams/index.js +184 -0
- package/dist/diagrams/migration-diagram.js +316 -0
- package/dist/diagrams/model-diagram.js +616 -0
- package/dist/diagrams/package-diagram.js +376 -0
- package/dist/diagrams/palette.js +85 -0
- package/dist/diagrams/relational-diagram.js +455 -0
- package/dist/diagrams/schemadoc-diagram.js +309 -0
- package/dist/diagrams/sequence-diagram.js +307 -0
- package/dist/diagrams/state-diagram.js +344 -0
- package/dist/diagrams/svg-dom.js +111 -0
- package/dist/diagrams/tree-diagram.js +250 -0
- package/dist/dialects/clickhouse/index.js +1742 -0
- package/dist/dialects/cockroachdb/index.js +4677 -0
- package/dist/dialects/cratedb/index.js +441 -0
- package/dist/dialects/databricks/index.js +517 -0
- package/dist/dialects/db2/index.js +2225 -0
- package/dist/dialects/dialect.js +720 -0
- package/dist/dialects/duckdb/index.js +2014 -0
- package/dist/dialects/exasol/index.js +357 -0
- package/dist/dialects/firebird/index.js +495 -0
- package/dist/dialects/greenplum/index.js +457 -0
- package/dist/dialects/hana/index.js +1741 -0
- package/dist/dialects/mariadb/index.js +3590 -0
- package/dist/dialects/mssql/index.js +2296 -0
- package/dist/dialects/mysql/index.js +4058 -0
- package/dist/dialects/oracle/index.js +2999 -0
- package/dist/dialects/postgres/index.js +5319 -0
- package/dist/dialects/query-stream-helper.js +152 -0
- package/dist/dialects/questdb/index.js +433 -0
- package/dist/dialects/redshift/index.js +2277 -0
- package/dist/dialects/singlestore/index.js +354 -0
- package/dist/dialects/snowflake/index.js +2082 -0
- package/dist/dialects/spanner/index.js +1768 -0
- package/dist/dialects/sqlite/index.js +3382 -0
- package/dist/dialects/tidb/index.js +377 -0
- package/dist/dialects/timescaledb/index.js +164 -0
- package/dist/dialects/trino/index.js +420 -0
- package/dist/dialects/turso/index.js +227 -0
- package/dist/dialects/vertica/index.js +317 -0
- package/dist/dialects/yugabytedb/index.js +424 -0
- package/dist/errors/index.js +472 -0
- package/dist/errors/utils.js +340 -0
- package/dist/errors.js +21 -0
- package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
- package/dist/extensions/catalog/mssql-features.js +147 -0
- package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
- package/dist/extensions/catalog/oracle-db2-features.js +196 -0
- package/dist/extensions/catalog/postgres-extensions.js +516 -0
- package/dist/extensions/index.js +71 -0
- package/dist/extensions/types.js +13 -0
- package/dist/foreign-data.js +173 -0
- package/dist/hooks/hooks-manager.js +350 -0
- package/dist/hooks/index.js +37 -0
- package/dist/index.js +463 -0
- package/dist/logging.js +335 -0
- package/dist/migrations/index.js +40 -0
- package/dist/migrations/migration.js +196 -0
- package/dist/migrations/migrator.js +411 -0
- package/dist/migrations/prormmigration.js +275 -0
- package/dist/migrations/query-interface.js +435 -0
- package/dist/migrations/seeder.js +353 -0
- package/dist/models/associations.js +852 -0
- package/dist/models/constraints.js +288 -0
- package/dist/models/data-types.js +2518 -0
- package/dist/models/decorators.js +445 -0
- package/dist/models/index.js +33 -0
- package/dist/models/indexes.js +531 -0
- package/dist/models/methods.js +382 -0
- package/dist/models/model-manager.js +103 -0
- package/dist/models/model.js +5349 -0
- package/dist/models/operators.js +67 -0
- package/dist/models/scopes.js +189 -0
- package/dist/models/typescript-types.js +26 -0
- package/dist/nosql/aerospike/index.js +205 -0
- package/dist/nosql/allegrograph/index.js +169 -0
- package/dist/nosql/arangodb/index.js +364 -0
- package/dist/nosql/azure-blob/index.js +206 -0
- package/dist/nosql/beanstalkd/index.js +231 -0
- package/dist/nosql/beequeue/index.js +210 -0
- package/dist/nosql/bigchaindb/index.js +195 -0
- package/dist/nosql/bigtable/index.js +224 -0
- package/dist/nosql/blazegraph/index.js +173 -0
- package/dist/nosql/bullmq/index.js +191 -0
- package/dist/nosql/cassandra/index.js +174 -0
- package/dist/nosql/chroma/index.js +190 -0
- package/dist/nosql/cloudflare-kv/index.js +220 -0
- package/dist/nosql/coherence/index.js +200 -0
- package/dist/nosql/cosmosdb/index.js +157 -0
- package/dist/nosql/couchbase/index.js +213 -0
- package/dist/nosql/dax/index.js +212 -0
- package/dist/nosql/deno-kv/index.js +206 -0
- package/dist/nosql/dgraph/index.js +171 -0
- package/dist/nosql/doris/index.js +169 -0
- package/dist/nosql/druid/index.js +162 -0
- package/dist/nosql/dynamodb/index.js +937 -0
- package/dist/nosql/elasticsearch/index.js +377 -0
- package/dist/nosql/etcd/index.js +502 -0
- package/dist/nosql/eventhubs/index.js +213 -0
- package/dist/nosql/eventstore/index.js +254 -0
- package/dist/nosql/faunadb/index.js +188 -0
- package/dist/nosql/firestore/index.js +177 -0
- package/dist/nosql/fluree/index.js +148 -0
- package/dist/nosql/fuseki/index.js +170 -0
- package/dist/nosql/gcs/index.js +172 -0
- package/dist/nosql/gearman/index.js +160 -0
- package/dist/nosql/geode/index.js +196 -0
- package/dist/nosql/graphdb/index.js +169 -0
- package/dist/nosql/graylog/index.js +188 -0
- package/dist/nosql/gridgain/index.js +171 -0
- package/dist/nosql/hazelcast/index.js +162 -0
- package/dist/nosql/hbase/index.js +230 -0
- package/dist/nosql/ignite/index.js +173 -0
- package/dist/nosql/immudb/index.js +184 -0
- package/dist/nosql/index.js +232 -0
- package/dist/nosql/infinispan/index.js +200 -0
- package/dist/nosql/influxdb/index.js +0 -0
- package/dist/nosql/kafka/index.js +234 -0
- package/dist/nosql/keyspaces/index.js +189 -0
- package/dist/nosql/kinesis/index.js +253 -0
- package/dist/nosql/leveldb/index.js +153 -0
- package/dist/nosql/lmdb/index.js +160 -0
- package/dist/nosql/loki/index.js +201 -0
- package/dist/nosql/marklogic/index.js +204 -0
- package/dist/nosql/materialize/index.js +145 -0
- package/dist/nosql/meilisearch/index.js +154 -0
- package/dist/nosql/memcached/index.js +223 -0
- package/dist/nosql/milvus/index.js +410 -0
- package/dist/nosql/minio/index.js +265 -0
- package/dist/nosql/momento/index.js +179 -0
- package/dist/nosql/mongodb/index.js +461 -0
- package/dist/nosql/nats/index.js +247 -0
- package/dist/nosql/nedb/index.js +164 -0
- package/dist/nosql/neo4j/index.js +450 -0
- package/dist/nosql/neptune/index.js +470 -0
- package/dist/nosql/nsq/index.js +200 -0
- package/dist/nosql/opensearch/index.js +186 -0
- package/dist/nosql/orientdb/index.js +175 -0
- package/dist/nosql/papertrail/index.js +200 -0
- package/dist/nosql/pinecone/index.js +0 -0
- package/dist/nosql/pinot/index.js +133 -0
- package/dist/nosql/pouchdb/index.js +172 -0
- package/dist/nosql/prometheus/index.js +174 -0
- package/dist/nosql/provendb/index.js +147 -0
- package/dist/nosql/pubsub/index.js +187 -0
- package/dist/nosql/pulsar/index.js +232 -0
- package/dist/nosql/qdrant/index.js +295 -0
- package/dist/nosql/qldb/index.js +192 -0
- package/dist/nosql/r2/index.js +281 -0
- package/dist/nosql/rabbitmq/index.js +237 -0
- package/dist/nosql/ravendb/index.js +175 -0
- package/dist/nosql/redis/index.js +607 -0
- package/dist/nosql/redpanda/index.js +237 -0
- package/dist/nosql/resque/index.js +203 -0
- package/dist/nosql/rethinkdb/index.js +232 -0
- package/dist/nosql/rocksdb/index.js +152 -0
- package/dist/nosql/rockset/index.js +126 -0
- package/dist/nosql/s3/index.js +298 -0
- package/dist/nosql/scylladb/index.js +178 -0
- package/dist/nosql/signoz/index.js +227 -0
- package/dist/nosql/sns/index.js +201 -0
- package/dist/nosql/solr/index.js +231 -0
- package/dist/nosql/splunk/index.js +227 -0
- package/dist/nosql/sqs/index.js +246 -0
- package/dist/nosql/stardog/index.js +169 -0
- package/dist/nosql/starrocks/index.js +170 -0
- package/dist/nosql/store.js +2 -0
- package/dist/nosql/sumologic/index.js +213 -0
- package/dist/nosql/surrealdb/index.js +179 -0
- package/dist/nosql/terminusdb/index.js +151 -0
- package/dist/nosql/tigergraph/index.js +469 -0
- package/dist/nosql/typesense/index.js +148 -0
- package/dist/nosql/unqlite/index.js +157 -0
- package/dist/nosql/upstash/index.js +167 -0
- package/dist/nosql/vercel-kv/index.js +166 -0
- package/dist/nosql/victoriametrics/index.js +213 -0
- package/dist/nosql/virtuoso/index.js +169 -0
- package/dist/nosql/weaviate/index.js +276 -0
- package/dist/operators/index.js +108 -0
- package/dist/operators.js +2690 -0
- package/dist/prisma-migrate/index.js +38 -0
- package/dist/prisma-migrate/migration-generator.js +125 -0
- package/dist/prisma-migrate/model-generator.js +172 -0
- package/dist/prisma-migrate/relations.js +100 -0
- package/dist/prisma-migrate/schema-parser.js +167 -0
- package/dist/prisma-migrate/type-mapper.js +40 -0
- package/dist/prorm.js +6832 -0
- package/dist/query-builders/cte-builder.js +80 -0
- package/dist/query-builders/functions/aggregate.js +390 -0
- package/dist/query-builders/functions/conditional.js +503 -0
- package/dist/query-builders/functions/datetime.js +695 -0
- package/dist/query-builders/functions/fulltext.js +439 -0
- package/dist/query-builders/functions/index.js +93 -0
- package/dist/query-builders/functions/json.js +427 -0
- package/dist/query-builders/functions/math.js +399 -0
- package/dist/query-builders/functions/string.js +518 -0
- package/dist/query-builders/functions/window.js +328 -0
- package/dist/query-builders/include-builder.js +323 -0
- package/dist/query-builders/index-expression-builder.js +242 -0
- package/dist/query-builders/index.js +161 -0
- package/dist/query-builders/insert-builder.js +164 -0
- package/dist/query-builders/model-helpers.js +38 -0
- package/dist/query-builders/order-limit-builder.js +239 -0
- package/dist/query-builders/sql-compiler.js +2040 -0
- package/dist/query-builders/subquery-builder.js +152 -0
- package/dist/query-builders/update-builder.js +182 -0
- package/dist/query-builders/view-builder.js +218 -0
- package/dist/query-builders/where-builder.js +1532 -0
- package/dist/query-interface.js +622 -0
- package/dist/query-optimizers/batch-optimizer.js +426 -0
- package/dist/query-optimizers/explain-plans.js +415 -0
- package/dist/query-optimizers/index.js +51 -0
- package/dist/query-optimizers/prepared-statement-cache.js +423 -0
- package/dist/query-optimizers/query-hints.js +438 -0
- package/dist/query-optimizers/query-optimizer.js +278 -0
- package/dist/query-optimizers/slow-query-logger.js +271 -0
- package/dist/replica-manager.js +507 -0
- package/dist/schema/index.js +15 -0
- package/dist/schema/migration-generator.js +374 -0
- package/dist/schema/schema-differ.js +549 -0
- package/dist/schema/types.js +6 -0
- package/dist/sql-constants.js +300 -0
- package/dist/sqlite-advanced.js +1047 -0
- package/dist/streams/index.js +12 -0
- package/dist/streams/transforms.js +178 -0
- package/dist/transaction.js +456 -0
- package/dist/types/index.js +180 -0
- package/dist/user-management.js +127 -0
- package/dist/utils/date.js +300 -0
- package/dist/utils/index.js +1079 -0
- package/dist/utils/string.js +161 -0
- package/dist/validators/index.js +19 -0
- package/dist/validators/validator.js +911 -0
- package/package.json +190 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* CLI Tools - Command-line interface for the ORM. This is the single,
|
|
5
|
+
* canonical CLI implementation exposed via the published `orm-cli` bin.
|
|
6
|
+
* Provides commands: init, migration:create, migration:run, migration:revert,
|
|
7
|
+
* migration:status, model:create, seeder:create, db:*, diagram:*
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* orm-cli init
|
|
11
|
+
* orm-cli migration:create -- --name <migration-name>
|
|
12
|
+
* orm-cli migration:run
|
|
13
|
+
* orm-cli migration:revert
|
|
14
|
+
* orm-cli migration:status
|
|
15
|
+
* orm-cli model:create --dialect sqlite --database ./mydb.sqlite
|
|
16
|
+
*
|
|
17
|
+
* Model Generator (programmatic):
|
|
18
|
+
* CLI.model('User', { name: 'STRING', email: 'STRING' })
|
|
19
|
+
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
32
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
33
|
+
}) : function(o, v) {
|
|
34
|
+
o["default"] = v;
|
|
35
|
+
});
|
|
36
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
37
|
+
var ownKeys = function(o) {
|
|
38
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
39
|
+
var ar = [];
|
|
40
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
41
|
+
return ar;
|
|
42
|
+
};
|
|
43
|
+
return ownKeys(o);
|
|
44
|
+
};
|
|
45
|
+
return function (mod) {
|
|
46
|
+
if (mod && mod.__esModule) return mod;
|
|
47
|
+
var result = {};
|
|
48
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
49
|
+
__setModuleDefault(result, mod);
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
})();
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.CLI = void 0;
|
|
55
|
+
exports.init = init;
|
|
56
|
+
exports.getProrm = getProrm;
|
|
57
|
+
exports.migrationCreate = migrationCreate;
|
|
58
|
+
exports.migrationRun = migrationRun;
|
|
59
|
+
exports.migrationRevert = migrationRevert;
|
|
60
|
+
exports.migrationStatus = migrationStatus;
|
|
61
|
+
exports.showHelp = showHelp;
|
|
62
|
+
exports.model = model;
|
|
63
|
+
exports.generateModels = generateModels;
|
|
64
|
+
exports.configure = configure;
|
|
65
|
+
exports.getOutputDir = getOutputDir;
|
|
66
|
+
exports.setOutputDir = setOutputDir;
|
|
67
|
+
exports.listModels = listModels;
|
|
68
|
+
exports.removeModel = removeModelFile;
|
|
69
|
+
exports.diagramModel = diagramModel;
|
|
70
|
+
exports.diagramER = diagramER;
|
|
71
|
+
exports.diagramMigration = diagramMigration;
|
|
72
|
+
exports.modelCreateCommand = modelCreateCommand;
|
|
73
|
+
exports.prismaImportCommand = prismaImportCommand;
|
|
74
|
+
exports.seederCreate = seederCreate;
|
|
75
|
+
exports.dbSeed = dbSeed;
|
|
76
|
+
exports.dbSeedUndo = dbSeedUndo;
|
|
77
|
+
exports.dbSeedUndoAll = dbSeedUndoAll;
|
|
78
|
+
exports.dbSeedStatus = dbSeedStatus;
|
|
79
|
+
const fs = __importStar(require("fs"));
|
|
80
|
+
const path = __importStar(require("path"));
|
|
81
|
+
const migrations_1 = require("./migrations");
|
|
82
|
+
const diagrams_1 = require("./diagrams");
|
|
83
|
+
const seeder_1 = require("./migrations/seeder");
|
|
84
|
+
const prorm_1 = require("./prorm");
|
|
85
|
+
/**
|
|
86
|
+
* Connect to a database and get schema information
|
|
87
|
+
*/
|
|
88
|
+
async function getDbSchema(connectionString) {
|
|
89
|
+
// Parse connection string: mysql://user:pass@host:port/db, postgres://..., sqlite:./path
|
|
90
|
+
let dialect = 'sqlite';
|
|
91
|
+
let database = ':memory:';
|
|
92
|
+
let username = '';
|
|
93
|
+
let password = '';
|
|
94
|
+
let host = 'localhost';
|
|
95
|
+
let port = 3306;
|
|
96
|
+
let storage = ':memory:';
|
|
97
|
+
if (connectionString.startsWith('mysql://') || connectionString.startsWith('mariadb://')) {
|
|
98
|
+
const url = new URL(connectionString);
|
|
99
|
+
dialect = connectionString.startsWith('mysql') ? 'mysql' : 'mariadb';
|
|
100
|
+
username = url.username;
|
|
101
|
+
password = url.password;
|
|
102
|
+
host = url.hostname;
|
|
103
|
+
port = parseInt(url.port) || 3306;
|
|
104
|
+
database = url.pathname.slice(1);
|
|
105
|
+
}
|
|
106
|
+
else if (connectionString.startsWith('postgres://') ||
|
|
107
|
+
connectionString.startsWith('postgresql://')) {
|
|
108
|
+
const url = new URL(connectionString);
|
|
109
|
+
dialect = 'postgres';
|
|
110
|
+
username = url.username;
|
|
111
|
+
password = url.password;
|
|
112
|
+
host = url.hostname;
|
|
113
|
+
port = parseInt(url.port) || 5432;
|
|
114
|
+
database = url.pathname.slice(1);
|
|
115
|
+
}
|
|
116
|
+
else if (connectionString.startsWith('sqlite:')) {
|
|
117
|
+
dialect = 'sqlite';
|
|
118
|
+
storage = connectionString.replace('sqlite:', '');
|
|
119
|
+
if (!storage)
|
|
120
|
+
storage = ':memory:';
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Assume it's a file path for sqlite
|
|
124
|
+
dialect = 'sqlite';
|
|
125
|
+
storage = connectionString;
|
|
126
|
+
}
|
|
127
|
+
const seq = new prorm_1.Prorm({
|
|
128
|
+
dialect,
|
|
129
|
+
database,
|
|
130
|
+
username,
|
|
131
|
+
password,
|
|
132
|
+
host,
|
|
133
|
+
port,
|
|
134
|
+
storage: dialect === 'sqlite' ? storage : undefined,
|
|
135
|
+
logging: false,
|
|
136
|
+
});
|
|
137
|
+
await seq.authenticate();
|
|
138
|
+
log(`Connected to ${dialect} database`, 'info');
|
|
139
|
+
// Query table information based on dialect
|
|
140
|
+
let tables = [];
|
|
141
|
+
if (dialect === 'mysql' || dialect === 'mariadb') {
|
|
142
|
+
const [rows] = (await seq.query(`SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?`, {
|
|
143
|
+
replacements: [database],
|
|
144
|
+
}));
|
|
145
|
+
for (const row of rows) {
|
|
146
|
+
const tableName = row.TABLE_NAME;
|
|
147
|
+
const [cols] = (await seq.query(`SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`, {
|
|
148
|
+
replacements: [database, tableName],
|
|
149
|
+
}));
|
|
150
|
+
tables.push({
|
|
151
|
+
name: tableName,
|
|
152
|
+
columns: cols.map((c) => ({
|
|
153
|
+
name: c.COLUMN_NAME,
|
|
154
|
+
type: c.COLUMN_TYPE,
|
|
155
|
+
allowNull: c.IS_NULLABLE === 'YES',
|
|
156
|
+
defaultValue: c.COLUMN_DEFAULT,
|
|
157
|
+
primaryKey: c.COLUMN_KEY === 'PRI',
|
|
158
|
+
unique: c.COLUMN_KEY === 'UNI',
|
|
159
|
+
autoIncrement: c.EXTRA === 'auto_increment',
|
|
160
|
+
})),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (dialect === 'postgres') {
|
|
165
|
+
const [tablesRes] = (await seq.query(`SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'`));
|
|
166
|
+
for (const row of tablesRes) {
|
|
167
|
+
const tableName = row.table_name;
|
|
168
|
+
const [cols] = (await seq.query(`SELECT column_name, data_type, is_nullable, column_default, character_maximum_length FROM information_schema.columns WHERE table_name = $1 AND table_schema = 'public'`, {
|
|
169
|
+
replacements: [tableName],
|
|
170
|
+
}));
|
|
171
|
+
tables.push({
|
|
172
|
+
name: tableName,
|
|
173
|
+
columns: cols.map((c) => ({
|
|
174
|
+
name: c.column_name,
|
|
175
|
+
type: c.data_type + (c.character_maximum_length ? `(${c.character_maximum_length})` : ''),
|
|
176
|
+
allowNull: c.is_nullable === 'YES',
|
|
177
|
+
defaultValue: c.column_default,
|
|
178
|
+
primaryKey: false,
|
|
179
|
+
unique: false,
|
|
180
|
+
autoIncrement: false,
|
|
181
|
+
})),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else if (dialect === 'sqlite') {
|
|
186
|
+
const [tablesRes] = (await seq.query(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`));
|
|
187
|
+
for (const row of tablesRes) {
|
|
188
|
+
const tableName = row.name;
|
|
189
|
+
const [cols] = (await seq.query(`PRAGMA table_info("${tableName}")`));
|
|
190
|
+
tables.push({
|
|
191
|
+
name: tableName,
|
|
192
|
+
columns: cols.map((c) => ({
|
|
193
|
+
name: c.name,
|
|
194
|
+
type: c.type,
|
|
195
|
+
allowNull: c.notnull === 0,
|
|
196
|
+
defaultValue: c.dflt_value,
|
|
197
|
+
primaryKey: c.pk === 1,
|
|
198
|
+
unique: false,
|
|
199
|
+
autoIncrement: c.autoincrement === 1,
|
|
200
|
+
})),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
await seq.close();
|
|
205
|
+
return tables;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Convert database schema to ModelDiagramOptions[]
|
|
209
|
+
*/
|
|
210
|
+
function schemaToModels(tables) {
|
|
211
|
+
return tables.map((table) => ({
|
|
212
|
+
name: table.name.charAt(0).toUpperCase() +
|
|
213
|
+
table.name.slice(1).replace(/_([a-z])/g, (_, c) => c.toUpperCase()),
|
|
214
|
+
tableName: table.name,
|
|
215
|
+
fields: Object.fromEntries(table.columns.map((col) => [
|
|
216
|
+
col.name,
|
|
217
|
+
{
|
|
218
|
+
type: col.type,
|
|
219
|
+
primaryKey: col.primaryKey,
|
|
220
|
+
allowNull: col.allowNull,
|
|
221
|
+
defaultValue: col.defaultValue,
|
|
222
|
+
autoIncrement: col.autoIncrement,
|
|
223
|
+
unique: col.unique,
|
|
224
|
+
},
|
|
225
|
+
])),
|
|
226
|
+
timestamps: true,
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
// ==================== Model Generator ====================
|
|
230
|
+
// Map of shorthand types to DataTypes
|
|
231
|
+
const TYPE_MAP = {
|
|
232
|
+
STRING: 'DataTypes.STRING(255)',
|
|
233
|
+
TEXT: 'DataTypes.TEXT()',
|
|
234
|
+
INTEGER: 'DataTypes.INTEGER()',
|
|
235
|
+
BIGINT: 'DataTypes.BIGINT()',
|
|
236
|
+
FLOAT: 'DataTypes.FLOAT()',
|
|
237
|
+
DOUBLE: 'DataTypes.DOUBLE()',
|
|
238
|
+
DECIMAL: 'DataTypes.DECIMAL(10, 2)',
|
|
239
|
+
BOOLEAN: 'DataTypes.BOOLEAN()',
|
|
240
|
+
DATE: 'DataTypes.DATE()',
|
|
241
|
+
DATEONLY: 'DataTypes.DATEONLY()',
|
|
242
|
+
TIME: 'DataTypes.TIME()',
|
|
243
|
+
UUID: 'DataTypes.UUID()',
|
|
244
|
+
JSON: 'DataTypes.JSON()',
|
|
245
|
+
JSONB: 'DataTypes.JSONB()',
|
|
246
|
+
BLOB: 'DataTypes.BLOB()',
|
|
247
|
+
ENUM: "DataTypes.ENUM('value1', 'value2')",
|
|
248
|
+
ARRAY: 'DataTypes.ARRAY(DataTypes.STRING)',
|
|
249
|
+
};
|
|
250
|
+
let modelConfig = {
|
|
251
|
+
outputDir: process.cwd(),
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* Configure model generator options
|
|
255
|
+
*/
|
|
256
|
+
function configure(options) {
|
|
257
|
+
modelConfig = { ...modelConfig, ...options };
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get the output directory for generated models
|
|
261
|
+
*/
|
|
262
|
+
function getOutputDir() {
|
|
263
|
+
return modelConfig.outputDir;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Set the output directory for generated models
|
|
267
|
+
*/
|
|
268
|
+
function setOutputDir(dir) {
|
|
269
|
+
modelConfig.outputDir = dir;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Parse a type string into DataTypes expression
|
|
273
|
+
*/
|
|
274
|
+
function parseModelType(typeStr) {
|
|
275
|
+
// Check for type with length (e.g., STRING(100))
|
|
276
|
+
const match = typeStr.match(/^(\w+)\((\d+)\)$/);
|
|
277
|
+
if (match) {
|
|
278
|
+
const [, type, length] = match;
|
|
279
|
+
if (TYPE_MAP[type]) {
|
|
280
|
+
return TYPE_MAP[type].replace(/\(\d+\)/, `(${length})`);
|
|
281
|
+
}
|
|
282
|
+
return `DataTypes.${type}(${length})`;
|
|
283
|
+
}
|
|
284
|
+
// Check for simple type
|
|
285
|
+
if (TYPE_MAP[typeStr]) {
|
|
286
|
+
return TYPE_MAP[typeStr];
|
|
287
|
+
}
|
|
288
|
+
// Default to STRING
|
|
289
|
+
return 'DataTypes.STRING(255)';
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Generate field definitions from simple field map
|
|
293
|
+
*/
|
|
294
|
+
function generateFieldDefs(fields, primaryKey, autoIncrement, allowNull, unique) {
|
|
295
|
+
const lines = [];
|
|
296
|
+
const fieldNames = Object.keys(fields);
|
|
297
|
+
// Add user-defined fields (skip id - it's added if primaryKey is true)
|
|
298
|
+
fieldNames.forEach((fieldName) => {
|
|
299
|
+
const typeStr = fields[fieldName];
|
|
300
|
+
const parsedType = parseModelType(typeStr);
|
|
301
|
+
const fieldDef = [];
|
|
302
|
+
fieldDef.push(` ${fieldName}: {`);
|
|
303
|
+
fieldDef.push(` type: ${parsedType},`);
|
|
304
|
+
// Only add allowNull if it's true (default is false)
|
|
305
|
+
if (allowNull) {
|
|
306
|
+
fieldDef.push(` allowNull: ${allowNull},`);
|
|
307
|
+
}
|
|
308
|
+
if (unique) {
|
|
309
|
+
fieldDef.push(` unique: true,`);
|
|
310
|
+
}
|
|
311
|
+
// Remove trailing comma and close
|
|
312
|
+
const lastIdx = fieldDef.length - 1;
|
|
313
|
+
fieldDef[lastIdx] = fieldDef[lastIdx].replace(/,$/, '');
|
|
314
|
+
fieldDef.push(' },');
|
|
315
|
+
lines.push(fieldDef.join('\n'));
|
|
316
|
+
});
|
|
317
|
+
// Prepend id field if primaryKey is enabled
|
|
318
|
+
if (primaryKey) {
|
|
319
|
+
const idField = ` id: {
|
|
320
|
+
type: DataTypes.INTEGER(),
|
|
321
|
+
primaryKey: true,
|
|
322
|
+
autoIncrement: ${autoIncrement},
|
|
323
|
+
},`;
|
|
324
|
+
lines.unshift(idField);
|
|
325
|
+
}
|
|
326
|
+
return lines.join('\n');
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Generate the complete model file content
|
|
330
|
+
*/
|
|
331
|
+
function generateModelFile(modelName, tableName, fieldDefs, timestamps, paranoid) {
|
|
332
|
+
const options = [];
|
|
333
|
+
options.push(`tableName: '${tableName}',`);
|
|
334
|
+
options.push(`timestamps: ${timestamps},`);
|
|
335
|
+
if (paranoid) {
|
|
336
|
+
options.push(`paranoid: true,`);
|
|
337
|
+
}
|
|
338
|
+
return `import { DataTypes, Model } from 'ts-prorm-orm';
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* ${modelName} model
|
|
342
|
+
* Generated by CLI model generator
|
|
343
|
+
*/
|
|
344
|
+
export interface ${modelName}Attributes {
|
|
345
|
+
id?: number;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export interface ${modelName}Instance extends Model, ${modelName}Attributes {
|
|
349
|
+
// Instance methods can be added here
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const ${modelName} = (prorm: any) => {
|
|
353
|
+
const ${modelName}Model = prorm.define('${modelName}', {
|
|
354
|
+
${fieldDefs}
|
|
355
|
+
}, {
|
|
356
|
+
${options.map((opt) => ` ${opt}`).join('\n')}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Define associations
|
|
360
|
+
// ${modelName}Model.associate = (models: any) => {
|
|
361
|
+
// ${modelName}Model.hasMany(models.Post, { foreignKey: 'userId', as: 'posts' });
|
|
362
|
+
// ${modelName}Model.belongsTo(models.Other, { foreignKey: 'otherId', as: 'other' });
|
|
363
|
+
// };
|
|
364
|
+
|
|
365
|
+
return ${modelName}Model;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
export { ${modelName} };
|
|
369
|
+
export default ${modelName};
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Generate a model file from model name and fields
|
|
374
|
+
* @param modelName - The name of the model (e.g., 'User')
|
|
375
|
+
* @param fields - Object defining fields (e.g., { name: 'STRING', email: 'STRING' })
|
|
376
|
+
* @param options - Optional model configuration
|
|
377
|
+
*/
|
|
378
|
+
function model(modelName, fields, options = {}) {
|
|
379
|
+
const { tableName = modelName.toLowerCase() + 's', timestamps = true, paranoid = false, primaryKey = true, autoIncrement = true, allowNull = false, unique = false, } = options;
|
|
380
|
+
// Build field definitions
|
|
381
|
+
const fieldDefs = generateFieldDefs(fields, primaryKey, autoIncrement, allowNull, unique);
|
|
382
|
+
// Generate the model file content
|
|
383
|
+
const modelContent = generateModelFile(modelName, tableName, fieldDefs, timestamps, paranoid);
|
|
384
|
+
// Determine output path
|
|
385
|
+
const outputPath = path.join(modelConfig.outputDir, `${modelName}.ts`);
|
|
386
|
+
// Write the file
|
|
387
|
+
fs.writeFileSync(outputPath, modelContent, 'utf-8');
|
|
388
|
+
console.log(`Model "${modelName}" created at: ${outputPath}`);
|
|
389
|
+
return outputPath;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Generate multiple models at once
|
|
393
|
+
*/
|
|
394
|
+
function generateModels(definitions) {
|
|
395
|
+
const generatedFiles = [];
|
|
396
|
+
for (const [modelName, fields] of Object.entries(definitions)) {
|
|
397
|
+
const filePath = model(modelName, fields);
|
|
398
|
+
generatedFiles.push(filePath);
|
|
399
|
+
}
|
|
400
|
+
return generatedFiles;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Get list of generated model files in the output directory
|
|
404
|
+
*/
|
|
405
|
+
function listModels() {
|
|
406
|
+
const outputDir = modelConfig.outputDir;
|
|
407
|
+
if (!fs.existsSync(outputDir)) {
|
|
408
|
+
return [];
|
|
409
|
+
}
|
|
410
|
+
return fs
|
|
411
|
+
.readdirSync(outputDir)
|
|
412
|
+
.filter((file) => file.endsWith('.ts') && file !== 'index.ts')
|
|
413
|
+
.sort();
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Remove a generated model file
|
|
417
|
+
*/
|
|
418
|
+
function removeModelFile(modelName) {
|
|
419
|
+
const filePath = path.join(modelConfig.outputDir, `${modelName}.ts`);
|
|
420
|
+
if (fs.existsSync(filePath)) {
|
|
421
|
+
fs.unlinkSync(filePath);
|
|
422
|
+
console.log(`Model "${modelName}" removed from: ${filePath}`);
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
console.log(`Model "${modelName}" not found at: ${filePath}`);
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* CLI object for chained usage: CLI.model('User', { ... })
|
|
430
|
+
*/
|
|
431
|
+
const CLI = {
|
|
432
|
+
model,
|
|
433
|
+
generateModels,
|
|
434
|
+
configure,
|
|
435
|
+
getOutputDir,
|
|
436
|
+
setOutputDir,
|
|
437
|
+
listModels,
|
|
438
|
+
removeModel: removeModelFile,
|
|
439
|
+
};
|
|
440
|
+
exports.CLI = CLI;
|
|
441
|
+
// Default configuration
|
|
442
|
+
const defaultConfig = {
|
|
443
|
+
migrationsPath: path.join(process.cwd(), 'migrations'),
|
|
444
|
+
seedersPath: path.join(process.cwd(), 'seeders'),
|
|
445
|
+
verbose: true,
|
|
446
|
+
};
|
|
447
|
+
/**
|
|
448
|
+
* Get project root directory
|
|
449
|
+
*/
|
|
450
|
+
function getProjectRoot() {
|
|
451
|
+
return process.cwd();
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Create directory if it doesn't exist
|
|
455
|
+
*/
|
|
456
|
+
function createDirectory(dirPath) {
|
|
457
|
+
if (!fs.existsSync(dirPath)) {
|
|
458
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
459
|
+
console.log(`Created: ${dirPath}`);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
console.log(`Exists: ${dirPath}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Create config file
|
|
467
|
+
*/
|
|
468
|
+
function createConfigFile(configDir) {
|
|
469
|
+
const configContent = `import { Prorm } from 'ts-prorm-orm';
|
|
470
|
+
|
|
471
|
+
// Zero-config default: SQLite. Set DB_DIALECT (and related DB_* env vars)
|
|
472
|
+
// to switch to mysql, postgres, or mariadb.
|
|
473
|
+
const prorm = new Prorm({
|
|
474
|
+
dialect: (process.env.DB_DIALECT as any) || 'sqlite',
|
|
475
|
+
storage: process.env.DB_STORAGE || './database.sqlite',
|
|
476
|
+
host: process.env.DB_HOST || 'localhost',
|
|
477
|
+
port: parseInt(process.env.DB_PORT || '3306'),
|
|
478
|
+
database: process.env.DB_NAME || 'myapp',
|
|
479
|
+
username: process.env.DB_USER || 'root',
|
|
480
|
+
password: process.env.DB_PASSWORD || '',
|
|
481
|
+
logging: console.log,
|
|
482
|
+
define: {
|
|
483
|
+
timestamps: true,
|
|
484
|
+
underscored: false,
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
export default prorm;
|
|
489
|
+
`;
|
|
490
|
+
const configPath = path.join(configDir, 'config.ts');
|
|
491
|
+
if (!fs.existsSync(configPath)) {
|
|
492
|
+
fs.writeFileSync(configPath, configContent);
|
|
493
|
+
console.log(`Created: ${configPath}`);
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
console.log(`Exists: ${configPath}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Create models index file
|
|
501
|
+
*/
|
|
502
|
+
function createModelsIndexFile(modelsDir) {
|
|
503
|
+
const indexContent = `import { DataTypes, Model, Prorm } from 'ts-prorm-orm';
|
|
504
|
+
import prorm from '../config/config';
|
|
505
|
+
|
|
506
|
+
export { prorm, DataTypes, Model };
|
|
507
|
+
|
|
508
|
+
// Import models and define associations here
|
|
509
|
+
// Example:
|
|
510
|
+
// import { User } from './user';
|
|
511
|
+
// User.hasMany(Post);
|
|
512
|
+
// Post.belongsTo(User);
|
|
513
|
+
|
|
514
|
+
export const models = {
|
|
515
|
+
// Add your models here
|
|
516
|
+
};
|
|
517
|
+
`;
|
|
518
|
+
const indexPath = path.join(modelsDir, 'index.ts');
|
|
519
|
+
if (!fs.existsSync(indexPath)) {
|
|
520
|
+
fs.writeFileSync(indexPath, indexContent);
|
|
521
|
+
console.log(`Created: ${indexPath}`);
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
console.log(`Exists: ${indexPath}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Create migrations index file
|
|
529
|
+
*/
|
|
530
|
+
function createMigrationsIndexFile(migrationsDir) {
|
|
531
|
+
const indexContent = `// Migration files go here
|
|
532
|
+
// Each migration should export 'up' and 'down' functions
|
|
533
|
+
//
|
|
534
|
+
// Example migration:
|
|
535
|
+
// export async function up(queryInterface: any) {
|
|
536
|
+
// await queryInterface.createTable('users', {
|
|
537
|
+
// id: {
|
|
538
|
+
// type: DataTypes.INTEGER,
|
|
539
|
+
// autoIncrement: true,
|
|
540
|
+
// primaryKey: true,
|
|
541
|
+
// },
|
|
542
|
+
// name: {
|
|
543
|
+
// type: DataTypes.STRING,
|
|
544
|
+
// allowNull: false,
|
|
545
|
+
// },
|
|
546
|
+
// createdAt: {
|
|
547
|
+
// type: DataTypes.DATE,
|
|
548
|
+
// allowNull: false,
|
|
549
|
+
// },
|
|
550
|
+
// updatedAt: {
|
|
551
|
+
// type: DataTypes.DATE,
|
|
552
|
+
// allowNull: false,
|
|
553
|
+
// },
|
|
554
|
+
// });
|
|
555
|
+
// }
|
|
556
|
+
//
|
|
557
|
+
// export async function down(queryInterface: any) {
|
|
558
|
+
// await queryInterface.dropTable('users');
|
|
559
|
+
// }
|
|
560
|
+
`;
|
|
561
|
+
const indexPath = path.join(migrationsDir, 'index.ts');
|
|
562
|
+
if (!fs.existsSync(indexPath)) {
|
|
563
|
+
fs.writeFileSync(indexPath, indexContent);
|
|
564
|
+
console.log(`Created: ${indexPath}`);
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
console.log(`Exists: ${indexPath}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Initialize a new Prorm project
|
|
572
|
+
*/
|
|
573
|
+
function init(options = {}) {
|
|
574
|
+
const projectRoot = options.directory || getProjectRoot();
|
|
575
|
+
console.log(`Initializing Prorm project in: ${projectRoot}`);
|
|
576
|
+
console.log('----------------------------------------');
|
|
577
|
+
const configDir = path.join(projectRoot, 'config');
|
|
578
|
+
const modelsDir = path.join(projectRoot, 'models');
|
|
579
|
+
const migrationsDir = path.join(projectRoot, 'migrations');
|
|
580
|
+
createDirectory(configDir);
|
|
581
|
+
createDirectory(modelsDir);
|
|
582
|
+
createDirectory(migrationsDir);
|
|
583
|
+
createConfigFile(configDir);
|
|
584
|
+
createModelsIndexFile(modelsDir);
|
|
585
|
+
createMigrationsIndexFile(migrationsDir);
|
|
586
|
+
console.log('----------------------------------------');
|
|
587
|
+
console.log('Prorm project initialized successfully!');
|
|
588
|
+
console.log('');
|
|
589
|
+
console.log('Next steps:');
|
|
590
|
+
console.log(' 1. Update config/config.ts with your database credentials');
|
|
591
|
+
console.log(' 2. Add your models in the models/ directory');
|
|
592
|
+
console.log(' 3. Run migrations using your migration tool');
|
|
593
|
+
}
|
|
594
|
+
// ============================================
|
|
595
|
+
// Migration CLI Commands
|
|
596
|
+
// ============================================
|
|
597
|
+
/**
|
|
598
|
+
* Print colored console output
|
|
599
|
+
*/
|
|
600
|
+
function log(message, type = 'info') {
|
|
601
|
+
const colors = {
|
|
602
|
+
info: '\x1b[36m', // Cyan
|
|
603
|
+
success: '\x1b[32m', // Green
|
|
604
|
+
error: '\x1b[31m', // Red
|
|
605
|
+
warning: '\x1b[33m', // Yellow
|
|
606
|
+
};
|
|
607
|
+
const reset = '\x1b[0m';
|
|
608
|
+
console.log(`${colors[type]}${message}${reset}`);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Command: db:create
|
|
612
|
+
* Creates the target database if it does not exist
|
|
613
|
+
*/
|
|
614
|
+
async function dbCreate() {
|
|
615
|
+
const dbDialect = process.env.DB_DIALECT || process.env.DIALECT || 'sqlite';
|
|
616
|
+
const dbName = process.env.DB_NAME || process.env.DB_DATABASE || 'database.db';
|
|
617
|
+
const dbUser = process.env.DB_USER || process.env.DB_USERNAME || '';
|
|
618
|
+
const dbPass = process.env.DB_PASSWORD || process.env.DB_PASS || '';
|
|
619
|
+
const dbHost = process.env.DB_HOST || 'localhost';
|
|
620
|
+
const dbPort = parseInt(process.env.DB_PORT || '5432', 10);
|
|
621
|
+
log(`Creating database: ${dbName}...`, 'info');
|
|
622
|
+
try {
|
|
623
|
+
switch (dbDialect) {
|
|
624
|
+
case 'sqlite':
|
|
625
|
+
// SQLite - just verify we can create the file
|
|
626
|
+
const sqlitePath = path.isAbsolute(dbName) ? dbName : path.join(process.cwd(), dbName);
|
|
627
|
+
if (!fs.existsSync(sqlitePath)) {
|
|
628
|
+
fs.writeFileSync(sqlitePath, ''); // Create empty file
|
|
629
|
+
log(`Created SQLite database: ${sqlitePath}`, 'success');
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
log(`Database already exists: ${sqlitePath}`, 'warning');
|
|
633
|
+
}
|
|
634
|
+
break;
|
|
635
|
+
case 'postgres':
|
|
636
|
+
case 'postgresql': {
|
|
637
|
+
// PostgreSQL - connect to postgres system db and create
|
|
638
|
+
const adminSeq = new prorm_1.Prorm({
|
|
639
|
+
dialect: 'postgres',
|
|
640
|
+
database: 'postgres',
|
|
641
|
+
username: dbUser,
|
|
642
|
+
password: dbPass,
|
|
643
|
+
host: dbHost,
|
|
644
|
+
port: dbPort,
|
|
645
|
+
logging: false,
|
|
646
|
+
});
|
|
647
|
+
await adminSeq.query(`CREATE DATABASE ${dbName}`);
|
|
648
|
+
await adminSeq.close();
|
|
649
|
+
log(`Created PostgreSQL database: ${dbName}`, 'success');
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
case 'mysql': {
|
|
653
|
+
// MySQL - connect to mysql system db and create
|
|
654
|
+
const adminSeq = new prorm_1.Prorm({
|
|
655
|
+
dialect: 'mysql',
|
|
656
|
+
database: 'mysql',
|
|
657
|
+
username: dbUser,
|
|
658
|
+
password: dbPass,
|
|
659
|
+
host: dbHost,
|
|
660
|
+
port: dbPort,
|
|
661
|
+
logging: false,
|
|
662
|
+
});
|
|
663
|
+
await adminSeq.query(`CREATE DATABASE \`${dbName}\``);
|
|
664
|
+
await adminSeq.close();
|
|
665
|
+
log(`Created MySQL database: ${dbName}`, 'success');
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
case 'mariadb': {
|
|
669
|
+
// MariaDB - similar to MySQL
|
|
670
|
+
const adminSeq = new prorm_1.Prorm({
|
|
671
|
+
dialect: 'mariadb',
|
|
672
|
+
database: 'mysql',
|
|
673
|
+
username: dbUser,
|
|
674
|
+
password: dbPass,
|
|
675
|
+
host: dbHost,
|
|
676
|
+
port: dbPort,
|
|
677
|
+
logging: false,
|
|
678
|
+
});
|
|
679
|
+
await adminSeq.query(`CREATE DATABASE \`${dbName}\``);
|
|
680
|
+
await adminSeq.close();
|
|
681
|
+
log(`Created MariaDB database: ${dbName}`, 'success');
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
default:
|
|
685
|
+
throw new Error(`Unsupported dialect: ${dbDialect}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
if (error.message?.includes('already exists')) {
|
|
690
|
+
log(`Database already exists: ${dbName}`, 'warning');
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
log(`Error creating database: ${error.message}`, 'error');
|
|
694
|
+
throw error;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Command: db:drop
|
|
700
|
+
* Drops the target database
|
|
701
|
+
*/
|
|
702
|
+
async function dbDrop() {
|
|
703
|
+
const dbDialect = process.env.DB_DIALECT || process.env.DIALECT || 'sqlite';
|
|
704
|
+
const dbName = process.env.DB_NAME || process.env.DB_DATABASE || 'database.db';
|
|
705
|
+
const dbUser = process.env.DB_USER || process.env.DB_USERNAME || '';
|
|
706
|
+
const dbPass = process.env.DB_PASSWORD || process.env.DB_PASS || '';
|
|
707
|
+
const dbHost = process.env.DB_HOST || 'localhost';
|
|
708
|
+
const dbPort = parseInt(process.env.DB_PORT || '5432', 10);
|
|
709
|
+
log(`Dropping database: ${dbName}...`, 'info');
|
|
710
|
+
try {
|
|
711
|
+
switch (dbDialect) {
|
|
712
|
+
case 'sqlite':
|
|
713
|
+
// SQLite - just delete the file
|
|
714
|
+
const sqlitePath = path.isAbsolute(dbName) ? dbName : path.join(process.cwd(), dbName);
|
|
715
|
+
if (fs.existsSync(sqlitePath)) {
|
|
716
|
+
fs.unlinkSync(sqlitePath);
|
|
717
|
+
log(`Dropped SQLite database: ${sqlitePath}`, 'success');
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
log(`Database does not exist: ${sqlitePath}`, 'warning');
|
|
721
|
+
}
|
|
722
|
+
break;
|
|
723
|
+
case 'postgres':
|
|
724
|
+
case 'postgresql': {
|
|
725
|
+
// PostgreSQL - connect to postgres system db and drop
|
|
726
|
+
const adminSeq = new prorm_1.Prorm({
|
|
727
|
+
dialect: 'postgres',
|
|
728
|
+
database: 'postgres',
|
|
729
|
+
username: dbUser,
|
|
730
|
+
password: dbPass,
|
|
731
|
+
host: dbHost,
|
|
732
|
+
port: dbPort,
|
|
733
|
+
logging: false,
|
|
734
|
+
});
|
|
735
|
+
// Terminate all connections to the database first
|
|
736
|
+
await adminSeq
|
|
737
|
+
.query(`
|
|
738
|
+
SELECT pg_terminate_backend(pg_stat_activity.pid)
|
|
739
|
+
FROM pg_stat_activity
|
|
740
|
+
WHERE pg_stat_activity.datname = '${dbName}'
|
|
741
|
+
AND pid <> pg_backend_pid()
|
|
742
|
+
`)
|
|
743
|
+
.catch(() => {
|
|
744
|
+
/* ignore errors */
|
|
745
|
+
});
|
|
746
|
+
await adminSeq.query(`DROP DATABASE IF EXISTS ${dbName}`);
|
|
747
|
+
await adminSeq.close();
|
|
748
|
+
log(`Dropped PostgreSQL database: ${dbName}`, 'success');
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
case 'mysql': {
|
|
752
|
+
// MySQL - connect to mysql system db and drop
|
|
753
|
+
const adminSeq = new prorm_1.Prorm({
|
|
754
|
+
dialect: 'mysql',
|
|
755
|
+
database: 'mysql',
|
|
756
|
+
username: dbUser,
|
|
757
|
+
password: dbPass,
|
|
758
|
+
host: dbHost,
|
|
759
|
+
port: dbPort,
|
|
760
|
+
logging: false,
|
|
761
|
+
});
|
|
762
|
+
await adminSeq.query(`DROP DATABASE IF EXISTS \`${dbName}\``);
|
|
763
|
+
await adminSeq.close();
|
|
764
|
+
log(`Dropped MySQL database: ${dbName}`, 'success');
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
case 'mariadb': {
|
|
768
|
+
// MariaDB - similar to MySQL
|
|
769
|
+
const adminSeq = new prorm_1.Prorm({
|
|
770
|
+
dialect: 'mariadb',
|
|
771
|
+
database: 'mysql',
|
|
772
|
+
username: dbUser,
|
|
773
|
+
password: dbPass,
|
|
774
|
+
host: dbHost,
|
|
775
|
+
port: dbPort,
|
|
776
|
+
logging: false,
|
|
777
|
+
});
|
|
778
|
+
await adminSeq.query(`DROP DATABASE IF EXISTS \`${dbName}\``);
|
|
779
|
+
await adminSeq.close();
|
|
780
|
+
log(`Dropped MariaDB database: ${dbName}`, 'success');
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
default:
|
|
784
|
+
throw new Error(`Unsupported dialect: ${dbDialect}`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch (error) {
|
|
788
|
+
log(`Error dropping database: ${error.message}`, 'error');
|
|
789
|
+
throw error;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Load Prorm instance from environment or config
|
|
794
|
+
*/
|
|
795
|
+
async function getProrm() {
|
|
796
|
+
// Try to load from config file or environment
|
|
797
|
+
const configPaths = [
|
|
798
|
+
path.join(process.cwd(), 'config', 'database.js'),
|
|
799
|
+
path.join(process.cwd(), 'config', 'database.ts'),
|
|
800
|
+
path.join(process.cwd(), 'database.js'),
|
|
801
|
+
path.join(process.cwd(), 'database.ts'),
|
|
802
|
+
path.join(process.cwd(), '.env'),
|
|
803
|
+
];
|
|
804
|
+
let prorm = null;
|
|
805
|
+
// Check for environment variables first
|
|
806
|
+
const databaseUrl = process.env.DATABASE_URL || process.env.DATABASE;
|
|
807
|
+
const dbDialect = process.env.DB_DIALECT || process.env.DIALECT || 'sqlite';
|
|
808
|
+
const dbName = process.env.DB_NAME || process.env.DB_DATABASE || 'database.db';
|
|
809
|
+
const dbUser = process.env.DB_USER || process.env.DB_USERNAME || '';
|
|
810
|
+
const dbPass = process.env.DB_PASSWORD || process.env.DB_PASS || '';
|
|
811
|
+
const dbHost = process.env.DB_HOST || 'localhost';
|
|
812
|
+
const dbPort = parseInt(process.env.DB_PORT || '5432', 10);
|
|
813
|
+
if (databaseUrl) {
|
|
814
|
+
// Parse database URL (e.g., postgres://user:pass@localhost:5432/db)
|
|
815
|
+
const url = new URL(databaseUrl);
|
|
816
|
+
// Normalize protocol to a supported Dialect (e.g. 'postgresql' -> 'postgres')
|
|
817
|
+
const urlDialect = url.protocol.replace(':', '');
|
|
818
|
+
prorm = new prorm_1.Prorm({
|
|
819
|
+
dialect: (urlDialect === 'postgresql' ? 'postgres' : urlDialect),
|
|
820
|
+
database: url.pathname.replace('/', ''),
|
|
821
|
+
username: url.username,
|
|
822
|
+
password: url.password,
|
|
823
|
+
host: url.hostname,
|
|
824
|
+
port: parseInt(url.port, 10) || 5432,
|
|
825
|
+
logging: false,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
// Create based on dialect
|
|
830
|
+
switch (dbDialect) {
|
|
831
|
+
case 'sqlite':
|
|
832
|
+
prorm = new prorm_1.Prorm({
|
|
833
|
+
dialect: 'sqlite',
|
|
834
|
+
storage: dbName,
|
|
835
|
+
logging: false,
|
|
836
|
+
});
|
|
837
|
+
break;
|
|
838
|
+
case 'postgres':
|
|
839
|
+
case 'postgresql':
|
|
840
|
+
prorm = new prorm_1.Prorm({
|
|
841
|
+
dialect: 'postgres',
|
|
842
|
+
host: dbHost,
|
|
843
|
+
port: dbPort,
|
|
844
|
+
database: dbName,
|
|
845
|
+
username: dbUser,
|
|
846
|
+
password: dbPass,
|
|
847
|
+
logging: false,
|
|
848
|
+
});
|
|
849
|
+
break;
|
|
850
|
+
case 'mysql':
|
|
851
|
+
prorm = new prorm_1.Prorm({
|
|
852
|
+
dialect: 'mysql',
|
|
853
|
+
host: dbHost,
|
|
854
|
+
port: dbPort,
|
|
855
|
+
database: dbName,
|
|
856
|
+
username: dbUser,
|
|
857
|
+
password: dbPass,
|
|
858
|
+
logging: false,
|
|
859
|
+
});
|
|
860
|
+
break;
|
|
861
|
+
case 'mariadb':
|
|
862
|
+
prorm = new prorm_1.Prorm({
|
|
863
|
+
dialect: 'mariadb',
|
|
864
|
+
host: dbHost,
|
|
865
|
+
port: dbPort,
|
|
866
|
+
database: dbName,
|
|
867
|
+
username: dbUser,
|
|
868
|
+
password: dbPass,
|
|
869
|
+
logging: false,
|
|
870
|
+
});
|
|
871
|
+
break;
|
|
872
|
+
default:
|
|
873
|
+
throw new Error(`Unsupported dialect: ${dbDialect}`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
// Establish the connection, then test it. `authenticate()` requires
|
|
877
|
+
// `connect()` to have been called first (it throws "Database connection
|
|
878
|
+
// not established" otherwise), so both steps are needed here.
|
|
879
|
+
try {
|
|
880
|
+
await prorm.connect();
|
|
881
|
+
await prorm.authenticate();
|
|
882
|
+
log('Database connection established', 'success');
|
|
883
|
+
}
|
|
884
|
+
catch (error) {
|
|
885
|
+
log(`Failed to connect to database: ${error}`, 'error');
|
|
886
|
+
throw error;
|
|
887
|
+
}
|
|
888
|
+
return prorm;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Ensure migrations directory exists
|
|
892
|
+
*/
|
|
893
|
+
function ensureMigrationsDir(migrationsPath) {
|
|
894
|
+
if (!fs.existsSync(migrationsPath)) {
|
|
895
|
+
fs.mkdirSync(migrationsPath, { recursive: true });
|
|
896
|
+
log(`Created migrations directory: ${migrationsPath}`, 'success');
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Command: migration:create
|
|
901
|
+
* Creates a new migration file
|
|
902
|
+
*/
|
|
903
|
+
async function migrationCreate(name, config) {
|
|
904
|
+
if (!name) {
|
|
905
|
+
log('Error: Migration name is required. Usage: --name <migration-name>', 'error');
|
|
906
|
+
process.exit(1);
|
|
907
|
+
}
|
|
908
|
+
ensureMigrationsDir(config.migrationsPath);
|
|
909
|
+
const filepath = (0, migrations_1.createMigration)(name, { path: config.migrationsPath });
|
|
910
|
+
log(`Migration created successfully: ${path.basename(filepath)}`, 'success');
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Command: migration:run
|
|
914
|
+
* Runs pending migrations
|
|
915
|
+
*/
|
|
916
|
+
async function migrationRun(options, config) {
|
|
917
|
+
const prorm = await getProrm();
|
|
918
|
+
const migrator = (0, migrations_1.createMigrator)({
|
|
919
|
+
prorm,
|
|
920
|
+
path: config.migrationsPath,
|
|
921
|
+
});
|
|
922
|
+
try {
|
|
923
|
+
// Sync migrations table
|
|
924
|
+
await migrator.syncMigrationsTable();
|
|
925
|
+
const migrationName = options.name;
|
|
926
|
+
if (migrationName) {
|
|
927
|
+
// Run specific migration
|
|
928
|
+
log(`Running migration: ${migrationName}...`, 'info');
|
|
929
|
+
const result = await migrator.up({ migration: migrationName });
|
|
930
|
+
if (result.executed > 0) {
|
|
931
|
+
log(`Migration "${migrationName}" executed successfully`, 'success');
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
log(`Migration "${migrationName}" already applied or not found`, 'warning');
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
else {
|
|
938
|
+
// Run all pending migrations
|
|
939
|
+
const pending = await migrator.pending();
|
|
940
|
+
if (pending.length === 0) {
|
|
941
|
+
log('No pending migrations to run', 'info');
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
log(`Running ${pending.length} pending migration(s)...`, 'info');
|
|
945
|
+
const result = await migrator.up();
|
|
946
|
+
log(`Executed ${result.executed} migration(s)`, 'success');
|
|
947
|
+
if (config.verbose && result.migrations.length > 0) {
|
|
948
|
+
result.migrations.forEach((name) => {
|
|
949
|
+
console.log(` - ${name}`);
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
finally {
|
|
955
|
+
await prorm.close();
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Command: migration:revert
|
|
960
|
+
* Reverts migrations
|
|
961
|
+
*/
|
|
962
|
+
async function migrationRevert(options, config) {
|
|
963
|
+
const prorm = await getProrm();
|
|
964
|
+
const migrator = (0, migrations_1.createMigrator)({
|
|
965
|
+
prorm,
|
|
966
|
+
path: config.migrationsPath,
|
|
967
|
+
});
|
|
968
|
+
try {
|
|
969
|
+
// Sync migrations table
|
|
970
|
+
await migrator.syncMigrationsTable();
|
|
971
|
+
const migrationName = options.name;
|
|
972
|
+
const steps = options.steps ? parseInt(options.steps, 10) : undefined;
|
|
973
|
+
if (migrationName) {
|
|
974
|
+
// Revert specific migration
|
|
975
|
+
log(`Reverting migration: ${migrationName}...`, 'info');
|
|
976
|
+
const result = await migrator.down({ migration: migrationName });
|
|
977
|
+
if (result.reverted > 0) {
|
|
978
|
+
log(`Migration "${migrationName}" reverted successfully`, 'success');
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
log(`Migration "${migrationName}" not found or not applied`, 'warning');
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
else if (steps !== undefined) {
|
|
985
|
+
// Revert N migrations
|
|
986
|
+
log(`Reverting ${steps} migration(s)...`, 'info');
|
|
987
|
+
const result = await migrator.down({ steps });
|
|
988
|
+
log(`Reverted ${result.reverted} migration(s)`, 'success');
|
|
989
|
+
if (config.verbose && result.migrations.length > 0) {
|
|
990
|
+
result.migrations.forEach((name) => {
|
|
991
|
+
console.log(` - ${name}`);
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
996
|
+
// Revert last migration
|
|
997
|
+
const executed = await migrator.executed();
|
|
998
|
+
if (executed.length === 0) {
|
|
999
|
+
log('No migrations to revert', 'info');
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
log('Reverting last migration...', 'info');
|
|
1003
|
+
const result = await migrator.down();
|
|
1004
|
+
log(`Reverted ${result.reverted} migration(s)`, 'success');
|
|
1005
|
+
if (config.verbose && result.migrations.length > 0) {
|
|
1006
|
+
result.migrations.forEach((name) => {
|
|
1007
|
+
console.log(` - ${name}`);
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
finally {
|
|
1013
|
+
await prorm.close();
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Command: migration:status
|
|
1018
|
+
* Shows migration status
|
|
1019
|
+
*/
|
|
1020
|
+
async function migrationStatus(config) {
|
|
1021
|
+
const prorm = await getProrm();
|
|
1022
|
+
const migrator = (0, migrations_1.createMigrator)({
|
|
1023
|
+
prorm,
|
|
1024
|
+
path: config.migrationsPath,
|
|
1025
|
+
});
|
|
1026
|
+
try {
|
|
1027
|
+
// Sync migrations table
|
|
1028
|
+
await migrator.syncMigrationsTable();
|
|
1029
|
+
const statuses = await migrator.status();
|
|
1030
|
+
if (statuses.length === 0) {
|
|
1031
|
+
log('No migrations found', 'warning');
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
console.log('\nMigration Status:');
|
|
1035
|
+
console.log('================');
|
|
1036
|
+
const pending = statuses.filter((s) => s.status === 'pending');
|
|
1037
|
+
const executed = statuses.filter((s) => s.status === 'up');
|
|
1038
|
+
// Show executed migrations first
|
|
1039
|
+
if (executed.length > 0) {
|
|
1040
|
+
console.log('\nExecuted Migrations:');
|
|
1041
|
+
executed.forEach((s) => {
|
|
1042
|
+
const batch = s.batch ? `[Batch ${s.batch}]` : '';
|
|
1043
|
+
console.log(` [UP] ${s.name} ${batch}`);
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
// Show pending migrations
|
|
1047
|
+
if (pending.length > 0) {
|
|
1048
|
+
console.log('\nPending Migrations:');
|
|
1049
|
+
pending.forEach((s) => {
|
|
1050
|
+
console.log(` [DOWN] ${s.name}`);
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
console.log('\n----------------');
|
|
1054
|
+
console.log(`Total: ${statuses.length} (${executed.length} executed, ${pending.length} pending)`);
|
|
1055
|
+
console.log('');
|
|
1056
|
+
}
|
|
1057
|
+
finally {
|
|
1058
|
+
await prorm.close();
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
// ==================== Seeder CLI Functions ====================
|
|
1062
|
+
/**
|
|
1063
|
+
* Ensure seeders directory exists
|
|
1064
|
+
*/
|
|
1065
|
+
function ensureSeedersDir(seedersPath) {
|
|
1066
|
+
if (!fs.existsSync(seedersPath)) {
|
|
1067
|
+
fs.mkdirSync(seedersPath, { recursive: true });
|
|
1068
|
+
log('Created seeders directory: ' + seedersPath, 'success');
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Command: seeder:create
|
|
1073
|
+
* Creates a new seeder file
|
|
1074
|
+
*/
|
|
1075
|
+
async function seederCreate(name, config) {
|
|
1076
|
+
if (!name) {
|
|
1077
|
+
log('Error: Seeder name is required. Usage: --name <seeder-name>', 'error');
|
|
1078
|
+
process.exit(1);
|
|
1079
|
+
}
|
|
1080
|
+
ensureSeedersDir(config.seedersPath);
|
|
1081
|
+
const timestamp = Date.now();
|
|
1082
|
+
const filepath = config.seedersPath + '/' + timestamp + '-' + name + '.js';
|
|
1083
|
+
const tmpl = 'module.exports = { up: async (q,s)=>{}, down: async (q,s)=>{} };';
|
|
1084
|
+
fs.writeFileSync(filepath, tmpl);
|
|
1085
|
+
log('Seeder created: ' + filepath, 'success');
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Command: db:seed
|
|
1089
|
+
* Runs all pending seeders, or a single one when --name is given.
|
|
1090
|
+
* --env <name> restricts to seeders whose `env` field matches (or have none).
|
|
1091
|
+
* --force (only valid together with --name) re-runs a seeder even if it was
|
|
1092
|
+
* already recorded as executed.
|
|
1093
|
+
*/
|
|
1094
|
+
async function dbSeed(options, config) {
|
|
1095
|
+
const prorm = await getProrm();
|
|
1096
|
+
const sr = new seeder_1.SeederRunner(prorm, { seedersPath: config.seedersPath });
|
|
1097
|
+
try {
|
|
1098
|
+
await sr.ensureSeedsTable();
|
|
1099
|
+
const ran = await sr.runSeeders(config.seedersPath, {
|
|
1100
|
+
only: options.name,
|
|
1101
|
+
env: options.env || process.env.NODE_ENV,
|
|
1102
|
+
force: Boolean(options.force),
|
|
1103
|
+
});
|
|
1104
|
+
log('Executed ' + ran.length + ' seeder(s)', 'success');
|
|
1105
|
+
if (config.verbose && ran.length > 0) {
|
|
1106
|
+
ran.forEach(function (name) {
|
|
1107
|
+
console.log(' - ' + name);
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
finally {
|
|
1112
|
+
await prorm.close();
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Command: db:seed:undo
|
|
1117
|
+
* Undoes a single seeder: the one named by --name, or the most recently
|
|
1118
|
+
* executed one if no --name is given. Use db:seed:undo:all to undo every
|
|
1119
|
+
* executed seeder in reverse order.
|
|
1120
|
+
*/
|
|
1121
|
+
async function dbSeedUndo(options, config) {
|
|
1122
|
+
const prorm = await getProrm();
|
|
1123
|
+
const sr = new seeder_1.SeederRunner(prorm, { seedersPath: config.seedersPath });
|
|
1124
|
+
try {
|
|
1125
|
+
await sr.ensureSeedsTable();
|
|
1126
|
+
if (options.name) {
|
|
1127
|
+
await sr.undoSeeder(options.name);
|
|
1128
|
+
log('Seeder undone: ' + options.name, 'success');
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
const executed = await sr.getExecutedSeeds();
|
|
1132
|
+
if (executed.length === 0) {
|
|
1133
|
+
log('No executed seeders to undo', 'info');
|
|
1134
|
+
}
|
|
1135
|
+
else {
|
|
1136
|
+
const last = executed[executed.length - 1];
|
|
1137
|
+
await sr.undoSeeder();
|
|
1138
|
+
log('Seeder undone: ' + last, 'success');
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
finally {
|
|
1143
|
+
await prorm.close();
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Command: db:seed:undo:all
|
|
1148
|
+
* Undoes every executed seeder, in reverse execution order.
|
|
1149
|
+
*/
|
|
1150
|
+
async function dbSeedUndoAll(config) {
|
|
1151
|
+
const prorm = await getProrm();
|
|
1152
|
+
const sr = new seeder_1.SeederRunner(prorm, { seedersPath: config.seedersPath });
|
|
1153
|
+
try {
|
|
1154
|
+
await sr.ensureSeedsTable();
|
|
1155
|
+
await sr.undoAllSeeders();
|
|
1156
|
+
log('All seeders undone', 'success');
|
|
1157
|
+
}
|
|
1158
|
+
finally {
|
|
1159
|
+
await prorm.close();
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Command: db:seed:status
|
|
1164
|
+
* Shows seeder status
|
|
1165
|
+
*/
|
|
1166
|
+
async function dbSeedStatus(config) {
|
|
1167
|
+
const prorm = await getProrm();
|
|
1168
|
+
const sr = new seeder_1.SeederRunner(prorm, { seedersPath: config.seedersPath });
|
|
1169
|
+
try {
|
|
1170
|
+
await sr.ensureSeedsTable();
|
|
1171
|
+
const statuses = await sr.getSeedStatus();
|
|
1172
|
+
console.log('\nSeeder Status:');
|
|
1173
|
+
console.log('=============');
|
|
1174
|
+
const pending = statuses.filter((s) => s.status === 'pending');
|
|
1175
|
+
const executed = statuses.filter((s) => s.status === 'executed');
|
|
1176
|
+
if (executed.length > 0) {
|
|
1177
|
+
console.log('\nExecuted Seeders:');
|
|
1178
|
+
executed.forEach((s) => console.log(' [UP] ' + s.name));
|
|
1179
|
+
}
|
|
1180
|
+
if (pending.length > 0) {
|
|
1181
|
+
console.log('\nPending Seeders:');
|
|
1182
|
+
pending.forEach((s) => console.log(' [DOWN] ' + s.name));
|
|
1183
|
+
}
|
|
1184
|
+
console.log('\n----------------');
|
|
1185
|
+
console.log('Total: ' +
|
|
1186
|
+
statuses.length +
|
|
1187
|
+
' (' +
|
|
1188
|
+
executed.length +
|
|
1189
|
+
' executed, ' +
|
|
1190
|
+
pending.length +
|
|
1191
|
+
' pending)');
|
|
1192
|
+
}
|
|
1193
|
+
finally {
|
|
1194
|
+
await prorm.close();
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
// ─── Diagram CLI handlers ────────────────────────────────────────────────────
|
|
1198
|
+
/**
|
|
1199
|
+
* Parse named CLI flags from an argv array.
|
|
1200
|
+
* --flag value → { flag: 'value' }
|
|
1201
|
+
* --flag → { flag: true }
|
|
1202
|
+
*/
|
|
1203
|
+
function parseFlags(args) {
|
|
1204
|
+
const flags = {};
|
|
1205
|
+
for (let i = 0; i < args.length; i++) {
|
|
1206
|
+
const a = args[i];
|
|
1207
|
+
if (a.startsWith('--')) {
|
|
1208
|
+
const key = a.slice(2);
|
|
1209
|
+
const next = args[i + 1];
|
|
1210
|
+
if (next && !next.startsWith('--')) {
|
|
1211
|
+
flags[key] = next;
|
|
1212
|
+
i++;
|
|
1213
|
+
}
|
|
1214
|
+
else {
|
|
1215
|
+
flags[key] = true;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return flags;
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Command: diagram:model
|
|
1223
|
+
*
|
|
1224
|
+
* Generates a single-model SVG card.
|
|
1225
|
+
*
|
|
1226
|
+
* Reads model spec from --file <json> or falls back to a minimal stub
|
|
1227
|
+
* using --name <ModelName>.
|
|
1228
|
+
*
|
|
1229
|
+
* cli diagram:model --file ./User.json --out ./diagrams/User.svg
|
|
1230
|
+
* cli diagram:model --name Order --out ./diagrams/Order.svg
|
|
1231
|
+
*/
|
|
1232
|
+
async function diagramModel(args) {
|
|
1233
|
+
const flags = parseFlags(args);
|
|
1234
|
+
const file = flags.file;
|
|
1235
|
+
const outArg = flags.out;
|
|
1236
|
+
const name = flags.name;
|
|
1237
|
+
const theme = flags.theme;
|
|
1238
|
+
const color = flags.color;
|
|
1239
|
+
const db = flags.db;
|
|
1240
|
+
const table = flags.table;
|
|
1241
|
+
let opts;
|
|
1242
|
+
// Option 1: Generate from database
|
|
1243
|
+
if (db) {
|
|
1244
|
+
try {
|
|
1245
|
+
const tables = await getDbSchema(db);
|
|
1246
|
+
const models = schemaToModels(tables);
|
|
1247
|
+
if (table) {
|
|
1248
|
+
// Generate single table diagram
|
|
1249
|
+
const model = models.find((m) => m.tableName === table || m.name.toLowerCase() === table.toLowerCase());
|
|
1250
|
+
if (!model) {
|
|
1251
|
+
log(`Table "${table}" not found in database`, 'error');
|
|
1252
|
+
process.exit(1);
|
|
1253
|
+
}
|
|
1254
|
+
opts = model;
|
|
1255
|
+
}
|
|
1256
|
+
else if (name) {
|
|
1257
|
+
const model = models.find((m) => m.name.toLowerCase() === name.toLowerCase());
|
|
1258
|
+
if (!model) {
|
|
1259
|
+
log(`Model "${name}" not found in database`, 'error');
|
|
1260
|
+
process.exit(1);
|
|
1261
|
+
}
|
|
1262
|
+
opts = model;
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
log('diagram:model --db requires --table <name> or --name <ModelName>', 'error');
|
|
1266
|
+
process.exit(1);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
catch (err) {
|
|
1270
|
+
log(`Database connection failed: ${err.message}`, 'error');
|
|
1271
|
+
process.exit(1);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
else if (file) {
|
|
1275
|
+
// Option 2: Load from JSON file
|
|
1276
|
+
const specPath = path.resolve(file);
|
|
1277
|
+
if (!fs.existsSync(specPath)) {
|
|
1278
|
+
log(`Spec file not found: ${specPath}`, 'error');
|
|
1279
|
+
process.exit(1);
|
|
1280
|
+
}
|
|
1281
|
+
opts = JSON.parse(fs.readFileSync(specPath, 'utf-8'));
|
|
1282
|
+
log(`Loaded model spec from ${specPath}`, 'info');
|
|
1283
|
+
}
|
|
1284
|
+
else if (name) {
|
|
1285
|
+
// Option 3: Minimal stub
|
|
1286
|
+
opts = { name, fields: {} };
|
|
1287
|
+
log(`No --file provided — rendering empty model "${name}"`, 'warning');
|
|
1288
|
+
}
|
|
1289
|
+
else {
|
|
1290
|
+
log('diagram:model requires --file <path> or --db <connection-string>', 'error');
|
|
1291
|
+
process.exit(1);
|
|
1292
|
+
}
|
|
1293
|
+
// Apply colorful options from CLI
|
|
1294
|
+
if (theme && ['light', 'dark'].includes(theme)) {
|
|
1295
|
+
opts.theme = theme;
|
|
1296
|
+
log(`Using theme: ${theme}`, 'info');
|
|
1297
|
+
}
|
|
1298
|
+
if (color) {
|
|
1299
|
+
opts.color = color;
|
|
1300
|
+
log(`Using accent color: ${color}`, 'info');
|
|
1301
|
+
}
|
|
1302
|
+
const defaultOut = path.join(process.cwd(), 'diagrams', `${opts.name}.svg`);
|
|
1303
|
+
const outPath = outArg ? path.resolve(outArg) : defaultOut;
|
|
1304
|
+
const diag = new diagrams_1.ModelDiagram(opts);
|
|
1305
|
+
const saved = diag.save(outPath);
|
|
1306
|
+
log(`✔ Model diagram saved → ${saved}`, 'success');
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Command: diagram:er
|
|
1310
|
+
*
|
|
1311
|
+
* Generates a full ER diagram SVG from a schema JSON file.
|
|
1312
|
+
*
|
|
1313
|
+
* Schema JSON format:
|
|
1314
|
+
* {
|
|
1315
|
+
* "title": "My Schema",
|
|
1316
|
+
* "columns": 3,
|
|
1317
|
+
* "models": [ <model-spec>, ... ]
|
|
1318
|
+
* }
|
|
1319
|
+
*
|
|
1320
|
+
* cli diagram:er --file ./schema.json --out ./diagrams/er.svg
|
|
1321
|
+
* cli diagram:er --file ./schema.json --title "Blog Schema" --columns 2
|
|
1322
|
+
*/
|
|
1323
|
+
async function diagramER(args) {
|
|
1324
|
+
const flags = parseFlags(args);
|
|
1325
|
+
const file = flags.file;
|
|
1326
|
+
const outArg = flags.out;
|
|
1327
|
+
const titleFlag = flags.title;
|
|
1328
|
+
const colsFlag = flags.columns;
|
|
1329
|
+
const theme = flags.theme;
|
|
1330
|
+
const color = flags.color;
|
|
1331
|
+
const db = flags.db;
|
|
1332
|
+
let schema;
|
|
1333
|
+
// Option 1: Generate from database
|
|
1334
|
+
if (db) {
|
|
1335
|
+
try {
|
|
1336
|
+
const tables = await getDbSchema(db);
|
|
1337
|
+
const models = schemaToModels(tables);
|
|
1338
|
+
schema = {
|
|
1339
|
+
title: 'Database Schema',
|
|
1340
|
+
columns: parseInt(colsFlag ?? '', 10) || 3,
|
|
1341
|
+
models,
|
|
1342
|
+
};
|
|
1343
|
+
log(`Generated ER diagram from database (${models.length} tables)`, 'info');
|
|
1344
|
+
}
|
|
1345
|
+
catch (err) {
|
|
1346
|
+
log(`Database connection failed: ${err.message}`, 'error');
|
|
1347
|
+
process.exit(1);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
else if (file) {
|
|
1351
|
+
// Option 2: Load from JSON file
|
|
1352
|
+
const schemaPath = path.resolve(file);
|
|
1353
|
+
if (!fs.existsSync(schemaPath)) {
|
|
1354
|
+
log(`Schema file not found: ${schemaPath}`, 'error');
|
|
1355
|
+
process.exit(1);
|
|
1356
|
+
}
|
|
1357
|
+
const raw = fs.readFileSync(schemaPath, 'utf-8');
|
|
1358
|
+
schema = JSON.parse(raw);
|
|
1359
|
+
log(`Loaded ER schema from ${schemaPath} (${schema.models.length} models)`, 'info');
|
|
1360
|
+
}
|
|
1361
|
+
else {
|
|
1362
|
+
log('diagram:er requires --file <schema.json> or --db <connection-string>', 'error');
|
|
1363
|
+
process.exit(1);
|
|
1364
|
+
}
|
|
1365
|
+
// Allow CLI overrides
|
|
1366
|
+
if (titleFlag)
|
|
1367
|
+
schema.title = titleFlag;
|
|
1368
|
+
if (colsFlag)
|
|
1369
|
+
schema.columns = parseInt(colsFlag, 10);
|
|
1370
|
+
if (theme)
|
|
1371
|
+
schema.theme = theme;
|
|
1372
|
+
if (color)
|
|
1373
|
+
schema.color = color;
|
|
1374
|
+
const defaultOut = path.join(process.cwd(), 'diagrams', 'er-diagram.svg');
|
|
1375
|
+
const outPath = outArg ? path.resolve(outArg) : defaultOut;
|
|
1376
|
+
const er = new diagrams_1.ERDiagram(schema);
|
|
1377
|
+
const saved = er.save(outPath);
|
|
1378
|
+
log(`✔ ER diagram saved → ${saved}`, 'success');
|
|
1379
|
+
log(` Models rendered: ${schema.models.map((m) => m.name).join(', ')}`, 'info');
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Command: diagram:migration
|
|
1383
|
+
*
|
|
1384
|
+
* Generates a migration diagram SVG showing schema changes.
|
|
1385
|
+
*
|
|
1386
|
+
* Migration JSON format:
|
|
1387
|
+
* {
|
|
1388
|
+
* "title": "Add email verification",
|
|
1389
|
+
* "description": "Add email verification fields to users",
|
|
1390
|
+
* "timestamp": "2024-01-15T10:30:00Z",
|
|
1391
|
+
* "changes": [
|
|
1392
|
+
* { "table": "users", "type": "modify", "changes": [
|
|
1393
|
+
* { "name": "emailVerified", "type": "BOOLEAN", "action": "add" },
|
|
1394
|
+
* { "name": "verifiedAt", "type": "DATE", "action": "add" }
|
|
1395
|
+
* ]},
|
|
1396
|
+
* { "table": "sessions", "type": "add", "changes": [
|
|
1397
|
+
* { "name": "id", "type": "UUID", "action": "add" },
|
|
1398
|
+
* { "name": "userId", "type": "UUID", "action": "add" }
|
|
1399
|
+
* ]}
|
|
1400
|
+
* ]
|
|
1401
|
+
* }
|
|
1402
|
+
*
|
|
1403
|
+
* cli diagram:migration --file ./migration.json --out ./diagrams/migration.svg
|
|
1404
|
+
* cli diagram:migration --file ./migration.json --title "User Migration"
|
|
1405
|
+
*/
|
|
1406
|
+
function diagramMigration(args) {
|
|
1407
|
+
const flags = parseFlags(args);
|
|
1408
|
+
const file = flags.file;
|
|
1409
|
+
const outArg = flags.out;
|
|
1410
|
+
const titleFlag = flags.title;
|
|
1411
|
+
const descFlag = flags.description;
|
|
1412
|
+
if (!file) {
|
|
1413
|
+
log('diagram:migration requires --file <migration.json>', 'error');
|
|
1414
|
+
log('Migration format: { "title":"…", "description":"…", "changes": [ { "table":"...", "type":"add|modify|remove", "changes": [...] } ] }', 'info');
|
|
1415
|
+
process.exit(1);
|
|
1416
|
+
}
|
|
1417
|
+
const migrationPath = path.resolve(file);
|
|
1418
|
+
if (!fs.existsSync(migrationPath)) {
|
|
1419
|
+
log(`Migration file not found: ${migrationPath}`, 'error');
|
|
1420
|
+
process.exit(1);
|
|
1421
|
+
}
|
|
1422
|
+
const raw = fs.readFileSync(migrationPath, 'utf-8');
|
|
1423
|
+
const opts = JSON.parse(raw);
|
|
1424
|
+
log(`Loaded migration from ${migrationPath} (${opts.changes.length} table changes)`, 'info');
|
|
1425
|
+
// CLI overrides
|
|
1426
|
+
if (titleFlag)
|
|
1427
|
+
opts.title = titleFlag;
|
|
1428
|
+
if (descFlag)
|
|
1429
|
+
opts.description = descFlag;
|
|
1430
|
+
const defaultOut = path.join(process.cwd(), 'diagrams', 'migration.svg');
|
|
1431
|
+
const outPath = outArg ? path.resolve(outArg) : defaultOut;
|
|
1432
|
+
const diag = new diagrams_1.MigrationDiagram(opts);
|
|
1433
|
+
const saved = diag.save(outPath);
|
|
1434
|
+
log(`✔ Migration diagram saved → ${saved}`, 'success');
|
|
1435
|
+
log(` Changes: ${opts.changes.map((c) => `${c.type} ${c.table}`).join(', ')}`, 'info');
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Extended visualizer generators — each renders its own JSON spec to SVG.
|
|
1439
|
+
* Every one accepts `theme`/`color`/(most) `title` in its options, so the
|
|
1440
|
+
* generic handler wires the matching CLI flags through.
|
|
1441
|
+
*/
|
|
1442
|
+
const EXTRA_DIAGRAMS = {
|
|
1443
|
+
class: diagrams_1.ClassDiagram,
|
|
1444
|
+
relational: diagrams_1.RelationalDiagram,
|
|
1445
|
+
sequence: diagrams_1.SequenceDiagram,
|
|
1446
|
+
state: diagrams_1.StateDiagram,
|
|
1447
|
+
dependency: diagrams_1.DependencyDiagram,
|
|
1448
|
+
schemadoc: diagrams_1.SchemaDocDiagram,
|
|
1449
|
+
index: diagrams_1.IndexDiagram,
|
|
1450
|
+
flow: diagrams_1.FlowDiagram,
|
|
1451
|
+
tree: diagrams_1.TreeDiagram,
|
|
1452
|
+
chen: diagrams_1.ChenDiagram,
|
|
1453
|
+
package: diagrams_1.PackageDiagram,
|
|
1454
|
+
gantt: diagrams_1.GanttDiagram,
|
|
1455
|
+
};
|
|
1456
|
+
/**
|
|
1457
|
+
* Command: diagram:<type> for the extended visualizers (class, relational,
|
|
1458
|
+
* sequence, state, dependency, schemadoc, index, flow, tree, chen, package,
|
|
1459
|
+
* gantt). Reads the generator's options from --file <json>, applies
|
|
1460
|
+
* --theme/--color/--title, and writes the SVG to --out.
|
|
1461
|
+
*
|
|
1462
|
+
* cli diagram:class --file ./classes.json --out ./diagrams/classes.svg
|
|
1463
|
+
* cli diagram:relational --file ./schema.json --out ./diagrams/erd.svg --theme dark
|
|
1464
|
+
* cli diagram:gantt --file ./plan.json --out ./diagrams/plan.svg --color emerald
|
|
1465
|
+
*/
|
|
1466
|
+
function diagramGeneric(type, args) {
|
|
1467
|
+
const Ctor = EXTRA_DIAGRAMS[type];
|
|
1468
|
+
if (!Ctor) {
|
|
1469
|
+
log(`Unknown diagram type: ${type}`, 'error');
|
|
1470
|
+
process.exit(1);
|
|
1471
|
+
}
|
|
1472
|
+
const flags = parseFlags(args);
|
|
1473
|
+
const file = flags.file;
|
|
1474
|
+
const outArg = flags.out;
|
|
1475
|
+
const theme = flags.theme;
|
|
1476
|
+
const color = flags.color;
|
|
1477
|
+
const title = flags.title;
|
|
1478
|
+
if (!file) {
|
|
1479
|
+
log(`diagram:${type} requires --file <spec.json>`, 'error');
|
|
1480
|
+
process.exit(1);
|
|
1481
|
+
}
|
|
1482
|
+
const specPath = path.resolve(file);
|
|
1483
|
+
if (!fs.existsSync(specPath)) {
|
|
1484
|
+
log(`Spec file not found: ${specPath}`, 'error');
|
|
1485
|
+
process.exit(1);
|
|
1486
|
+
}
|
|
1487
|
+
const opts = JSON.parse(fs.readFileSync(specPath, 'utf-8'));
|
|
1488
|
+
log(`Loaded ${type} spec from ${specPath}`, 'info');
|
|
1489
|
+
if (theme && ['light', 'dark'].includes(theme)) {
|
|
1490
|
+
opts.theme = theme;
|
|
1491
|
+
log(`Using theme: ${theme}`, 'info');
|
|
1492
|
+
}
|
|
1493
|
+
if (color) {
|
|
1494
|
+
opts.color = color;
|
|
1495
|
+
log(`Using accent color: ${color}`, 'info');
|
|
1496
|
+
}
|
|
1497
|
+
if (title && opts.title === undefined)
|
|
1498
|
+
opts.title = title;
|
|
1499
|
+
const defaultOut = path.join(process.cwd(), 'diagrams', `${type}.svg`);
|
|
1500
|
+
const outPath = outArg ? path.resolve(outArg) : defaultOut;
|
|
1501
|
+
const saved = new Ctor(opts).save(outPath);
|
|
1502
|
+
log(`✔ ${type} diagram saved → ${saved}`, 'success');
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Map SQL column types to DataTypes (used by model:create introspection)
|
|
1506
|
+
*/
|
|
1507
|
+
function mapColumnType(sqlType) {
|
|
1508
|
+
const typeUpper = sqlType.toUpperCase();
|
|
1509
|
+
const varcharMatch = typeUpper.match(/VARCHAR\((\d+)\)/);
|
|
1510
|
+
if (varcharMatch) {
|
|
1511
|
+
return `DataTypes.STRING(${varcharMatch[1]})`;
|
|
1512
|
+
}
|
|
1513
|
+
const charMatch = typeUpper.match(/CHAR\((\d+)\)/);
|
|
1514
|
+
if (charMatch) {
|
|
1515
|
+
return `DataTypes.CHAR(${charMatch[1]})`;
|
|
1516
|
+
}
|
|
1517
|
+
const decimalMatch = typeUpper.match(/DECIMAL\((\d+),(\d+)\)/);
|
|
1518
|
+
if (decimalMatch) {
|
|
1519
|
+
return `DataTypes.DECIMAL(${decimalMatch[1]}, ${decimalMatch[2]})`;
|
|
1520
|
+
}
|
|
1521
|
+
const floatMatch = typeUpper.match(/FLOAT\((\d+),(\d+)\)/);
|
|
1522
|
+
if (floatMatch) {
|
|
1523
|
+
return `DataTypes.FLOAT({ precision: ${floatMatch[1]}, decimals: ${floatMatch[2]} })`;
|
|
1524
|
+
}
|
|
1525
|
+
if (typeUpper.includes('INT')) {
|
|
1526
|
+
if (typeUpper.includes('TINYINT'))
|
|
1527
|
+
return 'DataTypes.TINYINT()';
|
|
1528
|
+
if (typeUpper.includes('SMALLINT'))
|
|
1529
|
+
return 'DataTypes.SMALLINT()';
|
|
1530
|
+
if (typeUpper.includes('MEDIUMINT'))
|
|
1531
|
+
return 'DataTypes.MEDIUMINT()';
|
|
1532
|
+
if (typeUpper.includes('BIGINT'))
|
|
1533
|
+
return 'DataTypes.BIGINT()';
|
|
1534
|
+
return 'DataTypes.INTEGER()';
|
|
1535
|
+
}
|
|
1536
|
+
if (typeUpper === 'TEXT' || typeUpper.includes('TEXT')) {
|
|
1537
|
+
if (typeUpper.includes('TINY'))
|
|
1538
|
+
return 'DataTypes.TEXT("tiny")';
|
|
1539
|
+
if (typeUpper.includes('MEDIUM'))
|
|
1540
|
+
return 'DataTypes.TEXT("medium")';
|
|
1541
|
+
if (typeUpper.includes('LONG'))
|
|
1542
|
+
return 'DataTypes.TEXT("long")';
|
|
1543
|
+
return 'DataTypes.TEXT()';
|
|
1544
|
+
}
|
|
1545
|
+
if (typeUpper === 'FLOAT' || typeUpper.includes('FLOAT'))
|
|
1546
|
+
return 'DataTypes.FLOAT()';
|
|
1547
|
+
if (typeUpper === 'DOUBLE' || typeUpper.includes('DOUBLE'))
|
|
1548
|
+
return 'DataTypes.DOUBLE()';
|
|
1549
|
+
if (typeUpper.includes('REAL') || typeUpper.includes('PRECISION'))
|
|
1550
|
+
return 'DataTypes.FLOAT()';
|
|
1551
|
+
if (typeUpper.includes('BOOLEAN') || typeUpper.includes('BOOL') || typeUpper === 'INTEGER') {
|
|
1552
|
+
return 'DataTypes.BOOLEAN()';
|
|
1553
|
+
}
|
|
1554
|
+
if (typeUpper.includes('DATETIME') || typeUpper.includes('TIMESTAMP')) {
|
|
1555
|
+
return 'DataTypes.DATE()';
|
|
1556
|
+
}
|
|
1557
|
+
if (typeUpper === 'DATE')
|
|
1558
|
+
return 'DataTypes.DATEONLY()';
|
|
1559
|
+
if (typeUpper === 'TIME')
|
|
1560
|
+
return 'DataTypes.TIME()';
|
|
1561
|
+
if (typeUpper.includes('UUID'))
|
|
1562
|
+
return 'DataTypes.UUID()';
|
|
1563
|
+
if (typeUpper.includes('JSON')) {
|
|
1564
|
+
if (typeUpper.includes('JSONB'))
|
|
1565
|
+
return 'DataTypes.JSONB(true)';
|
|
1566
|
+
return 'DataTypes.JSON(true)';
|
|
1567
|
+
}
|
|
1568
|
+
if (typeUpper.includes('BLOB') || typeUpper.includes('BINARY') || typeUpper.includes('BYTEA')) {
|
|
1569
|
+
if (typeUpper.includes('TINY'))
|
|
1570
|
+
return 'DataTypes.TINYBLOB()';
|
|
1571
|
+
if (typeUpper.includes('MEDIUM'))
|
|
1572
|
+
return 'DataTypes.MEDIUMBLOB()';
|
|
1573
|
+
if (typeUpper.includes('LONG'))
|
|
1574
|
+
return 'DataTypes.LONGBLOB()';
|
|
1575
|
+
return 'DataTypes.BLOB()';
|
|
1576
|
+
}
|
|
1577
|
+
const enumMatch = sqlType.match(/enum\(['"](.+?)['"]/i);
|
|
1578
|
+
if (enumMatch) {
|
|
1579
|
+
const values = enumMatch[1]
|
|
1580
|
+
.split("','")
|
|
1581
|
+
.map((v) => `'${v}'`)
|
|
1582
|
+
.join(', ');
|
|
1583
|
+
return `DataTypes.ENUM(${values})`;
|
|
1584
|
+
}
|
|
1585
|
+
return 'DataTypes.STRING';
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Generate an introspected model file's content from table column metadata.
|
|
1589
|
+
*/
|
|
1590
|
+
function generateIntrospectedModelFile(tableName, columns, options) {
|
|
1591
|
+
const modelName = options?.modelName || toPascalCase(tableName);
|
|
1592
|
+
const timestamps = options?.timestamps ?? true;
|
|
1593
|
+
const underscored = options?.underscored ?? false;
|
|
1594
|
+
const columnDefinitions = [];
|
|
1595
|
+
const attributes = [];
|
|
1596
|
+
for (const [columnName, colInfo] of Object.entries(columns)) {
|
|
1597
|
+
attributes.push(columnName);
|
|
1598
|
+
const dataTypeStr = mapColumnType(colInfo.type);
|
|
1599
|
+
const parts = [`type: ${dataTypeStr}`];
|
|
1600
|
+
if (!colInfo.allowNull) {
|
|
1601
|
+
parts.push('allowNull: false');
|
|
1602
|
+
}
|
|
1603
|
+
if (colInfo.defaultValue !== null && colInfo.defaultValue !== undefined) {
|
|
1604
|
+
const defaultVal = colInfo.defaultValue;
|
|
1605
|
+
if (typeof defaultVal === 'string') {
|
|
1606
|
+
if (defaultVal.toLowerCase() === 'null') {
|
|
1607
|
+
// Don't add null as default
|
|
1608
|
+
}
|
|
1609
|
+
else if (defaultVal.includes('(')) {
|
|
1610
|
+
parts.push(`defaultValue: prorm.literal('${defaultVal}')`);
|
|
1611
|
+
}
|
|
1612
|
+
else {
|
|
1613
|
+
parts.push(`defaultValue: '${defaultVal}'`);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
else if (typeof defaultVal === 'number') {
|
|
1617
|
+
parts.push(`defaultValue: ${defaultVal}`);
|
|
1618
|
+
}
|
|
1619
|
+
else if (typeof defaultVal === 'boolean') {
|
|
1620
|
+
parts.push(`defaultValue: ${defaultVal}`);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
if (colInfo.primaryKey) {
|
|
1624
|
+
parts.push('primaryKey: true');
|
|
1625
|
+
}
|
|
1626
|
+
if (colInfo.autoIncrement) {
|
|
1627
|
+
parts.push('autoIncrement: true');
|
|
1628
|
+
}
|
|
1629
|
+
columnDefinitions.push(` ${columnName}: { ${parts.join(', ')} }`);
|
|
1630
|
+
}
|
|
1631
|
+
const tableNameStr = underscored ? toSnakeCase(tableName) : tableName;
|
|
1632
|
+
return `/**
|
|
1633
|
+
* Auto-generated model for ${tableName}
|
|
1634
|
+
* Generated by CLI model:create command
|
|
1635
|
+
*/
|
|
1636
|
+
|
|
1637
|
+
import { DataTypes, Prorm } from 'ts-prorm-orm';
|
|
1638
|
+
|
|
1639
|
+
/**
|
|
1640
|
+
* Attributes for the ${modelName} model.
|
|
1641
|
+
*/
|
|
1642
|
+
export interface ${modelName}Attributes {
|
|
1643
|
+
${attributes.map((attr) => `${attr}?: any;`).join('\n ')}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/**
|
|
1647
|
+
* Registered ${modelName} model. Populated by init${modelName}().
|
|
1648
|
+
*/
|
|
1649
|
+
export let ${modelName}: ReturnType<Prorm['define']>;
|
|
1650
|
+
|
|
1651
|
+
/**
|
|
1652
|
+
* Register the ${modelName} model with a Prorm instance.
|
|
1653
|
+
*/
|
|
1654
|
+
export function init${modelName}(prorm: Prorm): ReturnType<Prorm['define']> {
|
|
1655
|
+
${modelName} = prorm.define<${modelName}Attributes>(
|
|
1656
|
+
'${modelName}',
|
|
1657
|
+
{
|
|
1658
|
+
${columnDefinitions.join(',\n')},
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
tableName: '${tableNameStr}',
|
|
1662
|
+
${timestamps ? 'timestamps: true,' : 'timestamps: false,'}
|
|
1663
|
+
${underscored ? 'underscored: true,' : ''}
|
|
1664
|
+
}
|
|
1665
|
+
);
|
|
1666
|
+
|
|
1667
|
+
return ${modelName};
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
export default init${modelName};
|
|
1671
|
+
`;
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Convert string to PascalCase
|
|
1675
|
+
*/
|
|
1676
|
+
function toPascalCase(str) {
|
|
1677
|
+
return str
|
|
1678
|
+
.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase())
|
|
1679
|
+
.replace(/^[a-z]/, (chr) => chr.toUpperCase());
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Convert string to snake_case
|
|
1683
|
+
*/
|
|
1684
|
+
function toSnakeCase(str) {
|
|
1685
|
+
return str
|
|
1686
|
+
.replace(/([A-Z])/g, '_$1')
|
|
1687
|
+
.replace(/[^a-zA-Z0-9]+/g, '_')
|
|
1688
|
+
.replace(/^_|_$/g, '')
|
|
1689
|
+
.toLowerCase();
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Parse command line arguments for model:create (DB introspection)
|
|
1693
|
+
*/
|
|
1694
|
+
function parseModelCreateArgs(args) {
|
|
1695
|
+
const options = {
|
|
1696
|
+
dialect: 'sqlite',
|
|
1697
|
+
database: '',
|
|
1698
|
+
outDir: './models',
|
|
1699
|
+
};
|
|
1700
|
+
for (let i = 0; i < args.length; i++) {
|
|
1701
|
+
const arg = args[i];
|
|
1702
|
+
const nextArg = args[i + 1];
|
|
1703
|
+
if (arg === '--dialect' && nextArg) {
|
|
1704
|
+
options.dialect = nextArg;
|
|
1705
|
+
i++;
|
|
1706
|
+
}
|
|
1707
|
+
else if (arg === '--database' && nextArg) {
|
|
1708
|
+
options.database = nextArg;
|
|
1709
|
+
i++;
|
|
1710
|
+
}
|
|
1711
|
+
else if (arg === '--username' && nextArg) {
|
|
1712
|
+
options.username = nextArg;
|
|
1713
|
+
i++;
|
|
1714
|
+
}
|
|
1715
|
+
else if (arg === '--password' && nextArg) {
|
|
1716
|
+
options.password = nextArg;
|
|
1717
|
+
i++;
|
|
1718
|
+
}
|
|
1719
|
+
else if (arg === '--host' && nextArg) {
|
|
1720
|
+
options.host = nextArg;
|
|
1721
|
+
i++;
|
|
1722
|
+
}
|
|
1723
|
+
else if (arg === '--port' && nextArg) {
|
|
1724
|
+
options.port = parseInt(nextArg, 10);
|
|
1725
|
+
i++;
|
|
1726
|
+
}
|
|
1727
|
+
else if (arg === '--storage' && nextArg) {
|
|
1728
|
+
options.storage = nextArg;
|
|
1729
|
+
i++;
|
|
1730
|
+
}
|
|
1731
|
+
else if (arg === '--out-dir' && nextArg) {
|
|
1732
|
+
options.outDir = nextArg;
|
|
1733
|
+
i++;
|
|
1734
|
+
}
|
|
1735
|
+
else if (arg === '--table' && nextArg) {
|
|
1736
|
+
options.table = nextArg;
|
|
1737
|
+
i++;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return options;
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Command: model:create
|
|
1744
|
+
* Generate model files from live database table introspection.
|
|
1745
|
+
*/
|
|
1746
|
+
async function modelCreateCommand(args) {
|
|
1747
|
+
const options = parseModelCreateArgs(args);
|
|
1748
|
+
if (!options.database) {
|
|
1749
|
+
log('Error: --database is required', 'error');
|
|
1750
|
+
log('Usage: orm-cli model:create --dialect sqlite --database ./mydb.sqlite', 'info');
|
|
1751
|
+
process.exit(1);
|
|
1752
|
+
}
|
|
1753
|
+
log(`\n=== Model Generator ===`, 'info');
|
|
1754
|
+
log(`Dialect: ${options.dialect}`, 'info');
|
|
1755
|
+
log(`Database: ${options.database}`, 'info');
|
|
1756
|
+
log(`Output: ${options.outDir}\n`, 'info');
|
|
1757
|
+
const prormConfig = {
|
|
1758
|
+
dialect: options.dialect,
|
|
1759
|
+
logging: false,
|
|
1760
|
+
};
|
|
1761
|
+
if (options.dialect === 'sqlite') {
|
|
1762
|
+
prormConfig.storage = options.storage || options.database;
|
|
1763
|
+
}
|
|
1764
|
+
else {
|
|
1765
|
+
prormConfig.host = options.host || 'localhost';
|
|
1766
|
+
prormConfig.database = options.database;
|
|
1767
|
+
prormConfig.username = options.username || 'root';
|
|
1768
|
+
prormConfig.password = options.password || '';
|
|
1769
|
+
if (options.port) {
|
|
1770
|
+
prormConfig.port = options.port;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
let prorm;
|
|
1774
|
+
try {
|
|
1775
|
+
prorm = new prorm_1.Prorm(prormConfig);
|
|
1776
|
+
await prorm.connect();
|
|
1777
|
+
log('Database connection established.\n', 'success');
|
|
1778
|
+
}
|
|
1779
|
+
catch (error) {
|
|
1780
|
+
log(`Error: Unable to connect to database: ${error.message}`, 'error');
|
|
1781
|
+
process.exit(1);
|
|
1782
|
+
}
|
|
1783
|
+
try {
|
|
1784
|
+
const tables = await prorm.showTables();
|
|
1785
|
+
const targetTables = options.table ? [options.table] : tables;
|
|
1786
|
+
if (targetTables.length === 0) {
|
|
1787
|
+
log('No tables found in database.', 'warning');
|
|
1788
|
+
await prorm.close();
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
log(`Found ${targetTables.length} table(s): ${targetTables.join(', ')}\n`, 'info');
|
|
1792
|
+
const outDir = path.resolve(options.outDir || './models');
|
|
1793
|
+
if (!fs.existsSync(outDir)) {
|
|
1794
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
1795
|
+
log(`Created output directory: ${outDir}\n`, 'success');
|
|
1796
|
+
}
|
|
1797
|
+
const generatedFiles = [];
|
|
1798
|
+
for (const tableName of targetTables) {
|
|
1799
|
+
if (!tables.includes(tableName)) {
|
|
1800
|
+
log(`Warning: Table '${tableName}' does not exist, skipping.`, 'warning');
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1803
|
+
log(`Generating model for table: ${tableName}`, 'info');
|
|
1804
|
+
const tableDescription = await prorm.describeTable(tableName);
|
|
1805
|
+
const modelContent = generateIntrospectedModelFile(tableName, tableDescription);
|
|
1806
|
+
const modelName = toPascalCase(tableName);
|
|
1807
|
+
const fileName = `${modelName}.ts`;
|
|
1808
|
+
const filePath = path.join(outDir, fileName);
|
|
1809
|
+
fs.writeFileSync(filePath, modelContent, 'utf-8');
|
|
1810
|
+
generatedFiles.push(fileName);
|
|
1811
|
+
log(` Created: ${filePath}`, 'success');
|
|
1812
|
+
}
|
|
1813
|
+
log(`\n=== Generation Complete ===\n`, 'success');
|
|
1814
|
+
log(`Generated ${generatedFiles.length} model file(s):`, 'info');
|
|
1815
|
+
for (const file of generatedFiles) {
|
|
1816
|
+
console.log(` - ${file}`);
|
|
1817
|
+
}
|
|
1818
|
+
if (generatedFiles.length > 0) {
|
|
1819
|
+
const indexContent = generatedFiles
|
|
1820
|
+
.map((file) => {
|
|
1821
|
+
const modelName = file.replace('.ts', '');
|
|
1822
|
+
return `export { ${modelName}, init${modelName} } from './${modelName}';`;
|
|
1823
|
+
})
|
|
1824
|
+
.join('\n');
|
|
1825
|
+
fs.writeFileSync(path.join(outDir, 'index.ts'), indexContent, 'utf-8');
|
|
1826
|
+
log(`\nCreated index file: ${path.join(outDir, 'index.ts')}`, 'success');
|
|
1827
|
+
}
|
|
1828
|
+
console.log('\n');
|
|
1829
|
+
}
|
|
1830
|
+
catch (error) {
|
|
1831
|
+
log(`Error during model generation: ${error.message}`, 'error');
|
|
1832
|
+
process.exit(1);
|
|
1833
|
+
}
|
|
1834
|
+
finally {
|
|
1835
|
+
await prorm.close();
|
|
1836
|
+
log('Database connection closed.', 'info');
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
// ─── End model:create ────────────────────────────────────────────────────────
|
|
1840
|
+
/**
|
|
1841
|
+
* Command: prisma:import
|
|
1842
|
+
* Converts a Prisma schema.prisma file into prorm model files + an initial
|
|
1843
|
+
* migration. Pure translation only (src/prisma-migrate/) — this command
|
|
1844
|
+
* just handles reading the input file and writing the generated output to
|
|
1845
|
+
* disk, and reports the unsupported-feature errors the converter throws
|
|
1846
|
+
* (implicit many-to-many relations, composite foreign keys, unmapped
|
|
1847
|
+
* Prisma scalar types) as a normal CLI error rather than a stack trace.
|
|
1848
|
+
*/
|
|
1849
|
+
async function prismaImportCommand(options) {
|
|
1850
|
+
const schemaPath = options.schema || './schema.prisma';
|
|
1851
|
+
const modelsOut = options['models-out'] || './models';
|
|
1852
|
+
const migrationsOut = options['migrations-out'] || './migrations';
|
|
1853
|
+
if (!fs.existsSync(schemaPath)) {
|
|
1854
|
+
log(`Error: schema file not found at ${schemaPath}. Usage: prisma:import --schema <path> [--models-out <dir>] [--migrations-out <dir>]`, 'error');
|
|
1855
|
+
process.exit(1);
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
const { convertPrismaSchema } = require('./prisma-migrate');
|
|
1859
|
+
const schemaSource = fs.readFileSync(schemaPath, 'utf-8');
|
|
1860
|
+
let result;
|
|
1861
|
+
try {
|
|
1862
|
+
result = convertPrismaSchema(schemaSource);
|
|
1863
|
+
}
|
|
1864
|
+
catch (error) {
|
|
1865
|
+
log(`Error converting schema: ${error.message}`, 'error');
|
|
1866
|
+
process.exit(1);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
fs.mkdirSync(modelsOut, { recursive: true });
|
|
1870
|
+
for (const model of result.models) {
|
|
1871
|
+
fs.writeFileSync(path.join(modelsOut, `${model.name}.ts`), model.source);
|
|
1872
|
+
log(`Created model: ${path.join(modelsOut, `${model.name}.ts`)}`, 'success');
|
|
1873
|
+
}
|
|
1874
|
+
fs.writeFileSync(path.join(modelsOut, 'index.ts'), result.modelsIndex);
|
|
1875
|
+
log(`Created model registry: ${path.join(modelsOut, 'index.ts')}`, 'success');
|
|
1876
|
+
fs.mkdirSync(migrationsOut, { recursive: true });
|
|
1877
|
+
const migrationFileName = `${Date.now()}-prisma-import.js`;
|
|
1878
|
+
fs.writeFileSync(path.join(migrationsOut, migrationFileName), result.migration);
|
|
1879
|
+
log(`Created migration: ${path.join(migrationsOut, migrationFileName)}`, 'success');
|
|
1880
|
+
log(`Converted ${result.models.length} model(s) from ${schemaPath}. Review the generated files — see the comment ` +
|
|
1881
|
+
'at the top of the migration file for what was translated on a best-effort basis.', 'info');
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Print the full CLI usage/help text to the console
|
|
1885
|
+
*/
|
|
1886
|
+
function showHelp() {
|
|
1887
|
+
console.log(`
|
|
1888
|
+
ORM CLI Tools
|
|
1889
|
+
=============
|
|
1890
|
+
|
|
1891
|
+
Usage:
|
|
1892
|
+
orm-cli <command> [options]
|
|
1893
|
+
|
|
1894
|
+
Commands:
|
|
1895
|
+
init Initialize a new project (config, models, migrations, seeders)
|
|
1896
|
+
model:create Generate model files from live database introspection
|
|
1897
|
+
migration:create Create a new migration file
|
|
1898
|
+
migration:run Run pending migrations
|
|
1899
|
+
migration:revert Revert migrations
|
|
1900
|
+
migration:status Show migration status
|
|
1901
|
+
seeder:create Create a new seeder file
|
|
1902
|
+
db:seed Run pending seeders (--name to run just one, --env to
|
|
1903
|
+
scope by environment, --force to re-run with --name)
|
|
1904
|
+
db:seed:undo Undo one seeder (--name <n>, or the last run if omitted)
|
|
1905
|
+
db:seed:undo:all Undo all executed seeders, in reverse order
|
|
1906
|
+
db:seed:status Show seeder status
|
|
1907
|
+
diagram:model Generate an SVG model diagram
|
|
1908
|
+
diagram:er Generate an SVG ER diagram for multiple models
|
|
1909
|
+
diagram:migration Generate an SVG migration diagram
|
|
1910
|
+
diagram:class Generate a UML class diagram (--file spec.json)
|
|
1911
|
+
diagram:relational Generate a crow's-foot relational diagram (--file spec.json)
|
|
1912
|
+
diagram:sequence Generate a UML sequence diagram (--file spec.json)
|
|
1913
|
+
diagram:state Generate a state-machine diagram (--file spec.json)
|
|
1914
|
+
diagram:dependency Generate a dependency-graph diagram (--file spec.json)
|
|
1915
|
+
diagram:schemadoc Generate a data-dictionary sheet (--file spec.json)
|
|
1916
|
+
diagram:index Generate an index/constraint map (--file spec.json)
|
|
1917
|
+
diagram:flow Generate a flowchart (--file spec.json)
|
|
1918
|
+
diagram:tree Generate a tree / mind-map (--file spec.json)
|
|
1919
|
+
diagram:chen Generate a Chen-notation ER diagram (--file spec.json)
|
|
1920
|
+
diagram:package Generate a UML package diagram (--file spec.json)
|
|
1921
|
+
diagram:gantt Generate a gantt / timeline diagram (--file spec.json)
|
|
1922
|
+
prisma:import Convert a Prisma schema.prisma into prorm models + an initial migration
|
|
1923
|
+
|
|
1924
|
+
Options:
|
|
1925
|
+
--name <name> Migration/model name
|
|
1926
|
+
--steps <n> Number of migrations to revert
|
|
1927
|
+
--path <path> Custom migrations directory path
|
|
1928
|
+
--directory <path> Project directory (for init)
|
|
1929
|
+
--verbose Show detailed output
|
|
1930
|
+
--help, -h Show this help message
|
|
1931
|
+
|
|
1932
|
+
model:create options:
|
|
1933
|
+
--dialect <dialect> Database dialect: sqlite, mysql, postgres, mariadb (default: sqlite)
|
|
1934
|
+
--database <name> Database name or file path (required)
|
|
1935
|
+
--username <user> Database username (mysql/mariadb/postgres)
|
|
1936
|
+
--password <pass> Database password (mysql/mariadb/postgres)
|
|
1937
|
+
--host <host> Database host (default: localhost)
|
|
1938
|
+
--port <port> Database port
|
|
1939
|
+
--storage <path> SQLite storage path
|
|
1940
|
+
--out-dir <path> Output directory for models (default: ./models)
|
|
1941
|
+
--table <name> Specific table to generate a model for (default: all tables)
|
|
1942
|
+
|
|
1943
|
+
Diagram options (diagram:model / diagram:er / diagram:migration):
|
|
1944
|
+
--file <path> JSON spec file (model/ER schema/migration)
|
|
1945
|
+
--db <conn-string> Database connection string (generates diagram from DB)
|
|
1946
|
+
--table <name> Table name for model diagram (with --db)
|
|
1947
|
+
--out <path> Output SVG file path (default: ./diagrams/<name>.svg)
|
|
1948
|
+
--title <title> ER/migration diagram title
|
|
1949
|
+
--columns <n> Number of columns in ER grid layout (default: 3)
|
|
1950
|
+
--theme <theme> Theme: light or dark (default: light)
|
|
1951
|
+
--color <color> Accent color: blue, violet, emerald, amber, pink, sky, indigo, purple, red, teal, green
|
|
1952
|
+
--description <desc> Migration description (diagram:migration only)
|
|
1953
|
+
|
|
1954
|
+
prisma:import options:
|
|
1955
|
+
--schema <path> Path to schema.prisma (default: ./schema.prisma)
|
|
1956
|
+
--models-out <path> Output directory for generated model files (default: ./models)
|
|
1957
|
+
--migrations-out <path> Output directory for the generated migration (default: ./migrations)
|
|
1958
|
+
|
|
1959
|
+
Connection string formats:
|
|
1960
|
+
mysql://user:pass@host:port/database
|
|
1961
|
+
postgres://user:pass@host:port/database
|
|
1962
|
+
sqlite:/path/to/database.db
|
|
1963
|
+
sqlite: (for in-memory database)
|
|
1964
|
+
|
|
1965
|
+
Examples:
|
|
1966
|
+
# Initialize a new project
|
|
1967
|
+
cli init
|
|
1968
|
+
cli init --directory ./my-project
|
|
1969
|
+
|
|
1970
|
+
# Generate models from an existing database (introspection)
|
|
1971
|
+
cli model:create --dialect sqlite --database ./mydb.sqlite
|
|
1972
|
+
cli model:create --dialect mysql --database mydb --username root --password secret --table users
|
|
1973
|
+
|
|
1974
|
+
# Create a new migration
|
|
1975
|
+
cli migration:create -- --name create-users-table
|
|
1976
|
+
|
|
1977
|
+
# Convert an existing Prisma project's schema.prisma to prorm
|
|
1978
|
+
cli prisma:import --schema ./schema.prisma --models-out ./src/models --migrations-out ./migrations
|
|
1979
|
+
|
|
1980
|
+
# Run all pending migrations
|
|
1981
|
+
cli migration:run
|
|
1982
|
+
|
|
1983
|
+
# Run a specific migration
|
|
1984
|
+
cli migration:run -- --name 20240315120000-create-users-table
|
|
1985
|
+
|
|
1986
|
+
# Revert the last migration
|
|
1987
|
+
cli migration:revert
|
|
1988
|
+
|
|
1989
|
+
# Revert a specific migration
|
|
1990
|
+
cli migration:revert -- --name 20240315120000-create-users-table
|
|
1991
|
+
|
|
1992
|
+
# Revert last 3 migrations
|
|
1993
|
+
cli migration:revert -- --steps 3
|
|
1994
|
+
|
|
1995
|
+
# Show migration status
|
|
1996
|
+
cli migration:status
|
|
1997
|
+
|
|
1998
|
+
# Use custom migrations path
|
|
1999
|
+
cli migration:run -- --path ./db/migrations
|
|
2000
|
+
|
|
2001
|
+
# Generate a model diagram from a JSON spec
|
|
2002
|
+
cli diagram:model --file ./User.json --out ./diagrams/User.svg
|
|
2003
|
+
|
|
2004
|
+
# Generate an ER diagram from a schema JSON (multiple models)
|
|
2005
|
+
cli diagram:er --file ./schema.json --out ./diagrams/er.svg
|
|
2006
|
+
|
|
2007
|
+
# Inline single-model diagram (no JSON file needed)
|
|
2008
|
+
cli diagram:model --name User --out ./diagrams/User.svg
|
|
2009
|
+
|
|
2010
|
+
# Generate diagram from database (for documentation)
|
|
2011
|
+
cli diagram:er --db sqlite:./database.db --out ./diagrams/schema.svg
|
|
2012
|
+
cli diagram:model --db mysql://root:pass@localhost:3306/mydb --table users --out ./diagrams/User.svg
|
|
2013
|
+
cli diagram:er --db postgres://user:pass@localhost:5432/mydb --title "Production DB" --color violet
|
|
2014
|
+
|
|
2015
|
+
# Extended visualizers — each reads its own JSON spec (--file) and writes SVG (--out)
|
|
2016
|
+
cli diagram:class --file ./classes.json --out ./diagrams/classes.svg
|
|
2017
|
+
cli diagram:relational --file ./schema.json --out ./diagrams/erd.svg --theme dark
|
|
2018
|
+
cli diagram:sequence --file ./flow.json --out ./diagrams/sequence.svg
|
|
2019
|
+
cli diagram:state --file ./lifecycle.json --out ./diagrams/state.svg
|
|
2020
|
+
cli diagram:dependency --file ./deps.json --out ./diagrams/deps.svg
|
|
2021
|
+
cli diagram:schemadoc --file ./schema.json --out ./diagrams/data-dictionary.svg
|
|
2022
|
+
cli diagram:index --file ./indexes.json --out ./diagrams/indexes.svg
|
|
2023
|
+
cli diagram:flow --file ./pipeline.json --out ./diagrams/flow.svg
|
|
2024
|
+
cli diagram:tree --file ./tree.json --out ./diagrams/tree.svg
|
|
2025
|
+
cli diagram:chen --file ./entities.json --out ./diagrams/chen.svg
|
|
2026
|
+
cli diagram:package --file ./packages.json --out ./diagrams/packages.svg
|
|
2027
|
+
cli diagram:gantt --file ./plan.json --out ./diagrams/plan.svg --color emerald
|
|
2028
|
+
|
|
2029
|
+
# JSON spec format for a single model (User.json):
|
|
2030
|
+
# {
|
|
2031
|
+
# "name": "User",
|
|
2032
|
+
# "tableName": "users",
|
|
2033
|
+
# "fields": {
|
|
2034
|
+
# "id": { "type": "INTEGER", "primaryKey": true, "autoIncrement": true },
|
|
2035
|
+
# "email": { "type": "VARCHAR(255)", "unique": true, "allowNull": false }
|
|
2036
|
+
# },
|
|
2037
|
+
# "associations": [
|
|
2038
|
+
# { "type": "hasMany", "target": "Post", "foreignKey": "userId" }
|
|
2039
|
+
# ],
|
|
2040
|
+
# "timestamps": true
|
|
2041
|
+
# }
|
|
2042
|
+
#
|
|
2043
|
+
# JSON spec format for an ER diagram (schema.json):
|
|
2044
|
+
# {
|
|
2045
|
+
# "title": "My App Schema",
|
|
2046
|
+
# "columns": 3,
|
|
2047
|
+
# "models": [ <model-spec>, <model-spec>, ... ]
|
|
2048
|
+
# }
|
|
2049
|
+
|
|
2050
|
+
Environment Variables:
|
|
2051
|
+
DATABASE_URL Full database URL (e.g., postgres://user:pass@localhost/db)
|
|
2052
|
+
DB_DIALECT Database dialect: sqlite, postgres, mysql, mariadb
|
|
2053
|
+
DB_NAME Database name
|
|
2054
|
+
DB_USER Database username
|
|
2055
|
+
DB_PASSWORD Database password
|
|
2056
|
+
DB_HOST Database host (default: localhost)
|
|
2057
|
+
DB_PORT Database port (default: 5432)
|
|
2058
|
+
`);
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Parse command-line arguments for migration commands
|
|
2062
|
+
*/
|
|
2063
|
+
function parseMigrationArgs(args) {
|
|
2064
|
+
const command = args[0] || 'help';
|
|
2065
|
+
const options = {};
|
|
2066
|
+
// Start at 1, not 2: args[0] is the command itself (e.g. 'migration:create'),
|
|
2067
|
+
// so the first real flag begins at args[1]. Starting at 2 silently dropped
|
|
2068
|
+
// the first flag after the command (e.g. `migration:create --name foo`
|
|
2069
|
+
// lost both `--name` and its value `foo`).
|
|
2070
|
+
for (let i = 1; i < args.length; i++) {
|
|
2071
|
+
const arg = args[i];
|
|
2072
|
+
if (arg.startsWith('--')) {
|
|
2073
|
+
const key = arg.slice(2);
|
|
2074
|
+
const nextArg = args[i + 1];
|
|
2075
|
+
if (nextArg && !nextArg.startsWith('--')) {
|
|
2076
|
+
options[key] = nextArg;
|
|
2077
|
+
i++;
|
|
2078
|
+
}
|
|
2079
|
+
else {
|
|
2080
|
+
options[key] = true;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
else if (arg.startsWith('-')) {
|
|
2084
|
+
const key = arg.slice(1);
|
|
2085
|
+
options[key] = true;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return { command, options };
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Main entry point
|
|
2092
|
+
*/
|
|
2093
|
+
function main() {
|
|
2094
|
+
const args = process.argv.slice(2);
|
|
2095
|
+
const command = args[0];
|
|
2096
|
+
// Handle init command (original behavior)
|
|
2097
|
+
if (command === 'init') {
|
|
2098
|
+
const dirIndex = args.indexOf('--directory');
|
|
2099
|
+
const directory = dirIndex !== -1 ? args[dirIndex + 1] : undefined;
|
|
2100
|
+
init({ directory });
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
// Handle help
|
|
2104
|
+
if (command === '--help' || command === '-h' || !command) {
|
|
2105
|
+
showHelp();
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
// Build config
|
|
2109
|
+
const config = {
|
|
2110
|
+
...defaultConfig,
|
|
2111
|
+
migrationsPath: (args.includes('--path') ? args[args.indexOf('--path') + 1] : undefined) ||
|
|
2112
|
+
defaultConfig.migrationsPath,
|
|
2113
|
+
verbose: args.includes('--verbose'),
|
|
2114
|
+
};
|
|
2115
|
+
// Parse migration command
|
|
2116
|
+
const { command: migrationCommand, options } = parseMigrationArgs(args);
|
|
2117
|
+
// Execute migration command asynchronously
|
|
2118
|
+
(async () => {
|
|
2119
|
+
try {
|
|
2120
|
+
switch (migrationCommand) {
|
|
2121
|
+
case 'model:create':
|
|
2122
|
+
await modelCreateCommand(args.slice(1));
|
|
2123
|
+
break;
|
|
2124
|
+
case 'migration:create':
|
|
2125
|
+
await migrationCreate(options.name, config);
|
|
2126
|
+
break;
|
|
2127
|
+
case 'migration:run':
|
|
2128
|
+
await migrationRun(options, config);
|
|
2129
|
+
break;
|
|
2130
|
+
case 'migration:revert':
|
|
2131
|
+
await migrationRevert(options, config);
|
|
2132
|
+
break;
|
|
2133
|
+
case 'migration:status':
|
|
2134
|
+
await migrationStatus(config);
|
|
2135
|
+
break;
|
|
2136
|
+
case 'db:create':
|
|
2137
|
+
await dbCreate();
|
|
2138
|
+
break;
|
|
2139
|
+
case 'db:drop':
|
|
2140
|
+
await dbDrop();
|
|
2141
|
+
break;
|
|
2142
|
+
case 'seeder:create':
|
|
2143
|
+
await seederCreate(options.name, config);
|
|
2144
|
+
break;
|
|
2145
|
+
case 'db:seed':
|
|
2146
|
+
await dbSeed(options, config);
|
|
2147
|
+
break;
|
|
2148
|
+
case 'db:seed:undo':
|
|
2149
|
+
await dbSeedUndo(options, config);
|
|
2150
|
+
break;
|
|
2151
|
+
case 'db:seed:undo:all':
|
|
2152
|
+
await dbSeedUndoAll(config);
|
|
2153
|
+
break;
|
|
2154
|
+
case 'db:seed:status':
|
|
2155
|
+
await dbSeedStatus(config);
|
|
2156
|
+
break;
|
|
2157
|
+
case 'diagram:model':
|
|
2158
|
+
await diagramModel(args);
|
|
2159
|
+
break;
|
|
2160
|
+
case 'diagram:er':
|
|
2161
|
+
await diagramER(args);
|
|
2162
|
+
break;
|
|
2163
|
+
case 'diagram:migration':
|
|
2164
|
+
await diagramMigration(args);
|
|
2165
|
+
break;
|
|
2166
|
+
case 'diagram:class':
|
|
2167
|
+
case 'diagram:relational':
|
|
2168
|
+
case 'diagram:sequence':
|
|
2169
|
+
case 'diagram:state':
|
|
2170
|
+
case 'diagram:dependency':
|
|
2171
|
+
case 'diagram:schemadoc':
|
|
2172
|
+
case 'diagram:index':
|
|
2173
|
+
case 'diagram:flow':
|
|
2174
|
+
case 'diagram:tree':
|
|
2175
|
+
case 'diagram:chen':
|
|
2176
|
+
case 'diagram:package':
|
|
2177
|
+
case 'diagram:gantt':
|
|
2178
|
+
diagramGeneric(command.slice('diagram:'.length), args);
|
|
2179
|
+
break;
|
|
2180
|
+
case 'prisma:import':
|
|
2181
|
+
await prismaImportCommand(options);
|
|
2182
|
+
break;
|
|
2183
|
+
default:
|
|
2184
|
+
if (command) {
|
|
2185
|
+
log(`Unknown command: ${command}`, 'error');
|
|
2186
|
+
}
|
|
2187
|
+
showHelp();
|
|
2188
|
+
process.exit(1);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
catch (error) {
|
|
2192
|
+
log(`Error: ${error.message}`, 'error');
|
|
2193
|
+
process.exit(1);
|
|
2194
|
+
}
|
|
2195
|
+
})();
|
|
2196
|
+
}
|
|
2197
|
+
// Run if executed directly (not when imported as a module)
|
|
2198
|
+
if (require.main === module) {
|
|
2199
|
+
main();
|
|
2200
|
+
}
|