tina4-nodejs 3.13.124 → 3.13.130
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/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +455 -85
- package/packages/cli/src/bin.ts +6 -0
- package/packages/cli/src/commands/generate.ts +93 -19
- package/packages/cli/src/commands/lint.ts +360 -0
- package/packages/core/dist/index.js +172 -23
- package/packages/core/src/fakeData.ts +37 -2
- package/packages/orm/dist/index.js +172 -23
- package/packages/orm/src/adapters/firebird.ts +7 -0
- package/packages/orm/src/adapters/mssql.ts +10 -0
- package/packages/orm/src/adapters/mysql.ts +6 -0
- package/packages/orm/src/fakeData.ts +27 -2
- package/packages/orm/src/seeder.ts +8 -3
- package/packages/orm/src/sqlTranslator.ts +45 -0
- package/types/cli/src/commands/generate.d.ts +29 -0
- package/types/cli/src/commands/lint.d.ts +5 -0
- package/types/core/src/fakeData.d.ts +12 -0
- package/types/orm/src/fakeData.d.ts +12 -1
- package/types/orm/src/sqlTranslator.d.ts +19 -0
package/packages/cli/dist/bin.js
CHANGED
|
@@ -1186,8 +1186,8 @@ function hashPassword(password, salt, iterations = 26e4) {
|
|
|
1186
1186
|
}
|
|
1187
1187
|
function checkPassword(password, hash) {
|
|
1188
1188
|
try {
|
|
1189
|
-
const
|
|
1190
|
-
const parts = hash.split(
|
|
1189
|
+
const delimiter5 = hash.includes("$") ? "$" : ":";
|
|
1190
|
+
const parts = hash.split(delimiter5);
|
|
1191
1191
|
if (parts.length !== 4 || parts[0] !== "pbkdf2_sha256") return false;
|
|
1192
1192
|
const iterations = parseInt(parts[1], 10);
|
|
1193
1193
|
const salt = parts[2];
|
|
@@ -6375,6 +6375,38 @@ var init_sqlTranslator = __esm({
|
|
|
6375
6375
|
return sql;
|
|
6376
6376
|
}
|
|
6377
6377
|
}
|
|
6378
|
+
/**
|
|
6379
|
+
* Translate SQLite-canonical DDL column TYPES + CREATE-TABLE options to the
|
|
6380
|
+
* target engine.
|
|
6381
|
+
*
|
|
6382
|
+
* ONLY acts on `CREATE TABLE` / `ALTER TABLE` statements, so a query or INSERT
|
|
6383
|
+
* that happens to contain the word `TEXT` (a column name, a string literal) is
|
|
6384
|
+
* never rewritten. Complements `autoIncrementSyntax` (which maps the id
|
|
6385
|
+
* keyword) so ONE portable migration — and every `Model.createTable()` DDL,
|
|
6386
|
+
* which is also SQLite-canonical — applies on every engine instead of failing
|
|
6387
|
+
* on Firebird/MSSQL.
|
|
6388
|
+
*
|
|
6389
|
+
* * Firebird has no `TEXT` (-607), no `REAL`, and no `CREATE TABLE IF NOT
|
|
6390
|
+
* EXISTS`.
|
|
6391
|
+
* * MSSQL has no `CREATE TABLE IF NOT EXISTS` and its `TIMESTAMP` is a
|
|
6392
|
+
* rowversion, not a datetime — a `created_at TIMESTAMP` there is wrong.
|
|
6393
|
+
* * MySQL's `TIMESTAMP` carries auto-update / 2038 surprises, so a datetime
|
|
6394
|
+
* column maps to `DATETIME` (matching the adapters' createTableAsync).
|
|
6395
|
+
*/
|
|
6396
|
+
static ddlTypes(sql, engine) {
|
|
6397
|
+
const head = sql.replace(/^(?:\s*--[^\n]*\n)+/, "");
|
|
6398
|
+
if (!/^\s*(?:CREATE\s+TABLE|ALTER\s+TABLE)\b/i.test(head)) return sql;
|
|
6399
|
+
switch ((engine ?? "").toLowerCase()) {
|
|
6400
|
+
case "firebird":
|
|
6401
|
+
return sql.replace(/\bIF\s+NOT\s+EXISTS\b/gi, "").replace(/\bBLOB\s+SUB_TYPE\s+TEXT\b/gi, "\0FBTEXT\0").replace(/\bTEXT\b/gi, "BLOB SUB_TYPE TEXT").replaceAll("\0FBTEXT\0", "BLOB SUB_TYPE TEXT").replace(/\bREAL\b/gi, "DOUBLE PRECISION");
|
|
6402
|
+
case "mssql":
|
|
6403
|
+
return sql.replace(/\bIF\s+NOT\s+EXISTS\b/gi, "").replace(/\bTIMESTAMP\b/gi, "DATETIME2");
|
|
6404
|
+
case "mysql":
|
|
6405
|
+
return sql.replace(/\bTIMESTAMP\b/gi, "DATETIME");
|
|
6406
|
+
default:
|
|
6407
|
+
return sql;
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6378
6410
|
/**
|
|
6379
6411
|
* Convert ? placeholders to engine-specific style.
|
|
6380
6412
|
*
|
|
@@ -8416,6 +8448,8 @@ var init_mysql = __esm({
|
|
|
8416
8448
|
translateSql(sql) {
|
|
8417
8449
|
let translated = SQLTranslator.concatPipesToFunc(sql);
|
|
8418
8450
|
translated = SQLTranslator.ilikeToLike(translated);
|
|
8451
|
+
translated = SQLTranslator.autoIncrementSyntax(translated, "mysql");
|
|
8452
|
+
translated = SQLTranslator.ddlTypes(translated, "mysql");
|
|
8419
8453
|
return translated;
|
|
8420
8454
|
}
|
|
8421
8455
|
execute(sql, params) {
|
|
@@ -8837,6 +8871,9 @@ var init_mssql = __esm({
|
|
|
8837
8871
|
let translated = SQLTranslator.limitToTop(sql);
|
|
8838
8872
|
translated = SQLTranslator.concatPipesToFunc(translated);
|
|
8839
8873
|
translated = SQLTranslator.ilikeToLike(translated);
|
|
8874
|
+
translated = SQLTranslator.autoIncrementSyntax(translated, "mssql");
|
|
8875
|
+
translated = SQLTranslator.booleanToInt(translated);
|
|
8876
|
+
translated = SQLTranslator.ddlTypes(translated, "mssql");
|
|
8840
8877
|
return translated;
|
|
8841
8878
|
}
|
|
8842
8879
|
execSqlPromise(sql, params) {
|
|
@@ -9467,6 +9504,8 @@ var init_firebird = __esm({
|
|
|
9467
9504
|
let translated = SQLTranslator.limitToRows(sql);
|
|
9468
9505
|
translated = SQLTranslator.booleanToInt(translated);
|
|
9469
9506
|
translated = SQLTranslator.ilikeToLike(translated);
|
|
9507
|
+
translated = SQLTranslator.autoIncrementSyntax(translated, "firebird");
|
|
9508
|
+
translated = SQLTranslator.ddlTypes(translated, "firebird");
|
|
9470
9509
|
return translated;
|
|
9471
9510
|
}
|
|
9472
9511
|
/**
|
|
@@ -12555,7 +12594,7 @@ async function removeMigrationRecord(name) {
|
|
|
12555
12594
|
[name]
|
|
12556
12595
|
);
|
|
12557
12596
|
}
|
|
12558
|
-
async function rollback(migrationsDir,
|
|
12597
|
+
async function rollback(migrationsDir, delimiter5) {
|
|
12559
12598
|
if (migrationsDir instanceof Map) {
|
|
12560
12599
|
const downFunctions = migrationsDir;
|
|
12561
12600
|
const migrations2 = await getLastBatchMigrations();
|
|
@@ -12574,7 +12613,7 @@ async function rollback(migrationsDir, delimiter4) {
|
|
|
12574
12613
|
return rolledBack2;
|
|
12575
12614
|
}
|
|
12576
12615
|
const dir = resolve5(migrationsDir ?? "migrations");
|
|
12577
|
-
const delim =
|
|
12616
|
+
const delim = delimiter5 ?? ";";
|
|
12578
12617
|
const db = getAdapter();
|
|
12579
12618
|
const migrations = await getLastBatchMigrations();
|
|
12580
12619
|
const rolledBack = [];
|
|
@@ -12625,12 +12664,12 @@ function parseSetTerm(statement) {
|
|
|
12625
12664
|
const m = statement.trim().match(SET_TERM_RE);
|
|
12626
12665
|
return m ? m[1] : null;
|
|
12627
12666
|
}
|
|
12628
|
-
function splitStatements(sql,
|
|
12667
|
+
function splitStatements(sql, delimiter5 = ";") {
|
|
12629
12668
|
sql = normalizeQuotes(sql);
|
|
12630
12669
|
const statements = [];
|
|
12631
12670
|
let current = "";
|
|
12632
12671
|
const n = sql.length;
|
|
12633
|
-
let dlen =
|
|
12672
|
+
let dlen = delimiter5.length;
|
|
12634
12673
|
let i = 0;
|
|
12635
12674
|
let inDollarBlock = false;
|
|
12636
12675
|
let inSlashBlock = false;
|
|
@@ -12699,15 +12738,15 @@ function splitStatements(sql, delimiter4 = ";") {
|
|
|
12699
12738
|
}
|
|
12700
12739
|
continue;
|
|
12701
12740
|
}
|
|
12702
|
-
if (dlen > 0 && sql.startsWith(
|
|
12741
|
+
if (dlen > 0 && sql.startsWith(delimiter5, i)) {
|
|
12703
12742
|
i += dlen;
|
|
12704
12743
|
const stmt2 = current.trim();
|
|
12705
12744
|
current = "";
|
|
12706
12745
|
if (stmt2) {
|
|
12707
12746
|
const newTerm = parseSetTerm(stmt2);
|
|
12708
12747
|
if (newTerm !== null) {
|
|
12709
|
-
|
|
12710
|
-
dlen =
|
|
12748
|
+
delimiter5 = newTerm;
|
|
12749
|
+
dlen = delimiter5.length;
|
|
12711
12750
|
} else {
|
|
12712
12751
|
statements.push(stmt2);
|
|
12713
12752
|
}
|
|
@@ -12746,7 +12785,7 @@ function warnUnprefixedMigrations(files) {
|
|
|
12746
12785
|
async function migrate(adapter, options) {
|
|
12747
12786
|
const db = adapter ?? getAdapter();
|
|
12748
12787
|
const dir = resolve5(options?.migrationsDir ?? "migrations");
|
|
12749
|
-
const
|
|
12788
|
+
const delimiter5 = options?.delimiter ?? ";";
|
|
12750
12789
|
const result = { applied: [], skipped: [], failed: [] };
|
|
12751
12790
|
if (!existsSync6(dir)) {
|
|
12752
12791
|
return result;
|
|
@@ -12789,7 +12828,7 @@ async function migrate(adapter, options) {
|
|
|
12789
12828
|
result.skipped.push(file);
|
|
12790
12829
|
continue;
|
|
12791
12830
|
}
|
|
12792
|
-
const statements = splitStatements(sqlContent,
|
|
12831
|
+
const statements = splitStatements(sqlContent, delimiter5);
|
|
12793
12832
|
try {
|
|
12794
12833
|
await adapterStartTransaction(db);
|
|
12795
12834
|
for (const stmt of statements) {
|
|
@@ -15418,7 +15457,7 @@ function mulberry32(seed) {
|
|
|
15418
15457
|
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
15419
15458
|
};
|
|
15420
15459
|
}
|
|
15421
|
-
var FIRST_NAMES, LAST_NAMES, DOMAINS, WORDS, CITIES, COUNTRIES, STREETS, JOB_TITLES, CURRENCIES, FakeData;
|
|
15460
|
+
var FIRST_NAMES, LAST_NAMES, DOMAINS, WORDS, CITIES, COUNTRIES, STREETS, JOB_TITLES, CURRENCIES, PRODUCT_ADJECTIVES, PRODUCT_NOUNS, FakeData;
|
|
15422
15461
|
var init_fakeData = __esm({
|
|
15423
15462
|
"../core/src/fakeData.ts"() {
|
|
15424
15463
|
"use strict";
|
|
@@ -15680,6 +15719,59 @@ var init_fakeData = __esm({
|
|
|
15680
15719
|
"ZAR",
|
|
15681
15720
|
"INR"
|
|
15682
15721
|
];
|
|
15722
|
+
PRODUCT_ADJECTIVES = [
|
|
15723
|
+
"Wireless",
|
|
15724
|
+
"Organic",
|
|
15725
|
+
"Premium",
|
|
15726
|
+
"Classic",
|
|
15727
|
+
"Eco",
|
|
15728
|
+
"Smart",
|
|
15729
|
+
"Portable",
|
|
15730
|
+
"Deluxe",
|
|
15731
|
+
"Compact",
|
|
15732
|
+
"Rustic",
|
|
15733
|
+
"Handcrafted",
|
|
15734
|
+
"Vintage",
|
|
15735
|
+
"Modern",
|
|
15736
|
+
"Ergonomic",
|
|
15737
|
+
"Stainless",
|
|
15738
|
+
"Bamboo",
|
|
15739
|
+
"Recycled",
|
|
15740
|
+
"Artisan",
|
|
15741
|
+
"Professional",
|
|
15742
|
+
"Ultra",
|
|
15743
|
+
"Insulated",
|
|
15744
|
+
"Lightweight",
|
|
15745
|
+
"Adjustable",
|
|
15746
|
+
"Foldable"
|
|
15747
|
+
];
|
|
15748
|
+
PRODUCT_NOUNS = [
|
|
15749
|
+
"Keyboard",
|
|
15750
|
+
"Coffee Beans",
|
|
15751
|
+
"Backpack",
|
|
15752
|
+
"Water Bottle",
|
|
15753
|
+
"Desk Lamp",
|
|
15754
|
+
"Headphones",
|
|
15755
|
+
"Notebook",
|
|
15756
|
+
"Sneakers",
|
|
15757
|
+
"Sunglasses",
|
|
15758
|
+
"Wallet",
|
|
15759
|
+
"Mug",
|
|
15760
|
+
"Chair",
|
|
15761
|
+
"Blender",
|
|
15762
|
+
"Speaker",
|
|
15763
|
+
"Charger",
|
|
15764
|
+
"Umbrella",
|
|
15765
|
+
"Toothbrush",
|
|
15766
|
+
"Jacket",
|
|
15767
|
+
"Watch",
|
|
15768
|
+
"Kettle",
|
|
15769
|
+
"Picture Frame",
|
|
15770
|
+
"Planter",
|
|
15771
|
+
"Cutlery Set",
|
|
15772
|
+
"Yoga Mat",
|
|
15773
|
+
"Phone Case"
|
|
15774
|
+
];
|
|
15683
15775
|
FakeData = class _FakeData {
|
|
15684
15776
|
rng;
|
|
15685
15777
|
seeded;
|
|
@@ -15751,6 +15843,16 @@ var init_fakeData = __esm({
|
|
|
15751
15843
|
jobTitle() {
|
|
15752
15844
|
return this.pick(JOB_TITLES);
|
|
15753
15845
|
}
|
|
15846
|
+
/**
|
|
15847
|
+
* A plausible product name, e.g. "Wireless Keyboard" or "Organic Coffee
|
|
15848
|
+
* Beans" — an adjective + noun from the product vocabulary. Deterministic
|
|
15849
|
+
* under a seed like every other generator (draws from the same instance
|
|
15850
|
+
* PRNG). Used to seed a generic `name` column on a product-ish table instead
|
|
15851
|
+
* of a person name (see the ORM FakeData `forField` heuristic).
|
|
15852
|
+
*/
|
|
15853
|
+
product() {
|
|
15854
|
+
return `${this.pick(PRODUCT_ADJECTIVES)} ${this.pick(PRODUCT_NOUNS)}`;
|
|
15855
|
+
}
|
|
15754
15856
|
paragraph(sentences = 4) {
|
|
15755
15857
|
const parts = [];
|
|
15756
15858
|
for (let i = 0; i < sentences; i++) {
|
|
@@ -15874,11 +15976,27 @@ var init_fakeData = __esm({
|
|
|
15874
15976
|
});
|
|
15875
15977
|
|
|
15876
15978
|
// ../orm/src/fakeData.ts
|
|
15877
|
-
|
|
15979
|
+
function isProductTable(table2) {
|
|
15980
|
+
const t = (table2 ?? "").toLowerCase();
|
|
15981
|
+
return PRODUCT_TABLE_HINTS.some((hint) => t.includes(hint));
|
|
15982
|
+
}
|
|
15983
|
+
var PRODUCT_TABLE_HINTS, FakeData2;
|
|
15878
15984
|
var init_fakeData2 = __esm({
|
|
15879
15985
|
"../orm/src/fakeData.ts"() {
|
|
15880
15986
|
"use strict";
|
|
15881
15987
|
init_fakeData();
|
|
15988
|
+
PRODUCT_TABLE_HINTS = [
|
|
15989
|
+
"product",
|
|
15990
|
+
"item",
|
|
15991
|
+
"catalog",
|
|
15992
|
+
"inventory",
|
|
15993
|
+
"goods",
|
|
15994
|
+
"merchandise",
|
|
15995
|
+
"sku",
|
|
15996
|
+
"listing",
|
|
15997
|
+
"stock",
|
|
15998
|
+
"ware"
|
|
15999
|
+
];
|
|
15882
16000
|
FakeData2 = class extends FakeData {
|
|
15883
16001
|
constructor(seed) {
|
|
15884
16002
|
super(seed);
|
|
@@ -15900,8 +16018,11 @@ var init_fakeData2 = __esm({
|
|
|
15900
16018
|
*
|
|
15901
16019
|
* @param fieldDef - An ORM FieldDefinition object
|
|
15902
16020
|
* @param columnName - Optional column name for heuristic matching (e.g. "email", "phone")
|
|
16021
|
+
* @param table - Optional table/model name. A generic `name`/`full_name`
|
|
16022
|
+
* column on a product-ish table (see {@link isProductTable}) gets a
|
|
16023
|
+
* product name; with no table context it stays a person name (back-compat).
|
|
15903
16024
|
*/
|
|
15904
|
-
forField(fieldDef, columnName) {
|
|
16025
|
+
forField(fieldDef, columnName, table2) {
|
|
15905
16026
|
if (fieldDef.primaryKey && fieldDef.autoIncrement) {
|
|
15906
16027
|
return void 0;
|
|
15907
16028
|
}
|
|
@@ -15911,7 +16032,9 @@ var init_fakeData2 = __esm({
|
|
|
15911
16032
|
const col = (columnName ?? "").toLowerCase();
|
|
15912
16033
|
if (col.includes("email")) return this.email();
|
|
15913
16034
|
if (col.includes("phone") || col.includes("mobile") || col.includes("tel")) return this.phone();
|
|
15914
|
-
if (col === "name" || col === "full_name" || col === "fullname")
|
|
16035
|
+
if (col === "name" || col === "full_name" || col === "fullname") {
|
|
16036
|
+
return isProductTable(table2) ? this.product() : this.name();
|
|
16037
|
+
}
|
|
15915
16038
|
if (col === "first_name" || col === "firstname") return this.firstName();
|
|
15916
16039
|
if (col === "last_name" || col === "lastname" || col === "surname") return this.lastName();
|
|
15917
16040
|
if (col.includes("address")) return this.address();
|
|
@@ -15998,7 +16121,7 @@ async function autoFieldMap(db, table2, fake = new FakeData2()) {
|
|
|
15998
16121
|
continue;
|
|
15999
16122
|
}
|
|
16000
16123
|
const fieldType = sqlTypeToFieldType(sqlType);
|
|
16001
|
-
fieldMap[name] = () => fake.forField({ type: fieldType }, name);
|
|
16124
|
+
fieldMap[name] = () => fake.forField({ type: fieldType }, name, table2);
|
|
16002
16125
|
}
|
|
16003
16126
|
return fieldMap;
|
|
16004
16127
|
}
|
|
@@ -16144,7 +16267,7 @@ async function seedOrm(ormClass, count = 10, overrides, seed, opts, fkPools) {
|
|
|
16144
16267
|
} else if (pools[name] && pools[name].length > 0) {
|
|
16145
16268
|
attrs[name] = fake.choice(pools[name]);
|
|
16146
16269
|
} else {
|
|
16147
|
-
attrs[name] = fake.forField(def, name);
|
|
16270
|
+
attrs[name] = fake.forField(def, name, modelName);
|
|
16148
16271
|
}
|
|
16149
16272
|
}
|
|
16150
16273
|
validateTypes(fields, attrs, modelName);
|
|
@@ -21179,14 +21302,14 @@ function extractBoundary(contentType) {
|
|
|
21179
21302
|
function parseMultipart(body, boundary) {
|
|
21180
21303
|
const fields = {};
|
|
21181
21304
|
const files = {};
|
|
21182
|
-
const
|
|
21305
|
+
const delimiter5 = Buffer.from(`--${boundary}`);
|
|
21183
21306
|
const closeDelimiter = Buffer.from(`--${boundary}--`);
|
|
21184
21307
|
const crlf = Buffer.from("\r\n");
|
|
21185
21308
|
const doubleCrlf = Buffer.from("\r\n\r\n");
|
|
21186
21309
|
let offset = 0;
|
|
21187
|
-
const firstIdx = bufferIndexOf(body,
|
|
21310
|
+
const firstIdx = bufferIndexOf(body, delimiter5, offset);
|
|
21188
21311
|
if (firstIdx === -1) return { fields, files };
|
|
21189
|
-
offset = firstIdx +
|
|
21312
|
+
offset = firstIdx + delimiter5.length;
|
|
21190
21313
|
if (body[offset] === 13 && body[offset + 1] === 10) {
|
|
21191
21314
|
offset += 2;
|
|
21192
21315
|
}
|
|
@@ -21195,7 +21318,7 @@ function parseMultipart(body, boundary) {
|
|
|
21195
21318
|
if (headersEnd === -1) break;
|
|
21196
21319
|
const headersStr = body.subarray(offset, headersEnd).toString("utf-8");
|
|
21197
21320
|
offset = headersEnd + doubleCrlf.length;
|
|
21198
|
-
const nextDelimIdx = bufferIndexOf(body,
|
|
21321
|
+
const nextDelimIdx = bufferIndexOf(body, delimiter5, offset);
|
|
21199
21322
|
if (nextDelimIdx === -1) break;
|
|
21200
21323
|
const contentEnd = nextDelimIdx - crlf.length;
|
|
21201
21324
|
const content = body.subarray(offset, contentEnd);
|
|
@@ -21218,7 +21341,7 @@ function parseMultipart(body, boundary) {
|
|
|
21218
21341
|
} else if (disposition.name) {
|
|
21219
21342
|
fields[disposition.name] = content.toString("utf-8");
|
|
21220
21343
|
}
|
|
21221
|
-
offset = nextDelimIdx +
|
|
21344
|
+
offset = nextDelimIdx + delimiter5.length;
|
|
21222
21345
|
if (body[offset] === 45 && body[offset + 1] === 45) {
|
|
21223
21346
|
break;
|
|
21224
21347
|
}
|
|
@@ -24391,6 +24514,7 @@ __export(generate_exports, {
|
|
|
24391
24514
|
parseEvery: () => parseEvery,
|
|
24392
24515
|
parseFields: () => parseFields,
|
|
24393
24516
|
pluralizeReserved: () => pluralizeReserved,
|
|
24517
|
+
resolveTable: () => resolveTable,
|
|
24394
24518
|
toPascal: () => toPascal,
|
|
24395
24519
|
toSnake: () => toSnake,
|
|
24396
24520
|
toTableName: () => toTableName
|
|
@@ -24433,12 +24557,35 @@ function toTableName(name) {
|
|
|
24433
24557
|
from: raw,
|
|
24434
24558
|
to: safe,
|
|
24435
24559
|
reason: `SQL reserved word '${raw}' would break CREATE TABLE`,
|
|
24436
|
-
override: `--table
|
|
24560
|
+
override: `--table-name <name> (table names interpolate unquoted; forcing a reserved name is yours to quote in raw SQL)`
|
|
24437
24561
|
});
|
|
24438
24562
|
return safe;
|
|
24439
24563
|
}
|
|
24440
24564
|
return raw;
|
|
24441
24565
|
}
|
|
24566
|
+
function resolveTable(name, flags, opts = {}) {
|
|
24567
|
+
const announce = opts.announce ?? false;
|
|
24568
|
+
const override = (flags ?? {})["table-name"];
|
|
24569
|
+
if (typeof override === "string" && override) {
|
|
24570
|
+
if (announce && SQL_RESERVED_TABLE_NAMES.has(toSnake(override))) {
|
|
24571
|
+
console.error(
|
|
24572
|
+
` ! table_name '${override}' is a SQL reserved word. Tina4 interpolates table names UNQUOTED, so the ORM's generated SQL will fail on it -- quote it yourself in raw SQL and migrations.`
|
|
24573
|
+
);
|
|
24574
|
+
}
|
|
24575
|
+
return override;
|
|
24576
|
+
}
|
|
24577
|
+
const bare = toSnake(name);
|
|
24578
|
+
if (!SQL_RESERVED_TABLE_NAMES.has(bare)) {
|
|
24579
|
+
return bare;
|
|
24580
|
+
}
|
|
24581
|
+
const table2 = toTableName(name);
|
|
24582
|
+
if (announce) {
|
|
24583
|
+
console.error(
|
|
24584
|
+
` \xB7 '${bare}' is a SQL reserved word; using table_name '${table2}' (Tina4 interpolates table names unquoted). Override with --table-name <name>.`
|
|
24585
|
+
);
|
|
24586
|
+
}
|
|
24587
|
+
return table2;
|
|
24588
|
+
}
|
|
24442
24589
|
function resetResolution(target, input, opts) {
|
|
24443
24590
|
__resolution.target = target;
|
|
24444
24591
|
__resolution.input = input;
|
|
@@ -24537,10 +24684,11 @@ function printResolution() {
|
|
|
24537
24684
|
lines.push(` migration ${b.migration_path}`);
|
|
24538
24685
|
}
|
|
24539
24686
|
const reserved = b.transformations.find((t) => t.kind === "reserved_word_pluralize");
|
|
24540
|
-
if (reserved && reserved.from
|
|
24687
|
+
if (reserved && reserved.from) {
|
|
24541
24688
|
lines.push("");
|
|
24542
|
-
lines.push(` To
|
|
24543
|
-
lines.push(` tina4nodejs generate ${__resolution.target} ${__resolution.input.name}
|
|
24689
|
+
lines.push(` To set the table name yourself:`);
|
|
24690
|
+
lines.push(` tina4nodejs generate ${__resolution.target} ${__resolution.input.name} --table-name <name>`);
|
|
24691
|
+
lines.push(` Tina4 interpolates table names unquoted; if you force the reserved '${reserved.from}', you own the quoting in raw SQL.`);
|
|
24544
24692
|
}
|
|
24545
24693
|
if (b.test_paths && b.test_paths.length > 0) {
|
|
24546
24694
|
lines.push("");
|
|
@@ -24676,6 +24824,7 @@ async function generate(what, name, extraArgs = []) {
|
|
|
24676
24824
|
console.error(" Usage: tina4nodejs generate <what> <name> [options]");
|
|
24677
24825
|
console.error(` Generators: ${GENERATOR_LIST}`);
|
|
24678
24826
|
console.error(' Options: --fields "name:string,price:float" --model ModelName');
|
|
24827
|
+
console.error(" --table-name <name> force the model's table name (else derived; reserved words auto-pluralise)");
|
|
24679
24828
|
console.error(" --public open a route's writes (default: secure)");
|
|
24680
24829
|
console.error(' --every 5m | --cron "\u2026" service schedule');
|
|
24681
24830
|
console.error(" --json emit machine-readable resolution envelope on stdout");
|
|
@@ -24726,7 +24875,7 @@ async function generateProgrammatic(what, name, extraArgs = []) {
|
|
|
24726
24875
|
}
|
|
24727
24876
|
function generateModel(name, flags, emitTest = true) {
|
|
24728
24877
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
24729
|
-
const table2 =
|
|
24878
|
+
const table2 = resolveTable(name, flags, { announce: true });
|
|
24730
24879
|
const dir = resolve11("src/models");
|
|
24731
24880
|
ensureDir(dir);
|
|
24732
24881
|
const path8 = join21(dir, `${name}.ts`);
|
|
@@ -24777,7 +24926,7 @@ function generateRoute(name, flags, emitTest = true) {
|
|
|
24777
24926
|
if (__resolution.target === "route") {
|
|
24778
24927
|
setResolutionField("file_path", `src/routes/api/${routePath}/get.ts`);
|
|
24779
24928
|
}
|
|
24780
|
-
const table2 = model ?
|
|
24929
|
+
const table2 = model ? resolveTable(model, flags, { announce: false }) : "";
|
|
24781
24930
|
const modelImportBase = model ? `import ${model} from "../../../models/${model}.js";
|
|
24782
24931
|
` : "";
|
|
24783
24932
|
const modelImportId = model ? `import ${model} from "../../../../models/${model}.js";
|
|
@@ -24998,7 +25147,7 @@ ${aiFill(`delete_${singular}`, {
|
|
|
24998
25147
|
}
|
|
24999
25148
|
}
|
|
25000
25149
|
function generateCrud(name, flags) {
|
|
25001
|
-
const table2 =
|
|
25150
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25002
25151
|
const routeName = toPlural(table2);
|
|
25003
25152
|
const isPublic = Boolean(flags.public);
|
|
25004
25153
|
if (!__resolution.jsonMode) console.log(`
|
|
@@ -25025,7 +25174,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
|
|
|
25025
25174
|
table2 = tableOverride;
|
|
25026
25175
|
} else {
|
|
25027
25176
|
const raw = name.replace(/^create_/, "").replace(/^add_/, "").replace(/^drop_/, "");
|
|
25028
|
-
table2 =
|
|
25177
|
+
table2 = resolveTable(raw, flags, { announce: false });
|
|
25029
25178
|
}
|
|
25030
25179
|
if (__resolution.target === "migration") {
|
|
25031
25180
|
setResolutionField("table_name", table2);
|
|
@@ -25048,7 +25197,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
|
|
|
25048
25197
|
const defaultClause = info.defaultVal !== "NULL" ? ` DEFAULT ${info.defaultVal}` : "";
|
|
25049
25198
|
colLines.push(` ${fname} ${info.sql}${defaultClause}`);
|
|
25050
25199
|
}
|
|
25051
|
-
colLines.push(" created_at
|
|
25200
|
+
colLines.push(" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP");
|
|
25052
25201
|
upSql = `CREATE TABLE IF NOT EXISTS ${table2} (
|
|
25053
25202
|
-- tina4:edit add columns beyond id + created_at
|
|
25054
25203
|
${colLines.join(",\n")}
|
|
@@ -25251,7 +25400,7 @@ void test${titleName};
|
|
|
25251
25400
|
}
|
|
25252
25401
|
function generateForm(name, flags) {
|
|
25253
25402
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
25254
|
-
const table2 =
|
|
25403
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25255
25404
|
const routeName = toPlural(table2);
|
|
25256
25405
|
const inputTypes = {
|
|
25257
25406
|
string: "text",
|
|
@@ -25317,7 +25466,7 @@ function generateForm(name, flags) {
|
|
|
25317
25466
|
}
|
|
25318
25467
|
function generateView(name, flags) {
|
|
25319
25468
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
25320
|
-
const table2 =
|
|
25469
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25321
25470
|
const routeName = toPlural(table2);
|
|
25322
25471
|
const cols = fields.map(([f]) => f);
|
|
25323
25472
|
const dir = resolve11("src/templates/pages");
|
|
@@ -25672,8 +25821,8 @@ ${rules} validator.required("name"); // starter rule (matches the model's def
|
|
|
25672
25821
|
writeFileSafe(path8, content);
|
|
25673
25822
|
emitValidatorTest(name, toSnake(name), toPascal(name));
|
|
25674
25823
|
}
|
|
25675
|
-
function generateSeeder(name,
|
|
25676
|
-
const table2 =
|
|
25824
|
+
function generateSeeder(name, flags) {
|
|
25825
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25677
25826
|
const dir = resolve11("src/seeds");
|
|
25678
25827
|
ensureDir(dir);
|
|
25679
25828
|
const path8 = join21(dir, `${table2}_seeder.ts`);
|
|
@@ -26191,8 +26340,8 @@ var init_generate = __esm({
|
|
|
26191
26340
|
"src/commands/generate.ts"() {
|
|
26192
26341
|
"use strict";
|
|
26193
26342
|
FIELD_TYPE_MAP = {
|
|
26194
|
-
string: { orm: '"string"', sql: "
|
|
26195
|
-
str: { orm: '"string"', sql: "
|
|
26343
|
+
string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
|
|
26344
|
+
str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
|
|
26196
26345
|
int: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
|
|
26197
26346
|
integer: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
|
|
26198
26347
|
float: { orm: '"number"', sql: "REAL", defaultVal: "0" },
|
|
@@ -26202,7 +26351,7 @@ var init_generate = __esm({
|
|
|
26202
26351
|
bool: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
|
|
26203
26352
|
boolean: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
|
|
26204
26353
|
text: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
|
|
26205
|
-
datetime: { orm: '"datetime"', sql: "
|
|
26354
|
+
datetime: { orm: '"datetime"', sql: "TIMESTAMP", defaultVal: "NULL" },
|
|
26206
26355
|
blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" }
|
|
26207
26356
|
};
|
|
26208
26357
|
SQL_RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -26298,7 +26447,7 @@ var init_generate = __esm({
|
|
|
26298
26447
|
TINA4_EDIT_MARKER = /^\s*(?:\/\/|--|\{#|#)\s*tina4:edit\s+(.+?)(?:\s*#\})?\s*$/;
|
|
26299
26448
|
DEFAULT_FIELDS = [["name", "string"]];
|
|
26300
26449
|
GENERATORS = {
|
|
26301
|
-
model: { handler: generateModel, usage: '<Name> [--fields "name:string,price:float"]', summary: "ORM model + matching migration" },
|
|
26450
|
+
model: { handler: generateModel, usage: '<Name> [--fields "name:string,price:float"] [--table-name <name>]', summary: "ORM model + matching migration" },
|
|
26302
26451
|
route: { handler: generateRoute, usage: "<name> [--model Name] [--public]", summary: "CRUD route file, secure by default (--public opens writes)" },
|
|
26303
26452
|
crud: { handler: generateCrud, usage: '<Name> [--fields "..."] [--public]', summary: "Model + migration + routes + form + view + test" },
|
|
26304
26453
|
migration: { handler: (n, f) => generateMigration(n, f, void 0, void 0, !f["no-test"]), usage: "<description>", summary: "Timestamped migration file (UP/DOWN)" },
|
|
@@ -36995,23 +37144,23 @@ var init_devAdmin = __esm({
|
|
|
36995
37144
|
});
|
|
36996
37145
|
};
|
|
36997
37146
|
handleDevAdminJs = async (_req, res) => {
|
|
36998
|
-
const { readFileSync:
|
|
36999
|
-
const { dirname:
|
|
37147
|
+
const { readFileSync: readFileSync30, existsSync: existsSync37 } = await import("node:fs");
|
|
37148
|
+
const { dirname: dirname18, join: join40, resolve: resolve29 } = await import("node:path");
|
|
37000
37149
|
const { fileURLToPath: fileURLToPath10 } = await import("node:url");
|
|
37001
|
-
const dir =
|
|
37150
|
+
const dir = dirname18(fileURLToPath10(import.meta.url));
|
|
37002
37151
|
const candidates = [
|
|
37003
|
-
|
|
37152
|
+
join40(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
37004
37153
|
// src/../public/js/
|
|
37005
|
-
|
|
37154
|
+
join40(dir, "..", "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
37006
37155
|
// deeper nesting
|
|
37007
37156
|
resolve29(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin.min.js"),
|
|
37008
37157
|
resolve29(process.cwd(), "public", "js", "tina4-dev-admin.min.js")
|
|
37009
37158
|
// project public/
|
|
37010
37159
|
];
|
|
37011
37160
|
for (const jsPath of candidates) {
|
|
37012
|
-
if (
|
|
37161
|
+
if (existsSync37(jsPath)) {
|
|
37013
37162
|
try {
|
|
37014
|
-
const content =
|
|
37163
|
+
const content = readFileSync30(jsPath, "utf-8");
|
|
37015
37164
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
37016
37165
|
res.raw.end(content);
|
|
37017
37166
|
return;
|
|
@@ -40888,23 +41037,23 @@ function guessContentType(filename) {
|
|
|
40888
41037
|
}
|
|
40889
41038
|
function buildMultipartBody(boundary, fieldName, filename, fileContent, contentType, extraFields) {
|
|
40890
41039
|
const crlf = "\r\n";
|
|
40891
|
-
const
|
|
41040
|
+
const delimiter5 = `--${boundary}`;
|
|
40892
41041
|
const parts = [];
|
|
40893
41042
|
if (extraFields) {
|
|
40894
41043
|
for (const [key, value] of Object.entries(extraFields)) {
|
|
40895
|
-
parts.push(Buffer.from(
|
|
41044
|
+
parts.push(Buffer.from(delimiter5 + crlf, "utf-8"));
|
|
40896
41045
|
parts.push(Buffer.from(`Content-Disposition: form-data; name="${key}"` + crlf + crlf, "utf-8"));
|
|
40897
41046
|
parts.push(Buffer.from(String(value) + crlf, "utf-8"));
|
|
40898
41047
|
}
|
|
40899
41048
|
}
|
|
40900
|
-
parts.push(Buffer.from(
|
|
41049
|
+
parts.push(Buffer.from(delimiter5 + crlf, "utf-8"));
|
|
40901
41050
|
parts.push(
|
|
40902
41051
|
Buffer.from(`Content-Disposition: form-data; name="${fieldName}"; filename="${filename}"` + crlf, "utf-8")
|
|
40903
41052
|
);
|
|
40904
41053
|
parts.push(Buffer.from(`Content-Type: ${contentType}` + crlf + crlf, "utf-8"));
|
|
40905
41054
|
parts.push(fileContent);
|
|
40906
41055
|
parts.push(Buffer.from(crlf, "utf-8"));
|
|
40907
|
-
parts.push(Buffer.from(
|
|
41056
|
+
parts.push(Buffer.from(delimiter5 + "--" + crlf, "utf-8"));
|
|
40908
41057
|
return Buffer.concat(parts);
|
|
40909
41058
|
}
|
|
40910
41059
|
async function* parseLineStream(chunks) {
|
|
@@ -47461,8 +47610,8 @@ async function runTests(testPath) {
|
|
|
47461
47610
|
console.log(` Found ${testFiles.length} test file(s)
|
|
47462
47611
|
`);
|
|
47463
47612
|
for (const file of testFiles) {
|
|
47464
|
-
const
|
|
47465
|
-
console.log(` Running: ${
|
|
47613
|
+
const relative12 = file.replace(cwd + "/", "");
|
|
47614
|
+
console.log(` Running: ${relative12}`);
|
|
47466
47615
|
try {
|
|
47467
47616
|
execSync3(`npx tsx "${file}"`, { cwd, stdio: "inherit" });
|
|
47468
47617
|
} catch {
|
|
@@ -47472,19 +47621,233 @@ async function runTests(testPath) {
|
|
|
47472
47621
|
process.exit(inlineFailed || fileFailed ? 1 : 0);
|
|
47473
47622
|
}
|
|
47474
47623
|
|
|
47624
|
+
// src/commands/lint.ts
|
|
47625
|
+
import { existsSync as existsSync33, readdirSync as readdirSync20, readFileSync as readFileSync28, statSync as statSync20, writeFileSync as writeFileSync20 } from "node:fs";
|
|
47626
|
+
import { createRequire as createRequire9 } from "node:module";
|
|
47627
|
+
import { delimiter as delimiter2, dirname as dirname16, join as join35, relative as relative11 } from "node:path";
|
|
47628
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
47629
|
+
var LINT_EXTENSIONS = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
|
|
47630
|
+
var JS_EXTENSIONS = [".js", ".mjs", ".cjs"];
|
|
47631
|
+
var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
|
|
47632
|
+
function isLintFile(name) {
|
|
47633
|
+
if (name.endsWith(".d.ts")) return false;
|
|
47634
|
+
return LINT_EXTENSIONS.some((ext) => name.endsWith(ext));
|
|
47635
|
+
}
|
|
47636
|
+
function walkSource2(dir, out) {
|
|
47637
|
+
let entries;
|
|
47638
|
+
try {
|
|
47639
|
+
entries = readdirSync20(dir);
|
|
47640
|
+
} catch {
|
|
47641
|
+
return;
|
|
47642
|
+
}
|
|
47643
|
+
for (const name of entries) {
|
|
47644
|
+
if (SKIP_DIRS3.has(name)) continue;
|
|
47645
|
+
const full = join35(dir, name);
|
|
47646
|
+
let st;
|
|
47647
|
+
try {
|
|
47648
|
+
st = statSync20(full);
|
|
47649
|
+
} catch {
|
|
47650
|
+
continue;
|
|
47651
|
+
}
|
|
47652
|
+
if (st.isDirectory()) {
|
|
47653
|
+
walkSource2(full, out);
|
|
47654
|
+
} else if (isLintFile(name)) {
|
|
47655
|
+
out.push(full);
|
|
47656
|
+
}
|
|
47657
|
+
}
|
|
47658
|
+
}
|
|
47659
|
+
function collectAppFiles(cwd) {
|
|
47660
|
+
const files = [];
|
|
47661
|
+
const srcDir = join35(cwd, "src");
|
|
47662
|
+
try {
|
|
47663
|
+
if (statSync20(srcDir).isDirectory()) walkSource2(srcDir, files);
|
|
47664
|
+
} catch {
|
|
47665
|
+
}
|
|
47666
|
+
for (const entry of ["app.ts", "app.mts", "app.js", "app.mjs"]) {
|
|
47667
|
+
const p = join35(cwd, entry);
|
|
47668
|
+
try {
|
|
47669
|
+
if (statSync20(p).isFile()) files.push(p);
|
|
47670
|
+
} catch {
|
|
47671
|
+
}
|
|
47672
|
+
}
|
|
47673
|
+
return files;
|
|
47674
|
+
}
|
|
47675
|
+
var ESLINT_FLAT_CONFIGS = [
|
|
47676
|
+
"eslint.config.js",
|
|
47677
|
+
"eslint.config.mjs",
|
|
47678
|
+
"eslint.config.cjs",
|
|
47679
|
+
"eslint.config.ts",
|
|
47680
|
+
"eslint.config.mts",
|
|
47681
|
+
"eslint.config.cts"
|
|
47682
|
+
];
|
|
47683
|
+
var ESLINT_SCAFFOLD_FILE = "eslint.config.js";
|
|
47684
|
+
var ESLINT_SCAFFOLD = `import js from "@eslint/js";
|
|
47685
|
+
import tseslint from "typescript-eslint";
|
|
47686
|
+
export default [js.configs.recommended, ...tseslint.configs.recommended];
|
|
47687
|
+
`;
|
|
47688
|
+
function findEslintConfig(cwd) {
|
|
47689
|
+
for (const name of ESLINT_FLAT_CONFIGS) {
|
|
47690
|
+
const p = join35(cwd, name);
|
|
47691
|
+
if (existsSync33(p)) return p;
|
|
47692
|
+
}
|
|
47693
|
+
return null;
|
|
47694
|
+
}
|
|
47695
|
+
function resolvePackageBin(cwd, pkg, binRelative) {
|
|
47696
|
+
const direct = join35(cwd, "node_modules", pkg, binRelative);
|
|
47697
|
+
if (existsSync33(direct)) return direct;
|
|
47698
|
+
const require3 = createRequire9(join35(cwd, "package.json"));
|
|
47699
|
+
let entry;
|
|
47700
|
+
try {
|
|
47701
|
+
entry = require3.resolve(pkg);
|
|
47702
|
+
} catch {
|
|
47703
|
+
return null;
|
|
47704
|
+
}
|
|
47705
|
+
let dir = dirname16(entry);
|
|
47706
|
+
for (let i = 0; i < 8; i++) {
|
|
47707
|
+
const pkgJson = join35(dir, "package.json");
|
|
47708
|
+
if (existsSync33(pkgJson)) {
|
|
47709
|
+
try {
|
|
47710
|
+
if (JSON.parse(readFileSync28(pkgJson, "utf-8")).name === pkg) {
|
|
47711
|
+
const bin = join35(dir, binRelative);
|
|
47712
|
+
return existsSync33(bin) ? bin : null;
|
|
47713
|
+
}
|
|
47714
|
+
} catch {
|
|
47715
|
+
}
|
|
47716
|
+
}
|
|
47717
|
+
const parent = dirname16(dir);
|
|
47718
|
+
if (parent === dir) break;
|
|
47719
|
+
dir = parent;
|
|
47720
|
+
}
|
|
47721
|
+
return null;
|
|
47722
|
+
}
|
|
47723
|
+
function resolveEslintBin(cwd) {
|
|
47724
|
+
return resolvePackageBin(cwd, "eslint", "bin/eslint.js");
|
|
47725
|
+
}
|
|
47726
|
+
function hasPackage(cwd, pkg) {
|
|
47727
|
+
return existsSync33(join35(cwd, "node_modules", ...pkg.split("/"), "package.json"));
|
|
47728
|
+
}
|
|
47729
|
+
function resolveNpm() {
|
|
47730
|
+
const windows = process.platform === "win32";
|
|
47731
|
+
const names = windows ? ["npm.cmd", "npm.exe", "npm"] : ["npm"];
|
|
47732
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter2)) {
|
|
47733
|
+
if (!dir) continue;
|
|
47734
|
+
for (const name of names) {
|
|
47735
|
+
const candidate = join35(dir, name);
|
|
47736
|
+
try {
|
|
47737
|
+
if (statSync20(candidate).isFile()) return candidate;
|
|
47738
|
+
} catch {
|
|
47739
|
+
}
|
|
47740
|
+
}
|
|
47741
|
+
}
|
|
47742
|
+
return null;
|
|
47743
|
+
}
|
|
47744
|
+
function runLint(args) {
|
|
47745
|
+
const fix = args.includes("--fix");
|
|
47746
|
+
const noInstall = args.includes("--no-install");
|
|
47747
|
+
const cwd = process.cwd();
|
|
47748
|
+
const files = collectAppFiles(cwd);
|
|
47749
|
+
if (files.length === 0) {
|
|
47750
|
+
console.log(" lint: nothing to lint (no src/ or app.ts).");
|
|
47751
|
+
process.exit(0);
|
|
47752
|
+
}
|
|
47753
|
+
let eslintBin = resolveEslintBin(cwd);
|
|
47754
|
+
let eslintConfig = findEslintConfig(cwd);
|
|
47755
|
+
if (!noInstall && (!eslintBin || !eslintConfig)) {
|
|
47756
|
+
const npm = resolveNpm();
|
|
47757
|
+
if (!npm) {
|
|
47758
|
+
console.log(" \xB7 npm not found \u2014 using the zero-dependency baseline.");
|
|
47759
|
+
} else {
|
|
47760
|
+
if (!eslintBin) {
|
|
47761
|
+
console.log(" \xB7 installing eslint (npm i -D eslint @eslint/js typescript-eslint)...");
|
|
47762
|
+
const rc = spawnSync3(npm, ["install", "-D", "eslint", "@eslint/js", "typescript-eslint"], {
|
|
47763
|
+
cwd,
|
|
47764
|
+
stdio: "inherit"
|
|
47765
|
+
}).status;
|
|
47766
|
+
if (rc === 0) {
|
|
47767
|
+
eslintBin = resolveEslintBin(cwd);
|
|
47768
|
+
} else {
|
|
47769
|
+
console.log(" \xB7 could not install eslint \u2014 using the zero-dependency baseline.");
|
|
47770
|
+
}
|
|
47771
|
+
}
|
|
47772
|
+
if (eslintBin && !eslintConfig && hasPackage(cwd, "@eslint/js") && hasPackage(cwd, "typescript-eslint")) {
|
|
47773
|
+
const scaffold = join35(cwd, ESLINT_SCAFFOLD_FILE);
|
|
47774
|
+
try {
|
|
47775
|
+
writeFileSync20(scaffold, ESLINT_SCAFFOLD, "utf-8");
|
|
47776
|
+
eslintConfig = scaffold;
|
|
47777
|
+
console.log(` \xB7 scaffolded ${ESLINT_SCAFFOLD_FILE} (@eslint/js + typescript-eslint recommended).`);
|
|
47778
|
+
} catch (err) {
|
|
47779
|
+
console.log(
|
|
47780
|
+
` \xB7 could not scaffold ${ESLINT_SCAFFOLD_FILE} (${err instanceof Error ? err.message : String(err)}).`
|
|
47781
|
+
);
|
|
47782
|
+
}
|
|
47783
|
+
}
|
|
47784
|
+
}
|
|
47785
|
+
}
|
|
47786
|
+
if (eslintBin && eslintConfig) {
|
|
47787
|
+
const label = fix ? "eslint --fix" : "eslint";
|
|
47788
|
+
const eslintArgs = [eslintBin, ...files, ...fix ? ["--fix"] : []];
|
|
47789
|
+
const code = spawnSync3(process.execPath, eslintArgs, { cwd, stdio: "inherit" }).status ?? 1;
|
|
47790
|
+
if (code !== 0) {
|
|
47791
|
+
console.log(` \u2717 lint failed \u2014 ${files.length} file(s) [${label}]`);
|
|
47792
|
+
process.exit(1);
|
|
47793
|
+
}
|
|
47794
|
+
console.log(` \u2713 lint clean \u2014 ${files.length} file(s) [${label}]`);
|
|
47795
|
+
process.exit(0);
|
|
47796
|
+
}
|
|
47797
|
+
if (fix) {
|
|
47798
|
+
console.log(" \xB7 --fix needs eslint \u2014 the baseline check has no autofix.");
|
|
47799
|
+
}
|
|
47800
|
+
const hasTsconfig = existsSync33(join35(cwd, "tsconfig.json"));
|
|
47801
|
+
const tscBin = hasTsconfig ? resolvePackageBin(cwd, "typescript", "bin/tsc") : null;
|
|
47802
|
+
if (tscBin) {
|
|
47803
|
+
const code = spawnSync3(process.execPath, [tscBin, "--noEmit"], { cwd, stdio: "inherit" }).status ?? 1;
|
|
47804
|
+
if (code !== 0) {
|
|
47805
|
+
console.log(` \u2717 lint failed \u2014 ${files.length} file(s) [tsc]`);
|
|
47806
|
+
process.exit(1);
|
|
47807
|
+
}
|
|
47808
|
+
console.log(` \u2713 lint clean \u2014 ${files.length} file(s) [tsc]`);
|
|
47809
|
+
process.exit(0);
|
|
47810
|
+
}
|
|
47811
|
+
const jsFiles = files.filter((f) => JS_EXTENSIONS.some((ext) => f.endsWith(ext)));
|
|
47812
|
+
if (jsFiles.length === 0) {
|
|
47813
|
+
console.log(
|
|
47814
|
+
" lint: no JavaScript files to check \u2014 add tsconfig.json + typescript to type-check .ts files."
|
|
47815
|
+
);
|
|
47816
|
+
process.exit(0);
|
|
47817
|
+
}
|
|
47818
|
+
let syntaxErrors = 0;
|
|
47819
|
+
for (const file of jsFiles) {
|
|
47820
|
+
const result = spawnSync3(process.execPath, ["--check", file], { cwd, encoding: "utf-8" });
|
|
47821
|
+
if ((result.status ?? 1) !== 0) {
|
|
47822
|
+
const stderr = (result.stderr || "").trim();
|
|
47823
|
+
const detail = stderr.split("\n").reverse().find((line) => line.includes("Error:")) || "syntax error";
|
|
47824
|
+
console.log(` \u2717 ${relative11(cwd, file)}: ${detail.trim()}`);
|
|
47825
|
+
syntaxErrors++;
|
|
47826
|
+
}
|
|
47827
|
+
}
|
|
47828
|
+
if (syntaxErrors > 0) {
|
|
47829
|
+
console.log(
|
|
47830
|
+
` \u2717 lint failed \u2014 ${syntaxErrors} syntax error(s) in ${jsFiles.length} file(s) [node --check]`
|
|
47831
|
+
);
|
|
47832
|
+
process.exit(1);
|
|
47833
|
+
}
|
|
47834
|
+
console.log(` \u2713 lint clean \u2014 ${jsFiles.length} file(s) [node --check]`);
|
|
47835
|
+
process.exit(0);
|
|
47836
|
+
}
|
|
47837
|
+
|
|
47475
47838
|
// src/bin.ts
|
|
47476
47839
|
init_generate();
|
|
47477
47840
|
|
|
47478
47841
|
// src/commands/seed.ts
|
|
47479
|
-
import { existsSync as
|
|
47480
|
-
import { resolve as resolve28, join as
|
|
47842
|
+
import { existsSync as existsSync34, readdirSync as readdirSync21 } from "node:fs";
|
|
47843
|
+
import { resolve as resolve28, join as join36 } from "node:path";
|
|
47481
47844
|
import { execSync as execSync4 } from "node:child_process";
|
|
47482
47845
|
async function runSeeds(seedPath) {
|
|
47483
47846
|
const cwd = process.cwd();
|
|
47484
47847
|
const seedDir = resolve28(cwd, "src/seeds");
|
|
47485
47848
|
if (seedPath) {
|
|
47486
47849
|
const file = resolve28(seedPath);
|
|
47487
|
-
if (!
|
|
47850
|
+
if (!existsSync34(file)) {
|
|
47488
47851
|
console.error(` Error: Seed file not found: ${seedPath}`);
|
|
47489
47852
|
process.exit(1);
|
|
47490
47853
|
}
|
|
@@ -47497,14 +47860,14 @@ async function runSeeds(seedPath) {
|
|
|
47497
47860
|
}
|
|
47498
47861
|
return;
|
|
47499
47862
|
}
|
|
47500
|
-
if (!
|
|
47863
|
+
if (!existsSync34(seedDir)) {
|
|
47501
47864
|
console.log(" No seeds directory found.");
|
|
47502
47865
|
console.log(" Create seed files in src/seeds/ (e.g. src/seeds/001-users.ts)");
|
|
47503
47866
|
return;
|
|
47504
47867
|
}
|
|
47505
47868
|
let seedFiles;
|
|
47506
47869
|
try {
|
|
47507
|
-
seedFiles =
|
|
47870
|
+
seedFiles = readdirSync21(seedDir).filter((f) => f.endsWith(".ts")).sort().map((f) => join36(seedDir, f));
|
|
47508
47871
|
} catch {
|
|
47509
47872
|
console.log(" Could not read src/seeds/ directory.");
|
|
47510
47873
|
return;
|
|
@@ -47517,8 +47880,8 @@ async function runSeeds(seedPath) {
|
|
|
47517
47880
|
`);
|
|
47518
47881
|
let failed = false;
|
|
47519
47882
|
for (const file of seedFiles) {
|
|
47520
|
-
const
|
|
47521
|
-
console.log(` Seeding: ${
|
|
47883
|
+
const relative12 = file.replace(cwd + "/", "");
|
|
47884
|
+
console.log(` Seeding: ${relative12}`);
|
|
47522
47885
|
try {
|
|
47523
47886
|
execSync4(`npx tsx "${file}"`, { cwd, stdio: "inherit" });
|
|
47524
47887
|
} catch {
|
|
@@ -47535,8 +47898,8 @@ async function runSeeds(seedPath) {
|
|
|
47535
47898
|
// src/commands/queue.ts
|
|
47536
47899
|
init_dotenv();
|
|
47537
47900
|
init_queue();
|
|
47538
|
-
import { readdirSync as
|
|
47539
|
-
import { extname as extname8, join as
|
|
47901
|
+
import { readdirSync as readdirSync22, statSync as statSync21 } from "node:fs";
|
|
47902
|
+
import { extname as extname8, join as join37 } from "node:path";
|
|
47540
47903
|
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
47541
47904
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["once", "json"]);
|
|
47542
47905
|
function parseFlags(args) {
|
|
@@ -47567,8 +47930,8 @@ function parseFlags(args) {
|
|
|
47567
47930
|
async function resolveQueueHandler(servicesDir, topic) {
|
|
47568
47931
|
let entries;
|
|
47569
47932
|
try {
|
|
47570
|
-
if (!
|
|
47571
|
-
entries =
|
|
47933
|
+
if (!statSync21(servicesDir).isDirectory()) return null;
|
|
47934
|
+
entries = readdirSync22(servicesDir);
|
|
47572
47935
|
} catch {
|
|
47573
47936
|
return null;
|
|
47574
47937
|
}
|
|
@@ -47576,9 +47939,9 @@ async function resolveQueueHandler(servicesDir, topic) {
|
|
|
47576
47939
|
if (entry.startsWith("_")) continue;
|
|
47577
47940
|
const ext = extname8(entry);
|
|
47578
47941
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
47579
|
-
const fullPath =
|
|
47942
|
+
const fullPath = join37(servicesDir, entry);
|
|
47580
47943
|
try {
|
|
47581
|
-
if (!
|
|
47944
|
+
if (!statSync21(fullPath).isFile()) continue;
|
|
47582
47945
|
const mod = await import(pathToFileURL3(fullPath).href);
|
|
47583
47946
|
const config = mod.default ?? mod;
|
|
47584
47947
|
if (config && typeof config === "object" && config.topic === topic && typeof config.handle === "function") {
|
|
@@ -47728,9 +48091,9 @@ async function queueCommand(args = []) {
|
|
|
47728
48091
|
}
|
|
47729
48092
|
|
|
47730
48093
|
// src/commands/build.ts
|
|
47731
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as
|
|
47732
|
-
import { basename as basename8, delimiter as
|
|
47733
|
-
import { spawnSync as
|
|
48094
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync35, statSync as statSync22 } from "node:fs";
|
|
48095
|
+
import { basename as basename8, delimiter as delimiter3, join as join38 } from "node:path";
|
|
48096
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
47734
48097
|
function parseFlags2(args) {
|
|
47735
48098
|
const flags = {};
|
|
47736
48099
|
let i = 0;
|
|
@@ -47755,13 +48118,13 @@ function whichDocker() {
|
|
|
47755
48118
|
const pathValue = process.env.PATH || process.env.Path || "";
|
|
47756
48119
|
if (!pathValue) return null;
|
|
47757
48120
|
const exts = process.platform === "win32" ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";") : [""];
|
|
47758
|
-
for (const dir of pathValue.split(
|
|
48121
|
+
for (const dir of pathValue.split(delimiter3)) {
|
|
47759
48122
|
if (!dir) continue;
|
|
47760
48123
|
for (const ext of exts) {
|
|
47761
|
-
const candidate =
|
|
48124
|
+
const candidate = join38(dir, `docker${ext}`);
|
|
47762
48125
|
try {
|
|
47763
48126
|
accessSync2(candidate, constants2.X_OK);
|
|
47764
|
-
if (
|
|
48127
|
+
if (statSync22(candidate).isFile()) return candidate;
|
|
47765
48128
|
} catch {
|
|
47766
48129
|
}
|
|
47767
48130
|
}
|
|
@@ -47776,7 +48139,7 @@ function buildImage(args) {
|
|
|
47776
48139
|
tag = `${dirName || "tina4app"}:latest`;
|
|
47777
48140
|
}
|
|
47778
48141
|
const dockerfile = typeof flags.file === "string" && flags.file ? flags.file : "Dockerfile";
|
|
47779
|
-
if (!
|
|
48142
|
+
if (!existsSync35(dockerfile) || !statSync22(dockerfile).isFile()) {
|
|
47780
48143
|
console.log(` \u2717 No ${dockerfile} found.`);
|
|
47781
48144
|
console.log(" A Tina4 app deploys as a container. Scaffold a Dockerfile first:");
|
|
47782
48145
|
console.log(" tina4 deploy docker (or: tina4nodejs init)");
|
|
@@ -47790,7 +48153,7 @@ function buildImage(args) {
|
|
|
47790
48153
|
process.exit(1);
|
|
47791
48154
|
}
|
|
47792
48155
|
console.log(` Building image ${tag} from ${dockerfile} ...`);
|
|
47793
|
-
const result =
|
|
48156
|
+
const result = spawnSync4(docker, ["build", "-t", tag, "-f", dockerfile, "."], {
|
|
47794
48157
|
stdio: "inherit"
|
|
47795
48158
|
});
|
|
47796
48159
|
const code = result.status ?? 1;
|
|
@@ -47803,22 +48166,22 @@ function buildImage(args) {
|
|
|
47803
48166
|
}
|
|
47804
48167
|
|
|
47805
48168
|
// src/bin.ts
|
|
47806
|
-
import { spawnSync as
|
|
47807
|
-
import { existsSync as
|
|
47808
|
-
import { delimiter as
|
|
48169
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
48170
|
+
import { existsSync as existsSync36, readFileSync as readFileSync29, statSync as statSync23 } from "node:fs";
|
|
48171
|
+
import { delimiter as delimiter4, dirname as dirname17, join as join39 } from "node:path";
|
|
47809
48172
|
import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
47810
48173
|
function readCliVersion() {
|
|
47811
|
-
let dir =
|
|
48174
|
+
let dir = dirname17(fileURLToPath9(import.meta.url));
|
|
47812
48175
|
for (let i = 0; i < 6; i++) {
|
|
47813
|
-
const pkgPath =
|
|
47814
|
-
if (
|
|
48176
|
+
const pkgPath = join39(dir, "package.json");
|
|
48177
|
+
if (existsSync36(pkgPath)) {
|
|
47815
48178
|
try {
|
|
47816
|
-
const pkg = JSON.parse(
|
|
48179
|
+
const pkg = JSON.parse(readFileSync29(pkgPath, "utf-8"));
|
|
47817
48180
|
if (typeof pkg.version === "string" && pkg.version) return pkg.version;
|
|
47818
48181
|
} catch {
|
|
47819
48182
|
}
|
|
47820
48183
|
}
|
|
47821
|
-
const parent =
|
|
48184
|
+
const parent = dirname17(dir);
|
|
47822
48185
|
if (parent === dir) break;
|
|
47823
48186
|
dir = parent;
|
|
47824
48187
|
}
|
|
@@ -48034,6 +48397,13 @@ var COMMANDS = {
|
|
|
48034
48397
|
usage: "[file]",
|
|
48035
48398
|
summary: "Run project tests"
|
|
48036
48399
|
},
|
|
48400
|
+
lint: {
|
|
48401
|
+
handler: (a) => {
|
|
48402
|
+
runLint(a);
|
|
48403
|
+
},
|
|
48404
|
+
usage: "[--fix] [--no-install]",
|
|
48405
|
+
summary: "Lint the project (eslint, installed dev-only on demand; else tsc/node --check baseline)"
|
|
48406
|
+
},
|
|
48037
48407
|
queue: {
|
|
48038
48408
|
handler: async (a) => {
|
|
48039
48409
|
await queueCommand(a);
|
|
@@ -48108,12 +48478,12 @@ var CLIENT_INSTALL_HINT = " Install it: curl -fsSL https://tina4.com/install.s
|
|
|
48108
48478
|
function findClient() {
|
|
48109
48479
|
const windows = process.platform === "win32";
|
|
48110
48480
|
const names = windows ? [`${CLIENT_BINARY}.exe`, `${CLIENT_BINARY}.cmd`, `${CLIENT_BINARY}.bat`] : [CLIENT_BINARY];
|
|
48111
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
48481
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter4)) {
|
|
48112
48482
|
if (!dir) continue;
|
|
48113
48483
|
for (const name of names) {
|
|
48114
|
-
const candidate =
|
|
48484
|
+
const candidate = join39(dir, name);
|
|
48115
48485
|
try {
|
|
48116
|
-
if (
|
|
48486
|
+
if (statSync23(candidate).isFile()) return candidate;
|
|
48117
48487
|
} catch {
|
|
48118
48488
|
}
|
|
48119
48489
|
}
|
|
@@ -48141,7 +48511,7 @@ ${CLIENT_INSTALL_HINT}
|
|
|
48141
48511
|
);
|
|
48142
48512
|
return EXIT_CLIENT_UNAVAILABLE;
|
|
48143
48513
|
}
|
|
48144
|
-
const result =
|
|
48514
|
+
const result = spawnSync5(client, [command, ...args], {
|
|
48145
48515
|
stdio: "inherit",
|
|
48146
48516
|
env: { ...process.env, [DELEGATION_GUARD_ENV]: command }
|
|
48147
48517
|
});
|