tina4-nodejs 3.13.95 → 3.13.97
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 +3 -4
- package/package.json +2 -1
- package/packages/cli/dist/bin.js +708 -1012
- package/packages/core/dist/index.js +588 -893
- package/packages/core/public/css/tina4.min.css +1 -1
- package/packages/core/src/index.ts +1 -3
- package/packages/core/src/messenger.ts +288 -96
- package/packages/core/src/queueBackends/kafkaBackend.ts +23 -2
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +29 -17
- package/packages/core/src/request.ts +28 -7
- package/packages/core/src/server.ts +135 -7
- package/packages/core/src/session.ts +8 -1
- package/packages/orm/dist/index.js +639 -944
- package/packages/orm/src/autoCrud.ts +12 -10
- package/packages/orm/src/database.ts +62 -58
- package/packages/orm/src/databaseResult.ts +44 -73
- package/packages/orm/src/index.ts +0 -3
- package/packages/orm/src/migration.ts +26 -8
- package/packages/orm/src/model.ts +4 -0
- package/packages/orm/src/queryBuilder.ts +12 -5
- package/packages/orm/src/types.ts +7 -74
- package/packages/swagger/dist/index.js +78 -20
- package/packages/swagger/src/generator.ts +172 -29
- package/types/core/src/index.d.ts +1 -3
- package/types/core/src/messenger.d.ts +45 -4
- package/types/core/src/queueBackends/kafkaBackend.d.ts +1 -0
- package/types/core/src/queueBackends/rabbitmqBackend.d.ts +2 -1
- package/types/core/src/server.d.ts +0 -4
- package/types/core/src/session.d.ts +7 -0
- package/types/orm/src/database.d.ts +34 -30
- package/types/orm/src/databaseResult.d.ts +26 -36
- package/types/orm/src/index.d.ts +1 -2
- package/types/orm/src/migration.d.ts +4 -3
- package/types/orm/src/types.d.ts +7 -34
- package/packages/core/src/scss.ts +0 -623
- package/types/core/src/scss.d.ts +0 -19
package/packages/cli/dist/bin.js
CHANGED
|
@@ -1829,64 +1829,6 @@ var init_middleware = __esm({
|
|
|
1829
1829
|
}
|
|
1830
1830
|
});
|
|
1831
1831
|
|
|
1832
|
-
// ../orm/src/types.ts
|
|
1833
|
-
var FetchResult;
|
|
1834
|
-
var init_types = __esm({
|
|
1835
|
-
"../orm/src/types.ts"() {
|
|
1836
|
-
"use strict";
|
|
1837
|
-
FetchResult = class {
|
|
1838
|
-
records;
|
|
1839
|
-
count;
|
|
1840
|
-
sql;
|
|
1841
|
-
constructor(records, sql = "") {
|
|
1842
|
-
this.records = records;
|
|
1843
|
-
this.count = records.length;
|
|
1844
|
-
this.sql = sql;
|
|
1845
|
-
}
|
|
1846
|
-
/** Paginate the in-memory result set. */
|
|
1847
|
-
toPaginate(page = 1, perPage = 20) {
|
|
1848
|
-
const total = this.count;
|
|
1849
|
-
const totalPages = Math.max(1, Math.ceil(total / perPage));
|
|
1850
|
-
const offset = (page - 1) * perPage;
|
|
1851
|
-
const data = this.records.slice(offset, offset + perPage);
|
|
1852
|
-
return {
|
|
1853
|
-
data,
|
|
1854
|
-
page,
|
|
1855
|
-
perPage,
|
|
1856
|
-
total,
|
|
1857
|
-
totalPages,
|
|
1858
|
-
hasNext: page < totalPages,
|
|
1859
|
-
hasPrev: page > 1
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
|
-
/** Return the first record or null. */
|
|
1863
|
-
first() {
|
|
1864
|
-
return this.records[0] ?? null;
|
|
1865
|
-
}
|
|
1866
|
-
/** Return the last record or null. */
|
|
1867
|
-
last() {
|
|
1868
|
-
return this.records[this.records.length - 1] ?? null;
|
|
1869
|
-
}
|
|
1870
|
-
/** Check if result is empty. */
|
|
1871
|
-
isEmpty() {
|
|
1872
|
-
return this.records.length === 0;
|
|
1873
|
-
}
|
|
1874
|
-
/** Convert to plain array. */
|
|
1875
|
-
toArray() {
|
|
1876
|
-
return [...this.records];
|
|
1877
|
-
}
|
|
1878
|
-
/** Convert to JSON string. */
|
|
1879
|
-
toJSON() {
|
|
1880
|
-
return JSON.stringify(this.records);
|
|
1881
|
-
}
|
|
1882
|
-
/** Iterate over records. */
|
|
1883
|
-
[Symbol.iterator]() {
|
|
1884
|
-
return this.records[Symbol.iterator]();
|
|
1885
|
-
}
|
|
1886
|
-
};
|
|
1887
|
-
}
|
|
1888
|
-
});
|
|
1889
|
-
|
|
1890
1832
|
// ../orm/src/databaseResult.ts
|
|
1891
1833
|
var DatabaseResult;
|
|
1892
1834
|
var init_databaseResult = __esm({
|
|
@@ -1950,75 +1892,52 @@ var init_databaseResult = __esm({
|
|
|
1950
1892
|
toArray() {
|
|
1951
1893
|
return this.records;
|
|
1952
1894
|
}
|
|
1953
|
-
/**
|
|
1895
|
+
/**
|
|
1896
|
+
* Describe the page this result IS — the canonical pagination envelope.
|
|
1954
1897
|
*
|
|
1955
|
-
*
|
|
1956
|
-
* (
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
1898
|
+
* Takes NO arguments and derives every field from the query that produced this
|
|
1899
|
+
* result (ADR-0043). Passing ANY argument RAISES: a DatabaseResult holds no
|
|
1900
|
+
* connection, so an argument could only re-slice the rows already in memory and
|
|
1901
|
+
* then report total_pages for pages it can never reach. To read page N, FETCH
|
|
1902
|
+
* page N (limit + offset) and call this with no arguments.
|
|
1959
1903
|
*
|
|
1960
|
-
*
|
|
1961
|
-
|
|
1962
|
-
/**
|
|
1963
|
-
* Describe the page this result actually IS. Takes no arguments.
|
|
1904
|
+
* The envelope is EXACTLY seven snake_case keys, identical across all four
|
|
1905
|
+
* frameworks: `records, total, page, per_page, total_pages, limit, offset`.
|
|
1964
1906
|
*
|
|
1965
|
-
*
|
|
1966
|
-
* (
|
|
1967
|
-
*
|
|
1968
|
-
*
|
|
1969
|
-
*
|
|
1970
|
-
*
|
|
1971
|
-
*
|
|
1907
|
+
* per_page = the query's limit
|
|
1908
|
+
* page = floor(offset / limit) + 1
|
|
1909
|
+
* total = the TRUE total for the filter — Database.fetch (and
|
|
1910
|
+
* QueryBuilder.get) run a COUNT probe whenever a limit was
|
|
1911
|
+
* applied — NEVER the number of rows returned
|
|
1912
|
+
* total_pages = ceil(total / per_page)
|
|
1913
|
+
* records = the rows the query returned, VERBATIM (never re-sliced)
|
|
1914
|
+
* limit = the SQL limit actually applied
|
|
1915
|
+
* offset = the SQL offset actually applied
|
|
1972
1916
|
*
|
|
1973
|
-
*
|
|
1974
|
-
*
|
|
1975
|
-
* (
|
|
1976
|
-
*
|
|
1977
|
-
* pages 1-5 of 20 were right and every page from 6 onward came back EMPTY
|
|
1978
|
-
* while totalPages reported 5,000.
|
|
1917
|
+
* The JSON payload is snake_case even though the method name is camelCase — a
|
|
1918
|
+
* JSON key is data, not a language surface (ADR-0043). The old duplicate and
|
|
1919
|
+
* camelCase keys (`data`, `count`, `perPage`, `totalPages`, `has_next`,
|
|
1920
|
+
* `has_prev`) are removed: Node emitted 13 keys, the worst offender of the four.
|
|
1979
1921
|
*
|
|
1980
|
-
*
|
|
1981
|
-
* all four frameworks - Database.fetch runs a COUNT probe whenever it applied
|
|
1982
|
-
* a limit. It used to be ROWS RETURNED here and in Ruby while Python and PHP
|
|
1983
|
-
* probed, so one query answered 20 in two frameworks and 250 in the other
|
|
1984
|
-
* two.
|
|
1922
|
+
* @throws {TypeError} if called with any argument.
|
|
1985
1923
|
*/
|
|
1986
|
-
toPaginate(
|
|
1987
|
-
if (
|
|
1924
|
+
toPaginate() {
|
|
1925
|
+
if (arguments.length > 0) {
|
|
1988
1926
|
throw new TypeError(
|
|
1989
|
-
|
|
1927
|
+
"toPaginate() takes no arguments and derives the page from the query that ran (ADR-0043). A DatabaseResult holds no connection, so an argument could only re-slice the rows already in memory and report total_pages for pages it can never reach. To read a page, FETCH it: db.fetch(sql, params, perPage, (page - 1) * perPage), then call toPaginate() with no arguments."
|
|
1990
1928
|
);
|
|
1991
1929
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
let rows;
|
|
1996
|
-
if (page === void 0 && perPage === void 0) {
|
|
1997
|
-
resolvedPerPage = this.limit > 0 ? this.limit : this.records.length;
|
|
1998
|
-
resolvedPage = resolvedPerPage > 0 ? Math.floor(this.offset / resolvedPerPage) + 1 : 1;
|
|
1999
|
-
offset = this.offset;
|
|
2000
|
-
rows = this.records;
|
|
2001
|
-
} else {
|
|
2002
|
-
resolvedPage = page ?? 1;
|
|
2003
|
-
resolvedPerPage = perPage ?? (this.limit > 0 ? this.limit : 10);
|
|
2004
|
-
offset = (resolvedPage - 1) * resolvedPerPage;
|
|
2005
|
-
rows = this.records.slice(offset, offset + resolvedPerPage);
|
|
2006
|
-
}
|
|
2007
|
-
const totalPages = resolvedPerPage > 0 ? Math.max(1, Math.ceil(this.count / resolvedPerPage)) : 1;
|
|
1930
|
+
const perPage = this.limit > 0 ? this.limit : this.records.length;
|
|
1931
|
+
const page = perPage > 0 ? Math.floor(this.offset / perPage) + 1 : 1;
|
|
1932
|
+
const totalPages = perPage > 0 ? Math.max(1, Math.ceil(this.count / perPage)) : 1;
|
|
2008
1933
|
return {
|
|
2009
|
-
records:
|
|
2010
|
-
data: rows,
|
|
2011
|
-
count: this.count,
|
|
1934
|
+
records: this.records,
|
|
2012
1935
|
total: this.count,
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
page: resolvedPage,
|
|
2016
|
-
per_page: resolvedPerPage,
|
|
2017
|
-
perPage: resolvedPerPage,
|
|
2018
|
-
totalPages,
|
|
1936
|
+
page,
|
|
1937
|
+
per_page: perPage,
|
|
2019
1938
|
total_pages: totalPages,
|
|
2020
|
-
|
|
2021
|
-
|
|
1939
|
+
limit: perPage,
|
|
1940
|
+
offset: this.offset
|
|
2022
1941
|
};
|
|
2023
1942
|
}
|
|
2024
1943
|
/** Iterable — for (const row of result) */
|
|
@@ -3862,7 +3781,7 @@ function withConnectTimeout(attempt, budgetMs, host, port, abandon) {
|
|
|
3862
3781
|
const elapsedMs = () => performance.now() - startedAt;
|
|
3863
3782
|
if (budgetMs === null) return attempt();
|
|
3864
3783
|
const started = attempt();
|
|
3865
|
-
return new Promise((
|
|
3784
|
+
return new Promise((resolve30, reject) => {
|
|
3866
3785
|
let expired = false;
|
|
3867
3786
|
const timer = setTimeout(() => {
|
|
3868
3787
|
expired = true;
|
|
@@ -3872,7 +3791,7 @@ function withConnectTimeout(attempt, budgetMs, host, port, abandon) {
|
|
|
3872
3791
|
(arrived) => {
|
|
3873
3792
|
clearTimeout(timer);
|
|
3874
3793
|
if (expired) abandon?.(arrived);
|
|
3875
|
-
else
|
|
3794
|
+
else resolve30(arrived);
|
|
3876
3795
|
},
|
|
3877
3796
|
(failure) => {
|
|
3878
3797
|
clearTimeout(timer);
|
|
@@ -4429,10 +4348,10 @@ var init_mysql = __esm({
|
|
|
4429
4348
|
...timeoutOption
|
|
4430
4349
|
});
|
|
4431
4350
|
}
|
|
4432
|
-
return new Promise((
|
|
4351
|
+
return new Promise((resolve30, reject) => {
|
|
4433
4352
|
this.connection.connect((err) => {
|
|
4434
4353
|
if (err) reject(err);
|
|
4435
|
-
else
|
|
4354
|
+
else resolve30();
|
|
4436
4355
|
});
|
|
4437
4356
|
});
|
|
4438
4357
|
},
|
|
@@ -4454,10 +4373,10 @@ var init_mysql = __esm({
|
|
|
4454
4373
|
}
|
|
4455
4374
|
}
|
|
4456
4375
|
queryPromise(sql, params) {
|
|
4457
|
-
return new Promise((
|
|
4376
|
+
return new Promise((resolve30, reject) => {
|
|
4458
4377
|
this.connection.query(sql, params ?? [], (err, results) => {
|
|
4459
4378
|
if (err) reject(err);
|
|
4460
|
-
else
|
|
4379
|
+
else resolve30(results);
|
|
4461
4380
|
});
|
|
4462
4381
|
});
|
|
4463
4382
|
}
|
|
@@ -4827,11 +4746,11 @@ var init_mssql = __esm({
|
|
|
4827
4746
|
};
|
|
4828
4747
|
}
|
|
4829
4748
|
await withConnectTimeout(
|
|
4830
|
-
() => new Promise((
|
|
4749
|
+
() => new Promise((resolve30, reject) => {
|
|
4831
4750
|
this.connection = new Connection(tediousConfig);
|
|
4832
4751
|
this.connection.on("connect", (err) => {
|
|
4833
4752
|
if (err) reject(err);
|
|
4834
|
-
else
|
|
4753
|
+
else resolve30();
|
|
4835
4754
|
});
|
|
4836
4755
|
this.connection.connect();
|
|
4837
4756
|
}),
|
|
@@ -4876,11 +4795,11 @@ var init_mssql = __esm({
|
|
|
4876
4795
|
const tediousModule = requireTedious();
|
|
4877
4796
|
const Request = tediousModule.Request;
|
|
4878
4797
|
const TYPES = tediousModule.TYPES;
|
|
4879
|
-
return new Promise((
|
|
4798
|
+
return new Promise((resolve30, reject) => {
|
|
4880
4799
|
const rows = [];
|
|
4881
4800
|
const request = new Request(sql, (err, rowCount) => {
|
|
4882
4801
|
if (err) reject(err);
|
|
4883
|
-
else
|
|
4802
|
+
else resolve30({ rows, rowCount });
|
|
4884
4803
|
});
|
|
4885
4804
|
if (params) {
|
|
4886
4805
|
params.forEach((p, i) => {
|
|
@@ -5083,8 +5002,8 @@ var init_mssql = __esm({
|
|
|
5083
5002
|
throw new Error("Use startTransactionAsync() for MSSQL.");
|
|
5084
5003
|
}
|
|
5085
5004
|
async startTransactionAsync() {
|
|
5086
|
-
await new Promise((
|
|
5087
|
-
this.connection.beginTransaction((err) => err ? reject(err) :
|
|
5005
|
+
await new Promise((resolve30, reject) => {
|
|
5006
|
+
this.connection.beginTransaction((err) => err ? reject(err) : resolve30());
|
|
5088
5007
|
});
|
|
5089
5008
|
this._inTransaction = true;
|
|
5090
5009
|
}
|
|
@@ -5092,8 +5011,8 @@ var init_mssql = __esm({
|
|
|
5092
5011
|
throw new Error("Use commitAsync() for MSSQL.");
|
|
5093
5012
|
}
|
|
5094
5013
|
async commitAsync() {
|
|
5095
|
-
await new Promise((
|
|
5096
|
-
this.connection.commitTransaction((err) => err ? reject(err) :
|
|
5014
|
+
await new Promise((resolve30, reject) => {
|
|
5015
|
+
this.connection.commitTransaction((err) => err ? reject(err) : resolve30());
|
|
5097
5016
|
});
|
|
5098
5017
|
this._inTransaction = false;
|
|
5099
5018
|
}
|
|
@@ -5101,8 +5020,8 @@ var init_mssql = __esm({
|
|
|
5101
5020
|
throw new Error("Use rollbackAsync() for MSSQL.");
|
|
5102
5021
|
}
|
|
5103
5022
|
async rollbackAsync() {
|
|
5104
|
-
await new Promise((
|
|
5105
|
-
this.connection.rollbackTransaction((err) => err ? reject(err) :
|
|
5023
|
+
await new Promise((resolve30, reject) => {
|
|
5024
|
+
this.connection.rollbackTransaction((err) => err ? reject(err) : resolve30());
|
|
5106
5025
|
});
|
|
5107
5026
|
this._inTransaction = false;
|
|
5108
5027
|
}
|
|
@@ -5372,8 +5291,8 @@ var init_firebird = __esm({
|
|
|
5372
5291
|
fbConfig.database = normalizeFirebirdDbIdentifier(fbConfig.database);
|
|
5373
5292
|
}
|
|
5374
5293
|
this.db = await withConnectTimeout(
|
|
5375
|
-
() => new Promise((
|
|
5376
|
-
fb.attach(fbConfig, (err, db) => err ? reject(err) :
|
|
5294
|
+
() => new Promise((resolve30, reject) => {
|
|
5295
|
+
fb.attach(fbConfig, (err, db) => err ? reject(err) : resolve30(db));
|
|
5377
5296
|
}),
|
|
5378
5297
|
connectTimeoutMillis(),
|
|
5379
5298
|
fbConfig.host,
|
|
@@ -5440,20 +5359,20 @@ var init_firebird = __esm({
|
|
|
5440
5359
|
return this.transaction ?? this.db;
|
|
5441
5360
|
}
|
|
5442
5361
|
queryPromise(sql, params) {
|
|
5443
|
-
return new Promise((
|
|
5362
|
+
return new Promise((resolve30, reject) => {
|
|
5444
5363
|
const translated = this.translateSql(sql);
|
|
5445
5364
|
this.statementHandle().query(translated, params ?? [], (err, result) => {
|
|
5446
5365
|
if (err) reject(err);
|
|
5447
|
-
else
|
|
5366
|
+
else resolve30(result ?? []);
|
|
5448
5367
|
});
|
|
5449
5368
|
});
|
|
5450
5369
|
}
|
|
5451
5370
|
executePromise(sql, params) {
|
|
5452
|
-
return new Promise((
|
|
5371
|
+
return new Promise((resolve30, reject) => {
|
|
5453
5372
|
const translated = this.translateSql(sql);
|
|
5454
5373
|
this.statementHandle().execute(translated, params ?? [], (err) => {
|
|
5455
5374
|
if (err) reject(err);
|
|
5456
|
-
else
|
|
5375
|
+
else resolve30();
|
|
5457
5376
|
});
|
|
5458
5377
|
});
|
|
5459
5378
|
}
|
|
@@ -5569,12 +5488,12 @@ var init_firebird = __esm({
|
|
|
5569
5488
|
}
|
|
5570
5489
|
async startTransactionAsync() {
|
|
5571
5490
|
this.ensureConnected();
|
|
5572
|
-
await new Promise((
|
|
5491
|
+
await new Promise((resolve30, reject) => {
|
|
5573
5492
|
this.db.transaction(0, (err, transaction) => {
|
|
5574
5493
|
if (err) reject(err);
|
|
5575
5494
|
else {
|
|
5576
5495
|
this.transaction = transaction;
|
|
5577
|
-
|
|
5496
|
+
resolve30();
|
|
5578
5497
|
}
|
|
5579
5498
|
});
|
|
5580
5499
|
});
|
|
@@ -5584,12 +5503,12 @@ var init_firebird = __esm({
|
|
|
5584
5503
|
}
|
|
5585
5504
|
async commitAsync() {
|
|
5586
5505
|
if (!this.transaction) throw new Error("No active transaction to commit.");
|
|
5587
|
-
await new Promise((
|
|
5506
|
+
await new Promise((resolve30, reject) => {
|
|
5588
5507
|
this.transaction.commit((err) => {
|
|
5589
5508
|
if (err) reject(err);
|
|
5590
5509
|
else {
|
|
5591
5510
|
this.transaction = null;
|
|
5592
|
-
|
|
5511
|
+
resolve30();
|
|
5593
5512
|
}
|
|
5594
5513
|
});
|
|
5595
5514
|
});
|
|
@@ -5599,12 +5518,12 @@ var init_firebird = __esm({
|
|
|
5599
5518
|
}
|
|
5600
5519
|
async rollbackAsync() {
|
|
5601
5520
|
if (!this.transaction) throw new Error("No active transaction to rollback.");
|
|
5602
|
-
await new Promise((
|
|
5521
|
+
await new Promise((resolve30, reject) => {
|
|
5603
5522
|
this.transaction.rollback((err) => {
|
|
5604
5523
|
if (err) reject(err);
|
|
5605
5524
|
else {
|
|
5606
5525
|
this.transaction = null;
|
|
5607
|
-
|
|
5526
|
+
resolve30();
|
|
5608
5527
|
}
|
|
5609
5528
|
});
|
|
5610
5529
|
});
|
|
@@ -6665,6 +6584,7 @@ __export(database_exports, {
|
|
|
6665
6584
|
getNamedAdapter: () => getNamedAdapter,
|
|
6666
6585
|
initDatabase: () => initDatabase,
|
|
6667
6586
|
parseDatabaseUrl: () => parseDatabaseUrl,
|
|
6587
|
+
probeTotal: () => probeTotal,
|
|
6668
6588
|
resetRequestCaches: () => resetRequestCaches,
|
|
6669
6589
|
resolveDbPool: () => resolveDbPool,
|
|
6670
6590
|
setAdapter: () => setAdapter,
|
|
@@ -6718,6 +6638,29 @@ async function adapterCreateTable(adapter, name, columns) {
|
|
|
6718
6638
|
if (adapter.createTableAsync) await adapter.createTableAsync(name, columns);
|
|
6719
6639
|
else adapter.createTable(name, columns);
|
|
6720
6640
|
}
|
|
6641
|
+
async function probeTotal(adapter, sql, params, limit) {
|
|
6642
|
+
if (limit === void 0 || limit <= 0) return void 0;
|
|
6643
|
+
try {
|
|
6644
|
+
const alias = adapter.countSubqueryAlias;
|
|
6645
|
+
const suffix = alias ? ` AS ${alias}` : "";
|
|
6646
|
+
const rows = await adapterFetch(
|
|
6647
|
+
adapter,
|
|
6648
|
+
`SELECT COUNT(*) AS tina4_total FROM (${sql}
|
|
6649
|
+
)${suffix}`,
|
|
6650
|
+
params,
|
|
6651
|
+
void 0,
|
|
6652
|
+
void 0,
|
|
6653
|
+
true
|
|
6654
|
+
);
|
|
6655
|
+
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
6656
|
+
if (!row) return void 0;
|
|
6657
|
+
const value = row["tina4_total"] ?? row["TINA4_TOTAL"] ?? Object.values(row)[0];
|
|
6658
|
+
const n = Number(value);
|
|
6659
|
+
return Number.isFinite(n) ? n : void 0;
|
|
6660
|
+
} catch {
|
|
6661
|
+
return void 0;
|
|
6662
|
+
}
|
|
6663
|
+
}
|
|
6721
6664
|
function extractLastInsertId(result) {
|
|
6722
6665
|
if (result && typeof result === "object") {
|
|
6723
6666
|
const r = result;
|
|
@@ -7176,65 +7119,13 @@ var init_database = __esm({
|
|
|
7176
7119
|
try {
|
|
7177
7120
|
const rows = await adapterFetch(adapter, sql, params, limit, offset, opts?.noCache);
|
|
7178
7121
|
this.lastError = null;
|
|
7179
|
-
const total = await
|
|
7122
|
+
const total = await probeTotal(adapter, sql, params, limit);
|
|
7180
7123
|
return new DatabaseResult(rows, void 0, total, limit, offset, adapter, sql);
|
|
7181
7124
|
} catch (e) {
|
|
7182
7125
|
this.lastError = e?.message ?? String(e);
|
|
7183
7126
|
throw e;
|
|
7184
7127
|
}
|
|
7185
7128
|
}
|
|
7186
|
-
/**
|
|
7187
|
-
* The true row count for `sql`, ignoring the pagination we appended.
|
|
7188
|
-
*
|
|
7189
|
-
* `count` is the TRUE TOTAL for the filter, not the number of rows this page
|
|
7190
|
-
* returned. Node and Ruby used to populate it with `records.length` while
|
|
7191
|
-
* Python and PHP populated it from a probe, so `db.fetch(sql).count` answered
|
|
7192
|
-
* 20 here and 250 there for one query against one table, and every paginated
|
|
7193
|
-
* response built on it under-reported. MEASURED 2026-08-05 on a 250-row table
|
|
7194
|
-
* read with limit=20: Node reported total 20 over 2 pages against Python's
|
|
7195
|
-
* 250 over 13.
|
|
7196
|
-
*
|
|
7197
|
-
* Only probed when a limit was actually applied. With no limit the rows
|
|
7198
|
-
* returned ARE the whole answer for this SQL, so `records.length` is already
|
|
7199
|
-
* the true total and a second round-trip would buy nothing — which is also
|
|
7200
|
-
* what keeps `fetchAll()` at one query.
|
|
7201
|
-
*
|
|
7202
|
-
* BEST EFFORT, and it can never mask a real failure: it runs AFTER the main
|
|
7203
|
-
* query (which has already thrown on bad SQL) and returns undefined on any
|
|
7204
|
-
* error. `undefined` — not 0 — is the miss value, so DatabaseResult falls
|
|
7205
|
-
* back to records.length, a true lower bound. Reporting 0 next to 100 real
|
|
7206
|
-
* records would be the same "states a wrong number authoritatively" defect
|
|
7207
|
-
* this change exists to remove.
|
|
7208
|
-
*
|
|
7209
|
-
* The closing paren goes on its OWN LINE: appended inline, a trailing
|
|
7210
|
-
* `-- comment` in the caller's SQL comments it out and the probe dies with
|
|
7211
|
-
* "incomplete input". Postgres, MySQL and MSSQL additionally require a name
|
|
7212
|
-
* for the derived table; SQLite and Firebird do not, and Firebird rejects
|
|
7213
|
-
* `AS` there — so the alias comes from the adapter, not an assumption.
|
|
7214
|
-
*/
|
|
7215
|
-
async countProbe(adapter, sql, params, limit) {
|
|
7216
|
-
if (limit === void 0 || limit <= 0) return void 0;
|
|
7217
|
-
try {
|
|
7218
|
-
const alias = adapter.countSubqueryAlias;
|
|
7219
|
-
const suffix = alias ? ` AS ${alias}` : "";
|
|
7220
|
-
const rows = await adapterFetch(
|
|
7221
|
-
adapter,
|
|
7222
|
-
`SELECT COUNT(*) AS tina4_total FROM (${sql}
|
|
7223
|
-
)${suffix}`,
|
|
7224
|
-
params,
|
|
7225
|
-
void 0,
|
|
7226
|
-
void 0,
|
|
7227
|
-
true
|
|
7228
|
-
);
|
|
7229
|
-
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
7230
|
-
if (!row) return void 0;
|
|
7231
|
-
const value = row["tina4_total"] ?? row["TINA4_TOTAL"] ?? Object.values(row)[0];
|
|
7232
|
-
const n = Number(value);
|
|
7233
|
-
return Number.isFinite(n) ? n : void 0;
|
|
7234
|
-
} catch {
|
|
7235
|
-
return void 0;
|
|
7236
|
-
}
|
|
7237
|
-
}
|
|
7238
7129
|
/**
|
|
7239
7130
|
* Fetch a single row or null.
|
|
7240
7131
|
*
|
|
@@ -7946,6 +7837,10 @@ async function discoverModels(modelsDir) {
|
|
|
7946
7837
|
}
|
|
7947
7838
|
const definition = {
|
|
7948
7839
|
tableName: ModelClass.tableName,
|
|
7840
|
+
// The class name is the type name a generated OpenAPI client wants
|
|
7841
|
+
// (`Item`, not `items`). Carry it so Swagger keys components.schemas by
|
|
7842
|
+
// it. A model exported as `default` keeps its declared class name here.
|
|
7843
|
+
className: typeof ModelClass.name === "string" && ModelClass.name ? ModelClass.name : void 0,
|
|
7949
7844
|
fields: ModelClass.fields,
|
|
7950
7845
|
fieldMapping: ModelClass.fieldMapping,
|
|
7951
7846
|
softDelete: ModelClass.softDelete ?? false,
|
|
@@ -8560,7 +8455,13 @@ async function status(adapter, options) {
|
|
|
8560
8455
|
return result;
|
|
8561
8456
|
}
|
|
8562
8457
|
async function createMigration(description, options) {
|
|
8563
|
-
|
|
8458
|
+
const kind = (options?.kind ?? "sql").trim().toLowerCase();
|
|
8459
|
+
if (!["sql", "code", "class"].includes(kind)) {
|
|
8460
|
+
throw new Error(
|
|
8461
|
+
`Unknown migration kind "${kind}". Use "sql" (default) or "code" (alias: "class"). An unrecognised kind used to produce a .sql file silently, which is why this now throws.`
|
|
8462
|
+
);
|
|
8463
|
+
}
|
|
8464
|
+
if (kind === "code" || kind === "class") {
|
|
8564
8465
|
return createClassMigration(description, options);
|
|
8565
8466
|
}
|
|
8566
8467
|
const dir = resolve4(options?.migrationsDir ?? "migrations");
|
|
@@ -8687,15 +8588,13 @@ var init_migration = __esm({
|
|
|
8687
8588
|
* Scaffold a new migration file.
|
|
8688
8589
|
*
|
|
8689
8590
|
* kind="sql" — creates {timestamp}_{description}.sql + .down.sql (default)
|
|
8690
|
-
* kind="
|
|
8591
|
+
* kind="code" — creates {timestamp}_{description}.ts with a TypeScript class
|
|
8592
|
+
* template. "class" is accepted as a legacy alias.
|
|
8691
8593
|
*
|
|
8692
8594
|
* Returns the path to the created up file (or class file).
|
|
8693
8595
|
*/
|
|
8694
8596
|
async create(description, kind = "sql") {
|
|
8695
|
-
|
|
8696
|
-
return createClassMigration(description, { migrationsDir: this.dir });
|
|
8697
|
-
}
|
|
8698
|
-
return createMigration(description, { migrationsDir: this.dir });
|
|
8597
|
+
return createMigration(description, { migrationsDir: this.dir, kind });
|
|
8699
8598
|
}
|
|
8700
8599
|
/** Return list of completed (applied) migration filenames. */
|
|
8701
8600
|
async getApplied() {
|
|
@@ -8938,15 +8837,8 @@ function generateCrudRoutes(models, options = {}) {
|
|
|
8938
8837
|
const total = Number(countRow[0]?.total ?? 0);
|
|
8939
8838
|
const limit = qp.limit ?? 100;
|
|
8940
8839
|
const page = qp.page ?? 1;
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
meta: {
|
|
8944
|
-
total,
|
|
8945
|
-
page,
|
|
8946
|
-
limit,
|
|
8947
|
-
totalPages: Math.ceil(total / limit)
|
|
8948
|
-
}
|
|
8949
|
-
});
|
|
8840
|
+
const offset = (page - 1) * limit;
|
|
8841
|
+
res.json(new DatabaseResult(rows, void 0, total, limit, offset).toPaginate());
|
|
8950
8842
|
}
|
|
8951
8843
|
});
|
|
8952
8844
|
routes.push({
|
|
@@ -9100,6 +8992,7 @@ var init_autoCrud = __esm({
|
|
|
9100
8992
|
"../orm/src/autoCrud.ts"() {
|
|
9101
8993
|
"use strict";
|
|
9102
8994
|
init_database();
|
|
8995
|
+
init_databaseResult();
|
|
9103
8996
|
init_query();
|
|
9104
8997
|
init_validation();
|
|
9105
8998
|
AutoCrud = class _AutoCrud {
|
|
@@ -9354,17 +9247,19 @@ var init_queryBuilder = __esm({
|
|
|
9354
9247
|
this.ensureDb();
|
|
9355
9248
|
const sql = this.toSql();
|
|
9356
9249
|
const allParams = [...this.params, ...this.havingParams];
|
|
9250
|
+
const queryParams = allParams.length > 0 ? allParams : void 0;
|
|
9357
9251
|
const rows = await adapterFetch(
|
|
9358
9252
|
this.db,
|
|
9359
9253
|
sql,
|
|
9360
|
-
|
|
9254
|
+
queryParams,
|
|
9361
9255
|
this.limitVal,
|
|
9362
9256
|
this.offsetVal
|
|
9363
9257
|
);
|
|
9258
|
+
const total = await probeTotal(this.db, sql, queryParams, this.limitVal);
|
|
9364
9259
|
return new DatabaseResult(
|
|
9365
9260
|
rows,
|
|
9366
9261
|
void 0,
|
|
9367
|
-
|
|
9262
|
+
total,
|
|
9368
9263
|
this.limitVal,
|
|
9369
9264
|
this.offsetVal,
|
|
9370
9265
|
this.db,
|
|
@@ -12941,7 +12836,6 @@ __export(src_exports, {
|
|
|
12941
12836
|
DatabaseUrl: () => DatabaseUrl,
|
|
12942
12837
|
DocStoreDriverMissing: () => DocStoreDriverMissing,
|
|
12943
12838
|
FakeData: () => FakeData2,
|
|
12944
|
-
FetchResult: () => FetchResult,
|
|
12945
12839
|
FirebirdAdapter: () => FirebirdAdapter,
|
|
12946
12840
|
InvalidId: () => InvalidId,
|
|
12947
12841
|
LocalStorage: () => LocalStorage,
|
|
@@ -13041,7 +12935,6 @@ __export(src_exports, {
|
|
|
13041
12935
|
var init_src = __esm({
|
|
13042
12936
|
"../orm/src/index.ts"() {
|
|
13043
12937
|
"use strict";
|
|
13044
|
-
init_types();
|
|
13045
12938
|
init_databaseResult();
|
|
13046
12939
|
init_database();
|
|
13047
12940
|
init_database();
|
|
@@ -13451,7 +13344,7 @@ ${s}\r
|
|
|
13451
13344
|
connect() {
|
|
13452
13345
|
if (this.connected) return Promise.resolve();
|
|
13453
13346
|
if (this.connecting) return this.connecting;
|
|
13454
|
-
this.connecting = new Promise((
|
|
13347
|
+
this.connecting = new Promise((resolve30, reject) => {
|
|
13455
13348
|
const sock = net.createConnection({ host: this.host, port: this.port });
|
|
13456
13349
|
sock.setNoDelay(true);
|
|
13457
13350
|
const onError = (err) => {
|
|
@@ -13488,7 +13381,7 @@ ${s}\r
|
|
|
13488
13381
|
sock.on("error", (e) => {
|
|
13489
13382
|
this.brokenError = e;
|
|
13490
13383
|
});
|
|
13491
|
-
|
|
13384
|
+
resolve30();
|
|
13492
13385
|
} catch (e) {
|
|
13493
13386
|
onError(e);
|
|
13494
13387
|
}
|
|
@@ -13551,12 +13444,12 @@ ${s}\r
|
|
|
13551
13444
|
}
|
|
13552
13445
|
/** Send one command and await its reply (assumes socket is up). */
|
|
13553
13446
|
raw(args) {
|
|
13554
|
-
return new Promise((
|
|
13447
|
+
return new Promise((resolve30, reject) => {
|
|
13555
13448
|
if (!this.sock || this.sock.destroyed) {
|
|
13556
13449
|
reject(this.brokenError ?? new Error("redis socket not connected"));
|
|
13557
13450
|
return;
|
|
13558
13451
|
}
|
|
13559
|
-
this.waiters.push({ resolve:
|
|
13452
|
+
this.waiters.push({ resolve: resolve30, reject });
|
|
13560
13453
|
this.sock.write(_RespClient.encode(args));
|
|
13561
13454
|
});
|
|
13562
13455
|
}
|
|
@@ -13898,7 +13791,7 @@ ${s}\r
|
|
|
13898
13791
|
connect() {
|
|
13899
13792
|
if (this.connected) return Promise.resolve();
|
|
13900
13793
|
if (this.connecting) return this.connecting;
|
|
13901
|
-
this.connecting = new Promise((
|
|
13794
|
+
this.connecting = new Promise((resolve30, reject) => {
|
|
13902
13795
|
const sock = net.createConnection({ host: this.host, port: this.port });
|
|
13903
13796
|
sock.setNoDelay(true);
|
|
13904
13797
|
sock.once("error", (err) => {
|
|
@@ -13919,7 +13812,7 @@ ${s}\r
|
|
|
13919
13812
|
p.resolve(this.buffer.toString("utf-8"));
|
|
13920
13813
|
}
|
|
13921
13814
|
});
|
|
13922
|
-
|
|
13815
|
+
resolve30();
|
|
13923
13816
|
});
|
|
13924
13817
|
});
|
|
13925
13818
|
return this.connecting;
|
|
@@ -13952,13 +13845,13 @@ ${s}\r
|
|
|
13952
13845
|
async send(payload, terminator) {
|
|
13953
13846
|
await this.connect();
|
|
13954
13847
|
if (!this.sock || this.sock.destroyed) return "";
|
|
13955
|
-
return new Promise((
|
|
13848
|
+
return new Promise((resolve30) => {
|
|
13956
13849
|
this.buffer = Buffer.alloc(0);
|
|
13957
|
-
this.pending = { terminator, resolve:
|
|
13850
|
+
this.pending = { terminator, resolve: resolve30 };
|
|
13958
13851
|
const timer = setTimeout(() => {
|
|
13959
|
-
if (this.pending && this.pending.resolve ===
|
|
13852
|
+
if (this.pending && this.pending.resolve === resolve30) {
|
|
13960
13853
|
this.pending = null;
|
|
13961
|
-
|
|
13854
|
+
resolve30(this.buffer.toString("utf-8"));
|
|
13962
13855
|
}
|
|
13963
13856
|
}, 4e3);
|
|
13964
13857
|
if (timer.unref) timer.unref();
|
|
@@ -15484,16 +15377,27 @@ async function parseBody(req2) {
|
|
|
15484
15377
|
}
|
|
15485
15378
|
const contentType = req2.headers["content-type"] ?? "";
|
|
15486
15379
|
const chunks = [];
|
|
15487
|
-
await new Promise((
|
|
15488
|
-
|
|
15489
|
-
|
|
15380
|
+
await new Promise((resolve30, reject) => {
|
|
15381
|
+
let received = 0;
|
|
15382
|
+
let refused = false;
|
|
15383
|
+
req2.on("data", (chunk) => {
|
|
15384
|
+
if (refused) return;
|
|
15385
|
+
received += chunk.length;
|
|
15386
|
+
if (received > TINA4_MAX_UPLOAD_SIZE) {
|
|
15387
|
+
refused = true;
|
|
15388
|
+
chunks.length = 0;
|
|
15389
|
+
reject(new PayloadTooLargeError(received, TINA4_MAX_UPLOAD_SIZE));
|
|
15390
|
+
return;
|
|
15391
|
+
}
|
|
15392
|
+
chunks.push(chunk);
|
|
15393
|
+
});
|
|
15394
|
+
req2.on("end", () => {
|
|
15395
|
+
if (!refused) resolve30();
|
|
15396
|
+
});
|
|
15490
15397
|
req2.on("error", reject);
|
|
15491
15398
|
});
|
|
15492
15399
|
const raw = Buffer.concat(chunks);
|
|
15493
15400
|
if (raw.length === 0) return;
|
|
15494
|
-
if (raw.length > TINA4_MAX_UPLOAD_SIZE) {
|
|
15495
|
-
throw new PayloadTooLargeError(raw.length, TINA4_MAX_UPLOAD_SIZE);
|
|
15496
|
-
}
|
|
15497
15401
|
if (contentType.includes("multipart/form-data")) {
|
|
15498
15402
|
const boundary = extractBoundary(contentType);
|
|
15499
15403
|
if (boundary) {
|
|
@@ -17442,10 +17346,17 @@ var init_session = __esm({
|
|
|
17442
17346
|
*
|
|
17443
17347
|
* session.flash("message", "Saved!") // set
|
|
17444
17348
|
* session.flash("message") // get + auto-remove → "Saved!"
|
|
17349
|
+
* session.flash("message", null) // get + auto-remove (null is a GET sentinel)
|
|
17350
|
+
*
|
|
17351
|
+
* `null` — NOT just `undefined` — is the GET sentinel, so `flash(key, null)`
|
|
17352
|
+
* READS and clears rather than STORING null. This matches the Python master
|
|
17353
|
+
* (`if value is not None`), PHP (`if ($value !== null)`) and Ruby
|
|
17354
|
+
* (`if value.nil?`): passing the language's "no value" literal means GET. A
|
|
17355
|
+
* caller wanting to persist an explicit null should store it with `set()`.
|
|
17445
17356
|
*/
|
|
17446
17357
|
flash(key, value) {
|
|
17447
17358
|
const flashKey = `${FLASH_PREFIX}${key}`;
|
|
17448
|
-
if (value !== void 0) {
|
|
17359
|
+
if (value !== void 0 && value !== null) {
|
|
17449
17360
|
this.set(flashKey, value);
|
|
17450
17361
|
return void 0;
|
|
17451
17362
|
}
|
|
@@ -23327,14 +23238,14 @@ data: ${channel.buffer.shift()}
|
|
|
23327
23238
|
`;
|
|
23328
23239
|
continue;
|
|
23329
23240
|
}
|
|
23330
|
-
const gotMessage = await new Promise((
|
|
23241
|
+
const gotMessage = await new Promise((resolve30) => {
|
|
23331
23242
|
const timer = setTimeout(() => {
|
|
23332
23243
|
channel.wake = null;
|
|
23333
|
-
|
|
23244
|
+
resolve30(false);
|
|
23334
23245
|
}, keepaliveMs);
|
|
23335
23246
|
channel.wake = () => {
|
|
23336
23247
|
clearTimeout(timer);
|
|
23337
|
-
|
|
23248
|
+
resolve30(true);
|
|
23338
23249
|
};
|
|
23339
23250
|
});
|
|
23340
23251
|
if (!gotMessage) yield `: keep-alive
|
|
@@ -25849,7 +25760,7 @@ var init_websocket = __esm({
|
|
|
25849
25760
|
* Start the WebSocket server.
|
|
25850
25761
|
*/
|
|
25851
25762
|
async start() {
|
|
25852
|
-
return new Promise((
|
|
25763
|
+
return new Promise((resolve30, reject) => {
|
|
25853
25764
|
this.server = createServer((req2, res) => {
|
|
25854
25765
|
res.writeHead(426, { "Content-Type": "text/plain" });
|
|
25855
25766
|
res.end("Upgrade Required");
|
|
@@ -25859,7 +25770,7 @@ var init_websocket = __esm({
|
|
|
25859
25770
|
});
|
|
25860
25771
|
this.server.listen(this.port, () => {
|
|
25861
25772
|
this.startIdleReaper();
|
|
25862
|
-
|
|
25773
|
+
resolve30();
|
|
25863
25774
|
});
|
|
25864
25775
|
this.server.on("error", (err) => {
|
|
25865
25776
|
this.emit("error", err);
|
|
@@ -26423,7 +26334,7 @@ var init_websocket = __esm({
|
|
|
26423
26334
|
client.trackerId = this.onAdd(socket.remoteAddress ?? "unknown", "/__dev_reload");
|
|
26424
26335
|
}
|
|
26425
26336
|
this.clients.add(client);
|
|
26426
|
-
const
|
|
26337
|
+
const cleanup = () => {
|
|
26427
26338
|
if (!this.clients.has(client)) return;
|
|
26428
26339
|
this.clients.delete(client);
|
|
26429
26340
|
if (client.trackerId && this.onRemove) this.onRemove(client.trackerId);
|
|
@@ -26446,13 +26357,13 @@ var init_websocket = __esm({
|
|
|
26446
26357
|
socket.end();
|
|
26447
26358
|
} catch {
|
|
26448
26359
|
}
|
|
26449
|
-
|
|
26360
|
+
cleanup();
|
|
26450
26361
|
return;
|
|
26451
26362
|
}
|
|
26452
26363
|
}
|
|
26453
26364
|
});
|
|
26454
|
-
socket.on("close",
|
|
26455
|
-
socket.on("error",
|
|
26365
|
+
socket.on("close", cleanup);
|
|
26366
|
+
socket.on("error", cleanup);
|
|
26456
26367
|
return true;
|
|
26457
26368
|
}
|
|
26458
26369
|
/**
|
|
@@ -27970,7 +27881,7 @@ var init_queue = __esm({
|
|
|
27970
27881
|
const jobs = this.popBatch(resolvedBatchSize);
|
|
27971
27882
|
if (jobs.length === 0) {
|
|
27972
27883
|
if (resolvedPollInterval <= 0) break;
|
|
27973
|
-
await new Promise((
|
|
27884
|
+
await new Promise((resolve30) => setTimeout(resolve30, resolvedPollInterval));
|
|
27974
27885
|
continue;
|
|
27975
27886
|
}
|
|
27976
27887
|
yield jobs;
|
|
@@ -27980,7 +27891,7 @@ var init_queue = __esm({
|
|
|
27980
27891
|
const raw = this.pop();
|
|
27981
27892
|
if (raw === null) {
|
|
27982
27893
|
if (resolvedPollInterval <= 0) break;
|
|
27983
|
-
await new Promise((
|
|
27894
|
+
await new Promise((resolve30) => setTimeout(resolve30, resolvedPollInterval));
|
|
27984
27895
|
continue;
|
|
27985
27896
|
}
|
|
27986
27897
|
yield createJob(raw, this);
|
|
@@ -32500,23 +32411,23 @@ var init_devAdmin = __esm({
|
|
|
32500
32411
|
});
|
|
32501
32412
|
};
|
|
32502
32413
|
handleDevAdminJs = async (_req, res) => {
|
|
32503
|
-
const { readFileSync:
|
|
32504
|
-
const { dirname:
|
|
32414
|
+
const { readFileSync: readFileSync28, existsSync: existsSync36 } = await import("node:fs");
|
|
32415
|
+
const { dirname: dirname15, join: join38, resolve: resolve30 } = await import("node:path");
|
|
32505
32416
|
const { fileURLToPath: fileURLToPath9 } = await import("node:url");
|
|
32506
|
-
const dir =
|
|
32417
|
+
const dir = dirname15(fileURLToPath9(import.meta.url));
|
|
32507
32418
|
const candidates = [
|
|
32508
|
-
|
|
32419
|
+
join38(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
32509
32420
|
// src/../public/js/
|
|
32510
|
-
|
|
32421
|
+
join38(dir, "..", "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
32511
32422
|
// deeper nesting
|
|
32512
|
-
|
|
32513
|
-
|
|
32423
|
+
resolve30(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin.min.js"),
|
|
32424
|
+
resolve30(process.cwd(), "public", "js", "tina4-dev-admin.min.js")
|
|
32514
32425
|
// project public/
|
|
32515
32426
|
];
|
|
32516
32427
|
for (const jsPath of candidates) {
|
|
32517
|
-
if (
|
|
32428
|
+
if (existsSync36(jsPath)) {
|
|
32518
32429
|
try {
|
|
32519
|
-
const content =
|
|
32430
|
+
const content = readFileSync28(jsPath, "utf-8");
|
|
32520
32431
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
32521
32432
|
res.raw.end(content);
|
|
32522
32433
|
return;
|
|
@@ -32977,8 +32888,12 @@ function sanitizeSecurity(reqs, schemes) {
|
|
|
32977
32888
|
function generate(routes, models = []) {
|
|
32978
32889
|
const info = {
|
|
32979
32890
|
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
32980
|
-
version
|
|
32981
|
-
|
|
32891
|
+
// The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
|
|
32892
|
+
// 0.0.1). description defaults to the empty string, not a canned sentence.
|
|
32893
|
+
// Both are the settled cross-framework defaults (parity with the Python
|
|
32894
|
+
// master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
|
|
32895
|
+
version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
|
|
32896
|
+
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? ""
|
|
32982
32897
|
};
|
|
32983
32898
|
const contactEmail = (process.env.TINA4_SWAGGER_CONTACT_EMAIL ?? "").trim();
|
|
32984
32899
|
const contactName = (process.env.TINA4_SWAGGER_CONTACT_TEAM ?? "").trim();
|
|
@@ -33011,9 +32926,11 @@ function generate(routes, models = []) {
|
|
|
33011
32926
|
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
33012
32927
|
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
33013
32928
|
const refSchemas = /* @__PURE__ */ new Set();
|
|
32929
|
+
const tableToSchema = /* @__PURE__ */ new Map();
|
|
33014
32930
|
for (const model of models) {
|
|
33015
|
-
const
|
|
33016
|
-
|
|
32931
|
+
const schemaKey = schemaNameForModel(model);
|
|
32932
|
+
tableToSchema.set(model.tableName, schemaKey);
|
|
32933
|
+
spec.components.schemas[schemaKey] = modelToSchema(model);
|
|
33017
32934
|
}
|
|
33018
32935
|
const usedTags = [];
|
|
33019
32936
|
const seenIds = /* @__PURE__ */ new Set();
|
|
@@ -33040,11 +32957,11 @@ function generate(routes, models = []) {
|
|
|
33040
32957
|
if (route.meta?.deprecated) operation.deprecated = true;
|
|
33041
32958
|
const pathParams = extractPathParams(route.pattern);
|
|
33042
32959
|
if (pathParams.length > 0) {
|
|
33043
|
-
operation.parameters = pathParams.map((name) => ({
|
|
32960
|
+
operation.parameters = pathParams.map(({ name, schema }) => ({
|
|
33044
32961
|
name,
|
|
33045
32962
|
in: "path",
|
|
33046
32963
|
required: true,
|
|
33047
|
-
schema
|
|
32964
|
+
schema
|
|
33048
32965
|
}));
|
|
33049
32966
|
}
|
|
33050
32967
|
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
@@ -33070,19 +32987,20 @@ function generate(routes, models = []) {
|
|
|
33070
32987
|
};
|
|
33071
32988
|
} else if (method === "post" || method === "put") {
|
|
33072
32989
|
const modelName = inferModelFromPath(route.pattern);
|
|
33073
|
-
|
|
33074
|
-
|
|
33075
|
-
|
|
33076
|
-
};
|
|
32990
|
+
const schemaKey = modelName ? tableToSchema.get(modelName) : void 0;
|
|
32991
|
+
if (schemaKey) {
|
|
32992
|
+
const sref = `#/components/schemas/${schemaKey}`;
|
|
32993
|
+
const media = { schema: { $ref: sref } };
|
|
33077
32994
|
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
33078
32995
|
operation.requestBody = {
|
|
33079
32996
|
required: true,
|
|
33080
32997
|
content: { "application/json": media }
|
|
33081
32998
|
};
|
|
33082
|
-
|
|
33083
|
-
|
|
33084
|
-
|
|
33085
|
-
|
|
32999
|
+
if (route.meta?.responses === void 0) {
|
|
33000
|
+
operation.responses = {
|
|
33001
|
+
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
33002
|
+
};
|
|
33003
|
+
}
|
|
33086
33004
|
} else if (route.meta?.example !== void 0) {
|
|
33087
33005
|
operation.requestBody = {
|
|
33088
33006
|
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
@@ -33169,6 +33087,21 @@ function resolveServers() {
|
|
|
33169
33087
|
const dev = (process.env.SWAGGER_DEV_URL ?? "").trim();
|
|
33170
33088
|
return dev.length > 0 ? [{ url: dev }] : [{ url: "/" }];
|
|
33171
33089
|
}
|
|
33090
|
+
function schemaNameForModel(model) {
|
|
33091
|
+
const explicit = model.className?.trim();
|
|
33092
|
+
if (explicit) return explicit;
|
|
33093
|
+
return deriveClassName(model.tableName);
|
|
33094
|
+
}
|
|
33095
|
+
function deriveClassName(tableName) {
|
|
33096
|
+
return singularize(tableName).split(/[_\s-]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("") || tableName;
|
|
33097
|
+
}
|
|
33098
|
+
function singularize(word) {
|
|
33099
|
+
if (/ies$/i.test(word) && word.length > 3) return word.slice(0, -3) + "y";
|
|
33100
|
+
if (/(ses|xes|zes|ches|shes)$/i.test(word)) return word.slice(0, -2);
|
|
33101
|
+
if (/ss$/i.test(word)) return word;
|
|
33102
|
+
if (/s$/i.test(word) && word.length > 1) return word.slice(0, -1);
|
|
33103
|
+
return word;
|
|
33104
|
+
}
|
|
33172
33105
|
function modelToSchema(model) {
|
|
33173
33106
|
const properties = {};
|
|
33174
33107
|
const required = [];
|
|
@@ -33242,15 +33175,35 @@ function inferSchema(value) {
|
|
|
33242
33175
|
if (typeof value === "number") return { type: Number.isInteger(value) ? "integer" : "number" };
|
|
33243
33176
|
return { type: "string" };
|
|
33244
33177
|
}
|
|
33178
|
+
function segmentParam(segment) {
|
|
33179
|
+
if (segment.startsWith("{") && segment.endsWith("}")) {
|
|
33180
|
+
const inner = segment.slice(1, -1);
|
|
33181
|
+
if (inner.startsWith("...")) return { name: inner.slice(3), type: "string" };
|
|
33182
|
+
const colon = inner.indexOf(":");
|
|
33183
|
+
if (colon >= 0) return { name: inner.slice(0, colon), type: inner.slice(colon + 1) };
|
|
33184
|
+
return { name: inner, type: "string" };
|
|
33185
|
+
}
|
|
33186
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
33187
|
+
const inner = segment.slice(1, -1);
|
|
33188
|
+
return { name: inner.startsWith("...") ? inner.slice(3) : inner, type: "string" };
|
|
33189
|
+
}
|
|
33190
|
+
if (segment.startsWith(":") && segment.length > 1) {
|
|
33191
|
+
return { name: segment.slice(1), type: "string" };
|
|
33192
|
+
}
|
|
33193
|
+
return null;
|
|
33194
|
+
}
|
|
33245
33195
|
function patternToOpenAPI(pattern) {
|
|
33246
|
-
return pattern.
|
|
33196
|
+
return pattern.split("/").map((segment) => {
|
|
33197
|
+
const p = segmentParam(segment);
|
|
33198
|
+
return p ? `{${p.name}}` : segment;
|
|
33199
|
+
}).join("/");
|
|
33247
33200
|
}
|
|
33248
33201
|
function extractPathParams(pattern) {
|
|
33249
33202
|
const params = [];
|
|
33250
|
-
const
|
|
33251
|
-
|
|
33252
|
-
|
|
33253
|
-
params.push(
|
|
33203
|
+
for (const segment of pattern.split("/")) {
|
|
33204
|
+
const p = segmentParam(segment);
|
|
33205
|
+
if (!p) continue;
|
|
33206
|
+
params.push({ name: p.name, schema: { ...PARAM_TYPE_SCHEMA[p.type] ?? { type: "string" } } });
|
|
33254
33207
|
}
|
|
33255
33208
|
return params;
|
|
33256
33209
|
}
|
|
@@ -33272,8 +33225,12 @@ function inferModelFromPath(pattern) {
|
|
|
33272
33225
|
if (rest.length === 1 && /^[[{]\.{0,3}\w+[\]}]$/.test(rest[0])) return candidate;
|
|
33273
33226
|
return null;
|
|
33274
33227
|
}
|
|
33228
|
+
function operationIdBase(method, openApiPath) {
|
|
33229
|
+
const clean = openApiPath.replace(/^\/+|\/+$/g, "").replace(/\//g, "_").replace(/\.\.\./g, "").replace(/[{}]/g, "").replace(/\*/g, "wildcard");
|
|
33230
|
+
return clean ? `${method}_${clean}` : method;
|
|
33231
|
+
}
|
|
33275
33232
|
function uniqueOperationId(method, openApiPath, seen) {
|
|
33276
|
-
const base = (method
|
|
33233
|
+
const base = operationIdBase(method, openApiPath);
|
|
33277
33234
|
let oid = base;
|
|
33278
33235
|
let n = 2;
|
|
33279
33236
|
while (seen.has(oid)) {
|
|
@@ -33283,13 +33240,25 @@ function uniqueOperationId(method, openApiPath, seen) {
|
|
|
33283
33240
|
seen.add(oid);
|
|
33284
33241
|
return oid;
|
|
33285
33242
|
}
|
|
33286
|
-
var WRITE_METHODS, registeredSchemes, registeredSchemas;
|
|
33243
|
+
var WRITE_METHODS, registeredSchemes, registeredSchemas, PARAM_TYPE_SCHEMA;
|
|
33287
33244
|
var init_generator = __esm({
|
|
33288
33245
|
"../swagger/src/generator.ts"() {
|
|
33289
33246
|
"use strict";
|
|
33290
33247
|
WRITE_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
|
|
33291
33248
|
registeredSchemes = {};
|
|
33292
33249
|
registeredSchemas = {};
|
|
33250
|
+
PARAM_TYPE_SCHEMA = {
|
|
33251
|
+
int: { type: "integer" },
|
|
33252
|
+
integer: { type: "integer" },
|
|
33253
|
+
float: { type: "number" },
|
|
33254
|
+
number: { type: "number" },
|
|
33255
|
+
uuid: { type: "string", format: "uuid" },
|
|
33256
|
+
slug: { type: "string", pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
|
|
33257
|
+
alpha: { type: "string", pattern: "^[A-Za-z]+$" },
|
|
33258
|
+
alnum: { type: "string", pattern: "^[A-Za-z0-9]+$" },
|
|
33259
|
+
path: { type: "string" },
|
|
33260
|
+
string: { type: "string" }
|
|
33261
|
+
};
|
|
33293
33262
|
}
|
|
33294
33263
|
});
|
|
33295
33264
|
|
|
@@ -33609,10 +33578,29 @@ function openBrowser(url) {
|
|
|
33609
33578
|
}, 2e3);
|
|
33610
33579
|
}
|
|
33611
33580
|
function resolvePortAndHost(config) {
|
|
33612
|
-
const
|
|
33581
|
+
const tina4Port = process.env.TINA4_PORT;
|
|
33582
|
+
const legacyPort = process.env.PORT;
|
|
33583
|
+
let port;
|
|
33584
|
+
if (config?.port !== void 0) {
|
|
33585
|
+
port = config.port;
|
|
33586
|
+
} else if (tina4Port && /^\d+$/.test(tina4Port)) {
|
|
33587
|
+
port = parseInt(tina4Port, 10);
|
|
33588
|
+
} else if (legacyPort && /^\d+$/.test(legacyPort)) {
|
|
33589
|
+
port = parseInt(legacyPort, 10);
|
|
33590
|
+
warnDeprecatedPort(port);
|
|
33591
|
+
} else {
|
|
33592
|
+
port = 7148;
|
|
33593
|
+
}
|
|
33613
33594
|
const host = config?.host ?? process.env.TINA4_HOST ?? process.env.HOST ?? "0.0.0.0";
|
|
33614
33595
|
return { port, host };
|
|
33615
33596
|
}
|
|
33597
|
+
function warnDeprecatedPort(port) {
|
|
33598
|
+
if (portDeprecationWarned) return;
|
|
33599
|
+
portDeprecationWarned = true;
|
|
33600
|
+
Log.warning(
|
|
33601
|
+
`PORT is deprecated and will be removed in 3.14 - use TINA4_PORT instead (binding port ${port} from PORT)`
|
|
33602
|
+
);
|
|
33603
|
+
}
|
|
33616
33604
|
function isBannerSuppressed() {
|
|
33617
33605
|
return isTruthy(process.env.TINA4_SUPPRESS);
|
|
33618
33606
|
}
|
|
@@ -33884,6 +33872,29 @@ function deployGallery(name) {
|
|
|
33884
33872
|
</body>
|
|
33885
33873
|
</html>`;
|
|
33886
33874
|
}
|
|
33875
|
+
function startLoopWatchdog() {
|
|
33876
|
+
const raw = (process.env.TINA4_LOOP_LAG_WARN_MS ?? "").trim();
|
|
33877
|
+
const threshold = /^\d+$/.test(raw) ? parseInt(raw, 10) : 250;
|
|
33878
|
+
if (threshold <= 0) {
|
|
33879
|
+
return { stop: () => {
|
|
33880
|
+
} };
|
|
33881
|
+
}
|
|
33882
|
+
let last = Date.now();
|
|
33883
|
+
let warned = 0;
|
|
33884
|
+
const timer = setInterval(() => {
|
|
33885
|
+
const now = Date.now();
|
|
33886
|
+
const lag = now - last - LOOP_WATCHDOG_TICK_MS;
|
|
33887
|
+
last = now;
|
|
33888
|
+
if (lag < threshold) return;
|
|
33889
|
+
warned++;
|
|
33890
|
+
if (warned > 5 && warned % 20 !== 0) return;
|
|
33891
|
+
Log.warning(
|
|
33892
|
+
`Event loop blocked for ${lag}ms. Node serves every request on one loop, so a handler doing CPU-bound work or synchronous I/O stalls all the others for that long. Move the work to Tina4's queue, or await it. Set TINA4_LOOP_LAG_WARN_MS to change the ${threshold}ms threshold, or 0 to silence.`
|
|
33893
|
+
);
|
|
33894
|
+
}, LOOP_WATCHDOG_TICK_MS);
|
|
33895
|
+
timer.unref();
|
|
33896
|
+
return { stop: () => clearInterval(timer) };
|
|
33897
|
+
}
|
|
33887
33898
|
async function start(config) {
|
|
33888
33899
|
const isManaged = process.argv.includes("--managed");
|
|
33889
33900
|
if (!isManaged && process.env.TINA4_OVERRIDE_CLIENT !== "true") {
|
|
@@ -34133,7 +34144,9 @@ async function startServer(config) {
|
|
|
34133
34144
|
const resolved = resolvePortAndHost(config);
|
|
34134
34145
|
const host = resolved.host;
|
|
34135
34146
|
let port = resolved.port;
|
|
34136
|
-
|
|
34147
|
+
if (!cluster.isWorker) {
|
|
34148
|
+
port = findAvailablePort(port);
|
|
34149
|
+
}
|
|
34137
34150
|
const isProduction = (process.env.TINA4_PRODUCTION ?? "").toLowerCase() === "true";
|
|
34138
34151
|
if (cluster.isPrimary && isProduction) {
|
|
34139
34152
|
const numCPUs = os2.cpus().length;
|
|
@@ -34349,7 +34362,20 @@ ${reset2}
|
|
|
34349
34362
|
await sessionAutoStart(rawReq, rawRes, req2);
|
|
34350
34363
|
await middleware.run(req2, res);
|
|
34351
34364
|
if (res.raw.writableEnded) return;
|
|
34352
|
-
|
|
34365
|
+
try {
|
|
34366
|
+
await req2.parseBody();
|
|
34367
|
+
} catch (err) {
|
|
34368
|
+
const status2 = err?.statusCode;
|
|
34369
|
+
if (typeof status2 === "number" && status2 >= 400 && status2 < 500) {
|
|
34370
|
+
if (!rawRes.writableEnded) {
|
|
34371
|
+
rawRes.statusCode = status2;
|
|
34372
|
+
rawRes.setHeader("content-type", "application/json");
|
|
34373
|
+
rawRes.end(JSON.stringify({ error: err.message }));
|
|
34374
|
+
}
|
|
34375
|
+
return;
|
|
34376
|
+
}
|
|
34377
|
+
throw err;
|
|
34378
|
+
}
|
|
34353
34379
|
const pathname = req2.path;
|
|
34354
34380
|
const reqStartTime = DevAdmin.isEnabled() ? Date.now() : 0;
|
|
34355
34381
|
const matchedPattern = { value: "" };
|
|
@@ -34548,8 +34574,10 @@ ${reset2}
|
|
|
34548
34574
|
};
|
|
34549
34575
|
process.on("SIGTERM", onSigterm);
|
|
34550
34576
|
process.on("SIGINT", onSigint);
|
|
34577
|
+
const loopWatchdog = startLoopWatchdog();
|
|
34551
34578
|
resolvePromise({
|
|
34552
34579
|
close: () => {
|
|
34580
|
+
loopWatchdog.stop();
|
|
34553
34581
|
process.off("SIGTERM", onSigterm);
|
|
34554
34582
|
process.off("SIGINT", onSigint);
|
|
34555
34583
|
stopAllBackgroundTasks();
|
|
@@ -34564,7 +34592,7 @@ ${reset2}
|
|
|
34564
34592
|
});
|
|
34565
34593
|
});
|
|
34566
34594
|
}
|
|
34567
|
-
var __filename, __dirname, BUILTIN_ERROR_TEMPLATES_DIR, BUILTIN_PUBLIC_DIR, swaggerAssetsEnabled, DEFAULT_SHUTDOWN_TIMEOUT_SECONDS, TINA4_VERSION2, frondCache, _LEGACY_ENV_VARS, TEMPLATE_PAGES_DIR, HTTP_REASON_PHRASES, templateCache, _dispatchFn, _serverHandle, FALLBACK_STAGES;
|
|
34595
|
+
var __filename, __dirname, BUILTIN_ERROR_TEMPLATES_DIR, BUILTIN_PUBLIC_DIR, swaggerAssetsEnabled, DEFAULT_SHUTDOWN_TIMEOUT_SECONDS, TINA4_VERSION2, frondCache, _LEGACY_ENV_VARS, portDeprecationWarned, TEMPLATE_PAGES_DIR, HTTP_REASON_PHRASES, templateCache, _dispatchFn, _serverHandle, LOOP_WATCHDOG_TICK_MS, FALLBACK_STAGES;
|
|
34568
34596
|
var init_server = __esm({
|
|
34569
34597
|
"../core/src/server.ts"() {
|
|
34570
34598
|
"use strict";
|
|
@@ -34617,6 +34645,7 @@ var init_server = __esm({
|
|
|
34617
34645
|
SWAGGER_VERSION: "TINA4_SWAGGER_VERSION",
|
|
34618
34646
|
ORM_PLURAL_TABLE_NAMES: "TINA4_ORM_PLURAL_TABLE_NAMES"
|
|
34619
34647
|
};
|
|
34648
|
+
portDeprecationWarned = false;
|
|
34620
34649
|
TEMPLATE_PAGES_DIR = "pages";
|
|
34621
34650
|
HTTP_REASON_PHRASES = {
|
|
34622
34651
|
100: "Continue",
|
|
@@ -34653,6 +34682,7 @@ var init_server = __esm({
|
|
|
34653
34682
|
templateCache = null;
|
|
34654
34683
|
_dispatchFn = null;
|
|
34655
34684
|
_serverHandle = null;
|
|
34685
|
+
LOOP_WATCHDOG_TICK_MS = 100;
|
|
34656
34686
|
FALLBACK_STAGES = [
|
|
34657
34687
|
serveTemplateFallback,
|
|
34658
34688
|
serveLandingPage,
|
|
@@ -34740,433 +34770,6 @@ var init_env = __esm({
|
|
|
34740
34770
|
}
|
|
34741
34771
|
});
|
|
34742
34772
|
|
|
34743
|
-
// ../core/src/scss.ts
|
|
34744
|
-
import { readFileSync as readFileSync23, writeFileSync as writeFileSync17, existsSync as existsSync25, mkdirSync as mkdirSync19, readdirSync as readdirSync18 } from "node:fs";
|
|
34745
|
-
import { join as join28, resolve as resolve20, dirname as dirname13 } from "node:path";
|
|
34746
|
-
function compileString(scss, importPaths, variables) {
|
|
34747
|
-
const imported = /* @__PURE__ */ new Set();
|
|
34748
|
-
scss = resolveImports(scss, importPaths, imported);
|
|
34749
|
-
scss = scss.replace(/(?<![:"'])\/\/[^\n]*/g, "");
|
|
34750
|
-
scss = extractVariables(scss, variables);
|
|
34751
|
-
const mixins = {};
|
|
34752
|
-
scss = extractMixins(scss, mixins);
|
|
34753
|
-
scss = resolveIncludes(scss, mixins);
|
|
34754
|
-
scss = resolveInterpolation(scss, variables);
|
|
34755
|
-
scss = substituteVariables(scss, variables);
|
|
34756
|
-
scss = evalMath(scss);
|
|
34757
|
-
scss = resolveColorFunctions(scss);
|
|
34758
|
-
const css = flattenNesting(scss);
|
|
34759
|
-
return cleanup(css);
|
|
34760
|
-
}
|
|
34761
|
-
function resolveImports(content, paths, imported) {
|
|
34762
|
-
return content.replace(/@import\s+["']?([^"';\n]+)["']?\s*;/g, (_match, name) => {
|
|
34763
|
-
name = name.trim();
|
|
34764
|
-
const candidates = [];
|
|
34765
|
-
for (const base of paths) {
|
|
34766
|
-
candidates.push(
|
|
34767
|
-
join28(base, `${name}.scss`),
|
|
34768
|
-
join28(base, `_${name}.scss`),
|
|
34769
|
-
join28(base, name)
|
|
34770
|
-
);
|
|
34771
|
-
}
|
|
34772
|
-
for (const candidate of candidates) {
|
|
34773
|
-
if (existsSync25(candidate) && !imported.has(candidate)) {
|
|
34774
|
-
imported.add(candidate);
|
|
34775
|
-
const fileContent = readFileSync23(candidate, "utf-8");
|
|
34776
|
-
return resolveImports(fileContent, [dirname13(candidate), ...paths], imported);
|
|
34777
|
-
}
|
|
34778
|
-
}
|
|
34779
|
-
return `/* IMPORT NOT FOUND: ${name} */`;
|
|
34780
|
-
});
|
|
34781
|
-
}
|
|
34782
|
-
function stripVariableFlags(value) {
|
|
34783
|
-
let declaresDefault = false;
|
|
34784
|
-
for (; ; ) {
|
|
34785
|
-
const match = VARIABLE_FLAG.exec(value);
|
|
34786
|
-
if (match === null) return [value.trim(), declaresDefault];
|
|
34787
|
-
if (match[1] === "default") declaresDefault = true;
|
|
34788
|
-
value = value.slice(0, match.index);
|
|
34789
|
-
}
|
|
34790
|
-
}
|
|
34791
|
-
function extractVariables(scss, variables) {
|
|
34792
|
-
return scss.replace(/\$([a-zA-Z_][\w-]*)\s*:\s*([^;]+);/g, (_m, name, value) => {
|
|
34793
|
-
const [stripped, declaresDefault] = stripVariableFlags(value.trim());
|
|
34794
|
-
if (declaresDefault && (variables[name] ?? "null") !== "null") {
|
|
34795
|
-
return "";
|
|
34796
|
-
}
|
|
34797
|
-
let resolved = stripped;
|
|
34798
|
-
for (const [vName, vVal] of Object.entries(variables)) {
|
|
34799
|
-
resolved = resolved.replaceAll(`$${vName}`, vVal);
|
|
34800
|
-
}
|
|
34801
|
-
variables[name] = resolved;
|
|
34802
|
-
return "";
|
|
34803
|
-
});
|
|
34804
|
-
}
|
|
34805
|
-
function substituteVariables(scss, variables) {
|
|
34806
|
-
const sorted = Object.keys(variables).sort((a, b) => b.length - a.length);
|
|
34807
|
-
for (const name of sorted) {
|
|
34808
|
-
scss = scss.replaceAll(`$${name}`, variables[name]);
|
|
34809
|
-
}
|
|
34810
|
-
return scss;
|
|
34811
|
-
}
|
|
34812
|
-
function resolveInterpolation(scss, variables) {
|
|
34813
|
-
const sorted = Object.keys(variables).sort((a, b) => b.length - a.length);
|
|
34814
|
-
return scss.replace(/#\{([^{}]*)\}/g, (_m, inner) => {
|
|
34815
|
-
let resolved = inner.trim();
|
|
34816
|
-
for (const name of sorted) {
|
|
34817
|
-
resolved = resolved.replaceAll(`$${name}`, variables[name]);
|
|
34818
|
-
}
|
|
34819
|
-
return resolved;
|
|
34820
|
-
});
|
|
34821
|
-
}
|
|
34822
|
-
function extractMixins(scss, mixins) {
|
|
34823
|
-
const pattern = /@mixin\s+([\w-]+)\s*(?:\(([^)]*)\))?\s*\{/g;
|
|
34824
|
-
let match;
|
|
34825
|
-
const locations = [];
|
|
34826
|
-
while ((match = pattern.exec(scss)) !== null) {
|
|
34827
|
-
const name = match[1];
|
|
34828
|
-
const paramsStr = match[2] ?? "";
|
|
34829
|
-
const params = paramsStr.split(",").map((p) => p.trim().replace(/^\$/, "")).filter(Boolean);
|
|
34830
|
-
const bodyStart = match.index + match[0].length;
|
|
34831
|
-
const body = findBlock(scss, bodyStart);
|
|
34832
|
-
if (body !== null) {
|
|
34833
|
-
mixins[name] = { params, body };
|
|
34834
|
-
locations.push({
|
|
34835
|
-
start: match.index,
|
|
34836
|
-
end: bodyStart + body.length + 1,
|
|
34837
|
-
name
|
|
34838
|
-
});
|
|
34839
|
-
}
|
|
34840
|
-
}
|
|
34841
|
-
let result = scss;
|
|
34842
|
-
for (const loc of locations.reverse()) {
|
|
34843
|
-
result = result.slice(0, loc.start) + result.slice(loc.end);
|
|
34844
|
-
}
|
|
34845
|
-
return result;
|
|
34846
|
-
}
|
|
34847
|
-
function resolveIncludes(scss, mixins) {
|
|
34848
|
-
return scss.replace(
|
|
34849
|
-
/@include\s+([\w-]+)\s*(?:\(([^)]*)\))?\s*;/g,
|
|
34850
|
-
(_m, name, argsStr) => {
|
|
34851
|
-
if (!(name in mixins)) {
|
|
34852
|
-
return `/* MIXIN NOT FOUND: ${name} */`;
|
|
34853
|
-
}
|
|
34854
|
-
const mixin = mixins[name];
|
|
34855
|
-
const args = argsStr ? argsStr.split(",").map((a) => a.trim()).filter(Boolean) : [];
|
|
34856
|
-
let body = mixin.body;
|
|
34857
|
-
for (let i = 0; i < mixin.params.length; i++) {
|
|
34858
|
-
const paramName = mixin.params[i].split(":")[0].trim();
|
|
34859
|
-
const defaultVal = mixin.params[i].includes(":") ? mixin.params[i].split(":").slice(1).join(":").trim() : "";
|
|
34860
|
-
const value = i < args.length ? args[i] : defaultVal;
|
|
34861
|
-
body = body.replaceAll(`$${paramName}`, value);
|
|
34862
|
-
}
|
|
34863
|
-
return body;
|
|
34864
|
-
}
|
|
34865
|
-
);
|
|
34866
|
-
}
|
|
34867
|
-
function evalMath(scss) {
|
|
34868
|
-
const placeholders = [];
|
|
34869
|
-
const masked = scss.replace(/calc\([^()]*\)/g, (m) => {
|
|
34870
|
-
placeholders.push(m);
|
|
34871
|
-
return `\0CALC${placeholders.length - 1}\0`;
|
|
34872
|
-
});
|
|
34873
|
-
const folded = masked.replace(
|
|
34874
|
-
/([\d.]+)([a-z%]*)\s*([+\-*/])\s*([\d.]+)([a-z%]*)/g,
|
|
34875
|
-
(full, n1, u1, op, n2, u2) => {
|
|
34876
|
-
const num1 = parseFloat(n1);
|
|
34877
|
-
const num2 = parseFloat(n2);
|
|
34878
|
-
if (Number.isNaN(num1) || Number.isNaN(num2)) return full;
|
|
34879
|
-
const unit1 = u1 || "";
|
|
34880
|
-
const unit2 = u2 || "";
|
|
34881
|
-
let unit;
|
|
34882
|
-
if (unit1 === unit2) {
|
|
34883
|
-
unit = unit1;
|
|
34884
|
-
} else if ((op === "*" || op === "/") && unit1 === "") {
|
|
34885
|
-
unit = unit2;
|
|
34886
|
-
} else if ((op === "*" || op === "/") && unit2 === "") {
|
|
34887
|
-
unit = unit1;
|
|
34888
|
-
} else {
|
|
34889
|
-
return full;
|
|
34890
|
-
}
|
|
34891
|
-
let result;
|
|
34892
|
-
switch (op) {
|
|
34893
|
-
case "+":
|
|
34894
|
-
result = num1 + num2;
|
|
34895
|
-
break;
|
|
34896
|
-
case "-":
|
|
34897
|
-
result = num1 - num2;
|
|
34898
|
-
break;
|
|
34899
|
-
case "*":
|
|
34900
|
-
result = num1 * num2;
|
|
34901
|
-
break;
|
|
34902
|
-
case "/":
|
|
34903
|
-
if (num2 === 0) return full;
|
|
34904
|
-
result = num1 / num2;
|
|
34905
|
-
break;
|
|
34906
|
-
default:
|
|
34907
|
-
return full;
|
|
34908
|
-
}
|
|
34909
|
-
if (result === Math.floor(result)) {
|
|
34910
|
-
return `${Math.floor(result)}${unit}`;
|
|
34911
|
-
}
|
|
34912
|
-
return `${result.toFixed(2)}${unit}`;
|
|
34913
|
-
}
|
|
34914
|
-
);
|
|
34915
|
-
return folded.replace(/\x00CALC(\d+)\x00/g, (_m, idx) => {
|
|
34916
|
-
return placeholders[parseInt(idx, 10)];
|
|
34917
|
-
});
|
|
34918
|
-
}
|
|
34919
|
-
function resolveColorFunctions(scss) {
|
|
34920
|
-
scss = scss.replace(
|
|
34921
|
-
/lighten\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/g,
|
|
34922
|
-
(_m, color, amt) => adjustLightness(color.trim(), parseFloat(amt.trim().replace(/%$/, "")) / 100)
|
|
34923
|
-
);
|
|
34924
|
-
scss = scss.replace(
|
|
34925
|
-
/darken\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/g,
|
|
34926
|
-
(_m, color, amt) => adjustLightness(color.trim(), -(parseFloat(amt.trim().replace(/%$/, "")) / 100))
|
|
34927
|
-
);
|
|
34928
|
-
scss = scss.replace(/rgba\(\s*(#[0-9a-fA-F]{3,8})\s*,\s*([\d.]+)\s*\)/g, (whole, hex, alpha) => {
|
|
34929
|
-
const rgb = hexToRgb(hex);
|
|
34930
|
-
return rgb === null ? whole : `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${alpha.trim()})`;
|
|
34931
|
-
});
|
|
34932
|
-
scss = scss.replace(/rgb\(\s*(#[0-9a-fA-F]{3,8})\s*\)/g, (whole, hex) => {
|
|
34933
|
-
const rgb = hexToRgb(hex);
|
|
34934
|
-
return rgb === null ? whole : `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
|
|
34935
|
-
});
|
|
34936
|
-
scss = scss.replace(
|
|
34937
|
-
/mix\(\s*(#[0-9a-fA-F]{3,8})\s*,\s*(#[0-9a-fA-F]{3,8})\s*(?:,\s*([\d.]+%?)\s*)?\)/g,
|
|
34938
|
-
(whole, h1, h2, weight) => {
|
|
34939
|
-
const c1 = hexToRgb(h1);
|
|
34940
|
-
const c2 = hexToRgb(h2);
|
|
34941
|
-
if (c1 === null || c2 === null) return whole;
|
|
34942
|
-
const w = weight ? parseFloat(weight.replace(/%$/, "")) / 100 : 0.5;
|
|
34943
|
-
const mixed = [0, 1, 2].map((i) => Math.round(c1[i] * w + c2[i] * (1 - w)));
|
|
34944
|
-
return `#${mixed.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
34945
|
-
}
|
|
34946
|
-
);
|
|
34947
|
-
return scss;
|
|
34948
|
-
}
|
|
34949
|
-
function hexToRgb(color) {
|
|
34950
|
-
let c = color.trim().replace(/^#/, "");
|
|
34951
|
-
if (c.length === 3) {
|
|
34952
|
-
c = c.split("").map((ch) => ch + ch).join("");
|
|
34953
|
-
}
|
|
34954
|
-
if (!/^[0-9a-fA-F]{6}$/.test(c)) return null;
|
|
34955
|
-
return [parseInt(c.slice(0, 2), 16), parseInt(c.slice(2, 4), 16), parseInt(c.slice(4, 6), 16)];
|
|
34956
|
-
}
|
|
34957
|
-
function adjustLightness(color, amount) {
|
|
34958
|
-
const rgb = hexToRgb(color);
|
|
34959
|
-
if (rgb === null) return color;
|
|
34960
|
-
let [r, g, b] = rgb.map((v) => v / 255);
|
|
34961
|
-
const max = Math.max(r, g, b);
|
|
34962
|
-
const min = Math.min(r, g, b);
|
|
34963
|
-
let l = (max + min) / 2;
|
|
34964
|
-
const d = max - min;
|
|
34965
|
-
let h = 0;
|
|
34966
|
-
let s = 0;
|
|
34967
|
-
if (d !== 0) {
|
|
34968
|
-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
34969
|
-
if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
|
|
34970
|
-
else if (max === g) h = (b - r) / d + 2;
|
|
34971
|
-
else h = (r - g) / d + 4;
|
|
34972
|
-
h /= 6;
|
|
34973
|
-
}
|
|
34974
|
-
l = Math.max(0, Math.min(1, l + amount));
|
|
34975
|
-
if (s === 0) {
|
|
34976
|
-
r = g = b = l;
|
|
34977
|
-
} else {
|
|
34978
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
34979
|
-
const p = 2 * l - q;
|
|
34980
|
-
r = hueToRgb(p, q, h + 1 / 3);
|
|
34981
|
-
g = hueToRgb(p, q, h);
|
|
34982
|
-
b = hueToRgb(p, q, h - 1 / 3);
|
|
34983
|
-
}
|
|
34984
|
-
const hex = (v) => Math.trunc(v * 255).toString(16).padStart(2, "0");
|
|
34985
|
-
return `#${hex(r)}${hex(g)}${hex(b)}`;
|
|
34986
|
-
}
|
|
34987
|
-
function hueToRgb(p, q, t) {
|
|
34988
|
-
if (t < 0) t += 1;
|
|
34989
|
-
if (t > 1) t -= 1;
|
|
34990
|
-
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
34991
|
-
if (t < 1 / 2) return q;
|
|
34992
|
-
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
34993
|
-
return p;
|
|
34994
|
-
}
|
|
34995
|
-
function flattenNesting(scss) {
|
|
34996
|
-
const output = [];
|
|
34997
|
-
flattenBlock(scss, [], output);
|
|
34998
|
-
return output.join("\n");
|
|
34999
|
-
}
|
|
35000
|
-
function flattenBlock(content, parentSelectors, output) {
|
|
35001
|
-
let pos = 0;
|
|
35002
|
-
const properties = [];
|
|
35003
|
-
while (pos < content.length) {
|
|
35004
|
-
while (pos < content.length && /[\s]/.test(content[pos])) {
|
|
35005
|
-
pos++;
|
|
35006
|
-
}
|
|
35007
|
-
if (pos >= content.length) break;
|
|
35008
|
-
if (content[pos] === "/" && content[pos + 1] === "*") {
|
|
35009
|
-
const end = content.indexOf("*/", pos + 2);
|
|
35010
|
-
if (end === -1) break;
|
|
35011
|
-
output.push(content.slice(pos, end + 2));
|
|
35012
|
-
pos = end + 2;
|
|
35013
|
-
continue;
|
|
35014
|
-
}
|
|
35015
|
-
if (content.slice(pos, pos + 6) === "@media") {
|
|
35016
|
-
const brace = content.indexOf("{", pos);
|
|
35017
|
-
if (brace === -1) break;
|
|
35018
|
-
const mediaQuery = content.slice(pos, brace).trim();
|
|
35019
|
-
const body = findBlock(content, brace + 1);
|
|
35020
|
-
if (body === null) break;
|
|
35021
|
-
pos = brace + 1 + body.length + 1;
|
|
35022
|
-
const innerOutput = [];
|
|
35023
|
-
flattenBlock(body, parentSelectors, innerOutput);
|
|
35024
|
-
if (innerOutput.length > 0) {
|
|
35025
|
-
output.push(`${mediaQuery} {`);
|
|
35026
|
-
for (const line of innerOutput) {
|
|
35027
|
-
output.push(` ${line}`);
|
|
35028
|
-
}
|
|
35029
|
-
output.push("}");
|
|
35030
|
-
}
|
|
35031
|
-
continue;
|
|
35032
|
-
}
|
|
35033
|
-
const bracePos = content.indexOf("{", pos);
|
|
35034
|
-
const semiPos = content.indexOf(";", pos);
|
|
35035
|
-
if (semiPos !== -1 && (bracePos === -1 || semiPos < bracePos)) {
|
|
35036
|
-
const prop = content.slice(pos, semiPos).trim();
|
|
35037
|
-
if (prop && !prop.startsWith("@")) {
|
|
35038
|
-
properties.push(prop);
|
|
35039
|
-
}
|
|
35040
|
-
pos = semiPos + 1;
|
|
35041
|
-
continue;
|
|
35042
|
-
}
|
|
35043
|
-
if (bracePos !== -1) {
|
|
35044
|
-
const selectorText = content.slice(pos, bracePos).trim();
|
|
35045
|
-
const body = findBlock(content, bracePos + 1);
|
|
35046
|
-
if (body === null) break;
|
|
35047
|
-
pos = bracePos + 1 + body.length + 1;
|
|
35048
|
-
if (!selectorText) continue;
|
|
35049
|
-
const selectors = selectorText.split(",").map((s) => s.trim());
|
|
35050
|
-
const newSelectors = [];
|
|
35051
|
-
for (const sel of selectors) {
|
|
35052
|
-
if (parentSelectors.length > 0) {
|
|
35053
|
-
for (const parent of parentSelectors) {
|
|
35054
|
-
if (sel.includes("&")) {
|
|
35055
|
-
newSelectors.push(sel.replace(/&/g, parent));
|
|
35056
|
-
} else {
|
|
35057
|
-
newSelectors.push(`${parent} ${sel}`);
|
|
35058
|
-
}
|
|
35059
|
-
}
|
|
35060
|
-
} else {
|
|
35061
|
-
newSelectors.push(sel);
|
|
35062
|
-
}
|
|
35063
|
-
}
|
|
35064
|
-
flattenBlock(body, newSelectors, output);
|
|
35065
|
-
continue;
|
|
35066
|
-
}
|
|
35067
|
-
const remaining = content.slice(pos).trim();
|
|
35068
|
-
if (remaining) {
|
|
35069
|
-
properties.push(remaining);
|
|
35070
|
-
}
|
|
35071
|
-
break;
|
|
35072
|
-
}
|
|
35073
|
-
if (properties.length > 0 && parentSelectors.length > 0) {
|
|
35074
|
-
const selectorStr = parentSelectors.join(", ");
|
|
35075
|
-
output.push(`${selectorStr} {`);
|
|
35076
|
-
for (const prop of properties) {
|
|
35077
|
-
output.push(` ${prop};`);
|
|
35078
|
-
}
|
|
35079
|
-
output.push("}");
|
|
35080
|
-
}
|
|
35081
|
-
}
|
|
35082
|
-
function findBlock(content, start2) {
|
|
35083
|
-
let depth = 1;
|
|
35084
|
-
let pos = start2;
|
|
35085
|
-
while (pos < content.length && depth > 0) {
|
|
35086
|
-
if (content[pos] === "{") depth++;
|
|
35087
|
-
else if (content[pos] === "}") depth--;
|
|
35088
|
-
if (depth > 0) pos++;
|
|
35089
|
-
}
|
|
35090
|
-
return depth === 0 ? content.slice(start2, pos) : null;
|
|
35091
|
-
}
|
|
35092
|
-
function cleanup(css) {
|
|
35093
|
-
css = css.replace(/[^{}]+\{\s*\}/g, "");
|
|
35094
|
-
css = css.replace(/\n{3,}/g, "\n\n");
|
|
35095
|
-
css = css.split("\n").map((line) => line.trimEnd()).join("\n");
|
|
35096
|
-
return css.trim() + "\n";
|
|
35097
|
-
}
|
|
35098
|
-
var ScssCompiler, VARIABLE_FLAG;
|
|
35099
|
-
var init_scss = __esm({
|
|
35100
|
-
"../core/src/scss.ts"() {
|
|
35101
|
-
"use strict";
|
|
35102
|
-
ScssCompiler = class {
|
|
35103
|
-
_importPaths;
|
|
35104
|
-
_variables;
|
|
35105
|
-
constructor(config) {
|
|
35106
|
-
this._importPaths = config?.importPaths ? [...config.importPaths] : [];
|
|
35107
|
-
this._variables = config?.variables ? { ...config.variables } : {};
|
|
35108
|
-
}
|
|
35109
|
-
/** Compile an SCSS string to CSS. */
|
|
35110
|
-
compile(source) {
|
|
35111
|
-
return compileString(source, this._importPaths, { ...this._variables });
|
|
35112
|
-
}
|
|
35113
|
-
/** Compile an SCSS file to CSS. */
|
|
35114
|
-
compileFile(filePath) {
|
|
35115
|
-
const absPath = resolve20(filePath);
|
|
35116
|
-
const content = readFileSync23(absPath, "utf-8");
|
|
35117
|
-
const paths = [dirname13(absPath), ...this._importPaths];
|
|
35118
|
-
return compileString(content, paths, { ...this._variables });
|
|
35119
|
-
}
|
|
35120
|
-
/** Add a directory to the import resolution path. */
|
|
35121
|
-
addImportPath(path8) {
|
|
35122
|
-
this._importPaths.push(resolve20(path8));
|
|
35123
|
-
}
|
|
35124
|
-
/** Set or override an SCSS variable. */
|
|
35125
|
-
setVariable(name, value) {
|
|
35126
|
-
const key = name.startsWith("$") ? name.slice(1) : name;
|
|
35127
|
-
this._variables[key] = value;
|
|
35128
|
-
}
|
|
35129
|
-
/** Compile all .scss files in a directory into a single CSS output file. */
|
|
35130
|
-
compileScss(scssDir = "src/scss", output = "src/public/css/default.css", minify = false) {
|
|
35131
|
-
const absDir = resolve20(scssDir);
|
|
35132
|
-
if (!existsSync25(absDir)) return "";
|
|
35133
|
-
const files = readdirSync18(absDir).filter((f) => f.endsWith(".scss") && !f.startsWith("_")).sort().map((f) => join28(absDir, f));
|
|
35134
|
-
if (files.length === 0) return "";
|
|
35135
|
-
const paths = [absDir, ...this._importPaths];
|
|
35136
|
-
const imported = /* @__PURE__ */ new Set();
|
|
35137
|
-
let merged = "";
|
|
35138
|
-
for (const file of files) {
|
|
35139
|
-
const content = readFileSync23(file, "utf-8");
|
|
35140
|
-
imported.add(file);
|
|
35141
|
-
merged += resolveImports(content, paths, imported) + "\n";
|
|
35142
|
-
}
|
|
35143
|
-
let css = compileString(merged, paths, { ...this._variables });
|
|
35144
|
-
if (minify) {
|
|
35145
|
-
css = css.replace(/\/\*.*?\*\//gs, "");
|
|
35146
|
-
css = css.replace(/\s+/g, " ");
|
|
35147
|
-
css = css.replace(/\s*([{}:;,])\s*/g, "$1");
|
|
35148
|
-
css = css.replace(/;}/g, "}");
|
|
35149
|
-
css = css.trim();
|
|
35150
|
-
}
|
|
35151
|
-
const absOutput = resolve20(output);
|
|
35152
|
-
const outDir = dirname13(absOutput);
|
|
35153
|
-
if (!existsSync25(outDir)) mkdirSync19(outDir, { recursive: true });
|
|
35154
|
-
let existing = null;
|
|
35155
|
-
try {
|
|
35156
|
-
existing = existsSync25(absOutput) ? readFileSync23(absOutput, "utf-8") : null;
|
|
35157
|
-
} catch {
|
|
35158
|
-
existing = null;
|
|
35159
|
-
}
|
|
35160
|
-
if (existing !== css) {
|
|
35161
|
-
writeFileSync17(absOutput, css, "utf-8");
|
|
35162
|
-
}
|
|
35163
|
-
return css;
|
|
35164
|
-
}
|
|
35165
|
-
};
|
|
35166
|
-
VARIABLE_FLAG = /\s*!(default|global)\s*$/;
|
|
35167
|
-
}
|
|
35168
|
-
});
|
|
35169
|
-
|
|
35170
34773
|
// ../core/src/mqttMessage.ts
|
|
35171
34774
|
var MqttMessage;
|
|
35172
34775
|
var init_mqttMessage = __esm({
|
|
@@ -35240,7 +34843,7 @@ var init_mqttMessage = __esm({
|
|
|
35240
34843
|
import net2 from "node:net";
|
|
35241
34844
|
import tls from "node:tls";
|
|
35242
34845
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
35243
|
-
import { existsSync as
|
|
34846
|
+
import { existsSync as existsSync25, readFileSync as readFileSync23 } from "node:fs";
|
|
35244
34847
|
var MqttError, MqttTimeoutError, CONNECT, CONNACK, PUBLISH, PUBACK, SUBSCRIBE, SUBACK, PINGREQ, PINGRESP, DISCONNECT, PROTOCOL_LEVEL, DEFAULT_PORT, DEFAULT_TLS_PORT, DEFAULT_URL, DEFAULT_KEEPALIVE, SUBSCRIPTION_REFUSED, MAX_REMAINING_LENGTH, QOS2_REFUSED_MESSAGE, CONNACK_RETURN_CODES, Mqtt;
|
|
35245
34848
|
var init_mqtt = __esm({
|
|
35246
34849
|
"../core/src/mqtt.ts"() {
|
|
@@ -35443,7 +35046,7 @@ var init_mqtt = __esm({
|
|
|
35443
35046
|
*/
|
|
35444
35047
|
async connect() {
|
|
35445
35048
|
this.closeSocket();
|
|
35446
|
-
if (this.secure && this.tlsVerify && this.caFile && !
|
|
35049
|
+
if (this.secure && this.tlsVerify && this.caFile && !existsSync25(this.caFile)) {
|
|
35447
35050
|
throw new MqttError(
|
|
35448
35051
|
`MQTT CA file not found: ${this.caFile} -- TINA4_MQTT_CA_FILE (or caFile) must point at the broker's CA certificate in PEM form`
|
|
35449
35052
|
);
|
|
@@ -35680,7 +35283,7 @@ var init_mqtt = __esm({
|
|
|
35680
35283
|
* a later client.
|
|
35681
35284
|
*/
|
|
35682
35285
|
openSocket() {
|
|
35683
|
-
return new Promise((
|
|
35286
|
+
return new Promise((resolve30, reject) => {
|
|
35684
35287
|
let settled = false;
|
|
35685
35288
|
const settle = (fn) => {
|
|
35686
35289
|
if (settled) return;
|
|
@@ -35705,10 +35308,10 @@ var init_mqtt = __esm({
|
|
|
35705
35308
|
servername: this.host,
|
|
35706
35309
|
rejectUnauthorized: this.tlsVerify
|
|
35707
35310
|
};
|
|
35708
|
-
if (this.tlsVerify && this.caFile) opts.ca =
|
|
35709
|
-
sock = tls.connect(opts, () => settle(() =>
|
|
35311
|
+
if (this.tlsVerify && this.caFile) opts.ca = readFileSync23(this.caFile);
|
|
35312
|
+
sock = tls.connect(opts, () => settle(() => resolve30(sock)));
|
|
35710
35313
|
} else {
|
|
35711
|
-
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() =>
|
|
35314
|
+
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve30(sock)));
|
|
35712
35315
|
}
|
|
35713
35316
|
sock.once("error", (err) => {
|
|
35714
35317
|
settle(() => {
|
|
@@ -35747,13 +35350,13 @@ var init_mqtt = __esm({
|
|
|
35747
35350
|
writePacket(header, body) {
|
|
35748
35351
|
if (this.socket === null) return Promise.reject(new MqttError("not connected to an MQTT broker"));
|
|
35749
35352
|
const packet = Buffer.concat([Buffer.from([header]), _Mqtt.encodeRemainingLength(body.length), body]);
|
|
35750
|
-
return new Promise((
|
|
35353
|
+
return new Promise((resolve30, reject) => {
|
|
35751
35354
|
this.socket.write(packet, (err) => {
|
|
35752
35355
|
if (err) {
|
|
35753
35356
|
reject(new MqttError(`MQTT write failed: ${err.message}`));
|
|
35754
35357
|
} else {
|
|
35755
35358
|
this.lastWriteAt = Date.now();
|
|
35756
|
-
|
|
35359
|
+
resolve30();
|
|
35757
35360
|
}
|
|
35758
35361
|
});
|
|
35759
35362
|
});
|
|
@@ -35786,7 +35389,7 @@ var init_mqtt = __esm({
|
|
|
35786
35389
|
if (this.readBuffer.length >= need) return Promise.resolve(this.take(need));
|
|
35787
35390
|
if (this.socket === null) return Promise.reject(this.socketError ?? new MqttError("not connected to an MQTT broker"));
|
|
35788
35391
|
if (this.socketError !== null) return Promise.reject(this.socketError);
|
|
35789
|
-
return new Promise((
|
|
35392
|
+
return new Promise((resolve30, reject) => {
|
|
35790
35393
|
let timer = null;
|
|
35791
35394
|
if (deadline !== null) {
|
|
35792
35395
|
const remaining = deadline - Date.now();
|
|
@@ -35801,7 +35404,7 @@ var init_mqtt = __esm({
|
|
|
35801
35404
|
}
|
|
35802
35405
|
}, remaining);
|
|
35803
35406
|
}
|
|
35804
|
-
this.waiter = { need, resolve:
|
|
35407
|
+
this.waiter = { need, resolve: resolve30, reject, timer };
|
|
35805
35408
|
this.serviceWaiter();
|
|
35806
35409
|
});
|
|
35807
35410
|
}
|
|
@@ -35925,8 +35528,8 @@ var init_mqtt = __esm({
|
|
|
35925
35528
|
});
|
|
35926
35529
|
|
|
35927
35530
|
// ../core/src/service.ts
|
|
35928
|
-
import { readdirSync as
|
|
35929
|
-
import { join as
|
|
35531
|
+
import { readdirSync as readdirSync18, statSync as statSync18, watchFile, unwatchFile } from "node:fs";
|
|
35532
|
+
import { join as join28, extname as extname8 } from "node:path";
|
|
35930
35533
|
import { pathToFileURL } from "node:url";
|
|
35931
35534
|
function matchCronField(field, value) {
|
|
35932
35535
|
if (field === "*") return true;
|
|
@@ -36092,14 +35695,14 @@ var init_service = __esm({
|
|
|
36092
35695
|
const discovered = [];
|
|
36093
35696
|
let entries;
|
|
36094
35697
|
try {
|
|
36095
|
-
entries =
|
|
35698
|
+
entries = readdirSync18(dir);
|
|
36096
35699
|
} catch {
|
|
36097
35700
|
return discovered;
|
|
36098
35701
|
}
|
|
36099
35702
|
for (const entry of entries) {
|
|
36100
35703
|
const ext = extname8(entry);
|
|
36101
35704
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
36102
|
-
const fullPath =
|
|
35705
|
+
const fullPath = join28(dir, entry);
|
|
36103
35706
|
const stat = statSync18(fullPath);
|
|
36104
35707
|
if (!stat.isFile()) continue;
|
|
36105
35708
|
try {
|
|
@@ -36208,14 +35811,14 @@ var init_service = __esm({
|
|
|
36208
35811
|
const dir = serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
36209
35812
|
let entries;
|
|
36210
35813
|
try {
|
|
36211
|
-
entries =
|
|
35814
|
+
entries = readdirSync18(dir);
|
|
36212
35815
|
} catch {
|
|
36213
35816
|
return;
|
|
36214
35817
|
}
|
|
36215
35818
|
for (const entry of entries) {
|
|
36216
35819
|
const ext = extname8(entry);
|
|
36217
35820
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
36218
|
-
const fullPath =
|
|
35821
|
+
const fullPath = join28(dir, entry);
|
|
36219
35822
|
if (watchedFiles.has(fullPath)) continue;
|
|
36220
35823
|
watchedFiles.add(fullPath);
|
|
36221
35824
|
watchFile(fullPath, { interval: 1e3 }, async () => {
|
|
@@ -36259,7 +35862,7 @@ import https from "node:https";
|
|
|
36259
35862
|
import { URL as URL2 } from "node:url";
|
|
36260
35863
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
36261
35864
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
36262
|
-
import { basename as
|
|
35865
|
+
import { basename as basename5 } from "node:path";
|
|
36263
35866
|
import { pipeline } from "node:stream/promises";
|
|
36264
35867
|
function sameOrigin(urlA, urlB) {
|
|
36265
35868
|
try {
|
|
@@ -36549,7 +36152,7 @@ var init_api = __esm({
|
|
|
36549
36152
|
error: err instanceof Error ? err.message : String(err)
|
|
36550
36153
|
};
|
|
36551
36154
|
}
|
|
36552
|
-
uploadName = filename ||
|
|
36155
|
+
uploadName = filename || basename5(filePath);
|
|
36553
36156
|
} else {
|
|
36554
36157
|
return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
|
|
36555
36158
|
}
|
|
@@ -36764,12 +36367,12 @@ var init_api = __esm({
|
|
|
36764
36367
|
* authenticate to.
|
|
36765
36368
|
*/
|
|
36766
36369
|
performRequest(method, url, headers, data, redirectsLeft) {
|
|
36767
|
-
return new Promise((
|
|
36370
|
+
return new Promise((resolve30) => {
|
|
36768
36371
|
let parsed;
|
|
36769
36372
|
try {
|
|
36770
36373
|
parsed = new URL2(url);
|
|
36771
36374
|
} catch (err) {
|
|
36772
|
-
|
|
36375
|
+
resolve30({ kind: "error", error: err instanceof Error ? err.message : String(err) });
|
|
36773
36376
|
return;
|
|
36774
36377
|
}
|
|
36775
36378
|
const isHttps = parsed.protocol === "https:";
|
|
@@ -36794,7 +36397,7 @@ var init_api = __esm({
|
|
|
36794
36397
|
try {
|
|
36795
36398
|
nextUrl = new URL2(location, url).toString();
|
|
36796
36399
|
} catch {
|
|
36797
|
-
|
|
36400
|
+
resolve30({ kind: "response", res });
|
|
36798
36401
|
return;
|
|
36799
36402
|
}
|
|
36800
36403
|
const crossOrigin = !sameOrigin(url, nextUrl);
|
|
@@ -36812,17 +36415,17 @@ var init_api = __esm({
|
|
|
36812
36415
|
deleteHeaderCaseInsensitive(nextHeaders, name);
|
|
36813
36416
|
}
|
|
36814
36417
|
}
|
|
36815
|
-
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(
|
|
36418
|
+
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(resolve30);
|
|
36816
36419
|
return;
|
|
36817
36420
|
}
|
|
36818
|
-
|
|
36421
|
+
resolve30({ kind: "response", res });
|
|
36819
36422
|
});
|
|
36820
36423
|
req2.on("timeout", () => {
|
|
36821
36424
|
req2.destroy();
|
|
36822
|
-
|
|
36425
|
+
resolve30({ kind: "error", error: `Request timed out after ${this.timeout}s` });
|
|
36823
36426
|
});
|
|
36824
36427
|
req2.on("error", (err) => {
|
|
36825
|
-
|
|
36428
|
+
resolve30({ kind: "error", error: err.message });
|
|
36826
36429
|
});
|
|
36827
36430
|
if (data) {
|
|
36828
36431
|
req2.write(data);
|
|
@@ -36832,7 +36435,7 @@ var init_api = __esm({
|
|
|
36832
36435
|
}
|
|
36833
36436
|
/** Buffer a response body, parse JSON if possible, and store cookies. */
|
|
36834
36437
|
readResponse(res) {
|
|
36835
|
-
return new Promise((
|
|
36438
|
+
return new Promise((resolve30) => {
|
|
36836
36439
|
const chunks = [];
|
|
36837
36440
|
res.on("data", (chunk) => {
|
|
36838
36441
|
chunks.push(chunk);
|
|
@@ -36847,7 +36450,7 @@ var init_api = __esm({
|
|
|
36847
36450
|
} catch {
|
|
36848
36451
|
parsed = raw;
|
|
36849
36452
|
}
|
|
36850
|
-
|
|
36453
|
+
resolve30({
|
|
36851
36454
|
http_code: res.statusCode ?? null,
|
|
36852
36455
|
body: parsed,
|
|
36853
36456
|
headers: respHeaders,
|
|
@@ -36855,7 +36458,7 @@ var init_api = __esm({
|
|
|
36855
36458
|
});
|
|
36856
36459
|
});
|
|
36857
36460
|
res.on("error", (err) => {
|
|
36858
|
-
|
|
36461
|
+
resolve30({ http_code: null, body: null, headers: {}, error: err.message });
|
|
36859
36462
|
});
|
|
36860
36463
|
});
|
|
36861
36464
|
}
|
|
@@ -36908,14 +36511,14 @@ var init_api = __esm({
|
|
|
36908
36511
|
// ../core/src/messenger.ts
|
|
36909
36512
|
import net3 from "node:net";
|
|
36910
36513
|
import tls2 from "node:tls";
|
|
36911
|
-
import { readFileSync as
|
|
36912
|
-
import { basename as
|
|
36514
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
36515
|
+
import { basename as basename6 } from "node:path";
|
|
36913
36516
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
36914
36517
|
function tlsRejectUnauthorized() {
|
|
36915
36518
|
return !isTruthy(process.env.TINA4_MAIL_TLS_INSECURE);
|
|
36916
36519
|
}
|
|
36917
36520
|
function readResponse(socket) {
|
|
36918
|
-
return new Promise((
|
|
36521
|
+
return new Promise((resolve30, reject) => {
|
|
36919
36522
|
let buffer = "";
|
|
36920
36523
|
const onData = (chunk) => {
|
|
36921
36524
|
buffer += chunk.toString("utf-8");
|
|
@@ -36927,7 +36530,7 @@ function readResponse(socket) {
|
|
|
36927
36530
|
if (line.length >= 4 && line[3] === " ") {
|
|
36928
36531
|
socket.removeListener("data", onData);
|
|
36929
36532
|
socket.removeListener("error", onError);
|
|
36930
|
-
|
|
36533
|
+
resolve30({ code, text: buffer.trim() });
|
|
36931
36534
|
return;
|
|
36932
36535
|
}
|
|
36933
36536
|
}
|
|
@@ -36941,10 +36544,10 @@ function readResponse(socket) {
|
|
|
36941
36544
|
});
|
|
36942
36545
|
}
|
|
36943
36546
|
function sendCommand(socket, command) {
|
|
36944
|
-
return new Promise((
|
|
36547
|
+
return new Promise((resolve30, reject) => {
|
|
36945
36548
|
socket.write(command + "\r\n", "utf-8", (err) => {
|
|
36946
36549
|
if (err) return reject(err);
|
|
36947
|
-
readResponse(socket).then(
|
|
36550
|
+
readResponse(socket).then(resolve30, reject);
|
|
36948
36551
|
});
|
|
36949
36552
|
});
|
|
36950
36553
|
}
|
|
@@ -37000,8 +36603,8 @@ function buildMimeMessage(options) {
|
|
|
37000
36603
|
lines.push(options.body);
|
|
37001
36604
|
}
|
|
37002
36605
|
for (const filePath of options.attachments) {
|
|
37003
|
-
const fileName =
|
|
37004
|
-
const fileData =
|
|
36606
|
+
const fileName = basename6(filePath);
|
|
36607
|
+
const fileData = readFileSync24(filePath);
|
|
37005
36608
|
const base64Data = fileData.toString("base64");
|
|
37006
36609
|
lines.push("");
|
|
37007
36610
|
lines.push(`--${boundary}`);
|
|
@@ -37044,7 +36647,7 @@ function imapQuote(s) {
|
|
|
37044
36647
|
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
37045
36648
|
}
|
|
37046
36649
|
function imapReadLine(socket) {
|
|
37047
|
-
return new Promise((
|
|
36650
|
+
return new Promise((resolve30, reject) => {
|
|
37048
36651
|
let buffer = "";
|
|
37049
36652
|
const onData = (chunk) => {
|
|
37050
36653
|
buffer += chunk.toString("utf-8");
|
|
@@ -37052,7 +36655,7 @@ function imapReadLine(socket) {
|
|
|
37052
36655
|
if (nlIndex !== -1) {
|
|
37053
36656
|
socket.removeListener("data", onData);
|
|
37054
36657
|
socket.removeListener("error", onError);
|
|
37055
|
-
|
|
36658
|
+
resolve30(buffer);
|
|
37056
36659
|
}
|
|
37057
36660
|
};
|
|
37058
36661
|
const onError = (err) => {
|
|
@@ -37064,7 +36667,7 @@ function imapReadLine(socket) {
|
|
|
37064
36667
|
});
|
|
37065
36668
|
}
|
|
37066
36669
|
function imapCommand(socket, command) {
|
|
37067
|
-
return new Promise((
|
|
36670
|
+
return new Promise((resolve30, reject) => {
|
|
37068
36671
|
imapTagCounter++;
|
|
37069
36672
|
const tag = `T${imapTagCounter}`;
|
|
37070
36673
|
const fullCommand = `${tag} ${command}\r
|
|
@@ -37075,7 +36678,7 @@ function imapCommand(socket, command) {
|
|
|
37075
36678
|
if (buffer.includes(`${tag} OK`)) {
|
|
37076
36679
|
socket.removeListener("data", onData);
|
|
37077
36680
|
socket.removeListener("error", onError);
|
|
37078
|
-
|
|
36681
|
+
resolve30(buffer);
|
|
37079
36682
|
return;
|
|
37080
36683
|
}
|
|
37081
36684
|
if (buffer.includes(`${tag} NO`) || buffer.includes(`${tag} BAD`)) {
|
|
@@ -37104,91 +36707,149 @@ function parseSearchResponse(response) {
|
|
|
37104
36707
|
if (!match) return [];
|
|
37105
36708
|
return match[1].trim().split(/\s+/).filter((s) => /^\d+$/.test(s));
|
|
37106
36709
|
}
|
|
37107
|
-
function
|
|
37108
|
-
const
|
|
37109
|
-
|
|
37110
|
-
|
|
37111
|
-
|
|
37112
|
-
let currentKey = "";
|
|
37113
|
-
for (const line of lines) {
|
|
37114
|
-
if (/^\s/.test(line) && currentKey) {
|
|
37115
|
-
headers[currentKey] += " " + line.trim();
|
|
37116
|
-
} else {
|
|
37117
|
-
const colonIdx = line.indexOf(":");
|
|
37118
|
-
if (colonIdx > 0) {
|
|
37119
|
-
currentKey = line.substring(0, colonIdx).trim().toLowerCase();
|
|
37120
|
-
headers[currentKey] = line.substring(colonIdx + 1).trim();
|
|
37121
|
-
}
|
|
37122
|
-
}
|
|
37123
|
-
}
|
|
37124
|
-
}
|
|
37125
|
-
const seen = /\\Seen/i.test(response);
|
|
37126
|
-
return {
|
|
37127
|
-
uid,
|
|
37128
|
-
subject: headers["subject"] ?? "",
|
|
37129
|
-
from: headers["from"] ?? "",
|
|
37130
|
-
to: headers["to"] ?? "",
|
|
37131
|
-
date: headers["date"] ?? "",
|
|
37132
|
-
snippet: "",
|
|
37133
|
-
seen
|
|
37134
|
-
};
|
|
36710
|
+
function extractRawMessage(response) {
|
|
36711
|
+
const m = response.match(/\{(\d+)\}\r\n/);
|
|
36712
|
+
if (!m) return response;
|
|
36713
|
+
const start2 = (m.index ?? 0) + m[0].length;
|
|
36714
|
+
return response.slice(start2, start2 + parseInt(m[1], 10));
|
|
37135
36715
|
}
|
|
37136
|
-
function
|
|
37137
|
-
const bodyMatch = response.match(/\{(\d+)\}\r\n([\s\S]*)/);
|
|
37138
|
-
const rawMessage = bodyMatch ? bodyMatch[2] : response;
|
|
37139
|
-
const headerEnd = rawMessage.indexOf("\r\n\r\n");
|
|
37140
|
-
const headerSection = headerEnd > 0 ? rawMessage.substring(0, headerEnd) : rawMessage;
|
|
37141
|
-
const bodySection = headerEnd > 0 ? rawMessage.substring(headerEnd + 4) : "";
|
|
36716
|
+
function parseMimeHeaders(section) {
|
|
37142
36717
|
const headers = {};
|
|
37143
|
-
const headerLines = headerSection.split(/\r\n/);
|
|
37144
36718
|
let currentKey = "";
|
|
37145
|
-
for (const line of
|
|
36719
|
+
for (const line of section.split(/\r\n/)) {
|
|
37146
36720
|
if (/^\s/.test(line) && currentKey) {
|
|
37147
36721
|
headers[currentKey] += " " + line.trim();
|
|
37148
36722
|
} else {
|
|
37149
|
-
const
|
|
37150
|
-
if (
|
|
37151
|
-
currentKey = line.substring(0,
|
|
37152
|
-
headers[currentKey] = line.substring(
|
|
36723
|
+
const idx = line.indexOf(":");
|
|
36724
|
+
if (idx > 0) {
|
|
36725
|
+
currentKey = line.substring(0, idx).trim().toLowerCase();
|
|
36726
|
+
headers[currentKey] = line.substring(idx + 1).trim();
|
|
36727
|
+
}
|
|
36728
|
+
}
|
|
36729
|
+
}
|
|
36730
|
+
return headers;
|
|
36731
|
+
}
|
|
36732
|
+
function decodeTransfer(body, encoding) {
|
|
36733
|
+
const enc = encoding.toLowerCase().trim();
|
|
36734
|
+
if (enc === "base64") {
|
|
36735
|
+
try {
|
|
36736
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64").toString("utf-8");
|
|
36737
|
+
} catch {
|
|
36738
|
+
return body;
|
|
36739
|
+
}
|
|
36740
|
+
}
|
|
36741
|
+
if (enc === "quoted-printable") {
|
|
36742
|
+
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)));
|
|
36743
|
+
}
|
|
36744
|
+
return body;
|
|
36745
|
+
}
|
|
36746
|
+
function decodeAttachmentBytes(body, encoding) {
|
|
36747
|
+
const enc = encoding.toLowerCase().trim();
|
|
36748
|
+
if (enc === "base64") {
|
|
36749
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64");
|
|
36750
|
+
}
|
|
36751
|
+
const trimmed = body.replace(/\r\n$/, "");
|
|
36752
|
+
if (enc === "quoted-printable") {
|
|
36753
|
+
const collapsed = trimmed.replace(/=\r?\n/g, "");
|
|
36754
|
+
const bytes = [];
|
|
36755
|
+
for (let i = 0; i < collapsed.length; i++) {
|
|
36756
|
+
const hex = collapsed.substring(i + 1, i + 3);
|
|
36757
|
+
if (collapsed[i] === "=" && /^[0-9A-Fa-f]{2}$/.test(hex)) {
|
|
36758
|
+
bytes.push(parseInt(hex, 16));
|
|
36759
|
+
i += 2;
|
|
36760
|
+
} else {
|
|
36761
|
+
bytes.push(collapsed.charCodeAt(i) & 255);
|
|
37153
36762
|
}
|
|
37154
36763
|
}
|
|
36764
|
+
return Buffer.from(bytes);
|
|
37155
36765
|
}
|
|
36766
|
+
return Buffer.from(trimmed, "utf-8");
|
|
36767
|
+
}
|
|
36768
|
+
function attachmentFilename(disposition, contentType) {
|
|
36769
|
+
const d = disposition.match(/filename="?([^";\r\n]+)"?/i);
|
|
36770
|
+
if (d) return d[1].trim();
|
|
36771
|
+
const c = contentType.match(/name="?([^";\r\n]+)"?/i);
|
|
36772
|
+
if (c) return c[1].trim();
|
|
36773
|
+
return "attachment";
|
|
36774
|
+
}
|
|
36775
|
+
function makeSnippet(bodyText, bodyHtml) {
|
|
36776
|
+
return (bodyText || bodyHtml || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
36777
|
+
}
|
|
36778
|
+
function toIsoDate(raw) {
|
|
36779
|
+
if (!raw) return "";
|
|
36780
|
+
const d = new Date(raw);
|
|
36781
|
+
return Number.isNaN(d.getTime()) ? raw : d.toISOString();
|
|
36782
|
+
}
|
|
36783
|
+
function parseMessage(response) {
|
|
36784
|
+
const raw = extractRawMessage(response);
|
|
36785
|
+
const headerEnd = raw.indexOf("\r\n\r\n");
|
|
36786
|
+
const headerSection = headerEnd >= 0 ? raw.substring(0, headerEnd) : raw;
|
|
36787
|
+
const bodySection = headerEnd >= 0 ? raw.substring(headerEnd + 4) : "";
|
|
36788
|
+
const headers = parseMimeHeaders(headerSection);
|
|
37156
36789
|
const contentType = headers["content-type"] ?? "text/plain";
|
|
37157
36790
|
let bodyText = "";
|
|
37158
36791
|
let bodyHtml = "";
|
|
36792
|
+
const attachments = [];
|
|
37159
36793
|
if (contentType.includes("multipart")) {
|
|
37160
36794
|
const boundaryMatch = contentType.match(/boundary="?([^";\s]+)"?/);
|
|
37161
36795
|
if (boundaryMatch) {
|
|
37162
|
-
const boundary = boundaryMatch[1];
|
|
37163
|
-
const
|
|
37164
|
-
|
|
37165
|
-
if (
|
|
37166
|
-
const
|
|
37167
|
-
|
|
37168
|
-
const
|
|
37169
|
-
|
|
37170
|
-
|
|
37171
|
-
|
|
37172
|
-
|
|
36796
|
+
const boundary = "--" + boundaryMatch[1];
|
|
36797
|
+
for (const part of bodySection.split(boundary)) {
|
|
36798
|
+
const trimmed = part.trim();
|
|
36799
|
+
if (trimmed === "" || trimmed === "--") continue;
|
|
36800
|
+
const pEnd = part.indexOf("\r\n\r\n");
|
|
36801
|
+
if (pEnd < 0) continue;
|
|
36802
|
+
const pHeaders = parseMimeHeaders(part.substring(0, pEnd));
|
|
36803
|
+
const pBody = part.substring(pEnd + 4);
|
|
36804
|
+
const cte = pHeaders["content-transfer-encoding"] ?? "";
|
|
36805
|
+
const pType = pHeaders["content-type"] ?? "text/plain";
|
|
36806
|
+
const disposition = pHeaders["content-disposition"] ?? "";
|
|
36807
|
+
if (/attachment/i.test(disposition)) {
|
|
36808
|
+
const content = decodeAttachmentBytes(pBody, cte);
|
|
36809
|
+
attachments.push({
|
|
36810
|
+
filename: attachmentFilename(disposition, pType),
|
|
36811
|
+
contentType: pType.split(";")[0].trim(),
|
|
36812
|
+
size: content.length,
|
|
36813
|
+
content
|
|
36814
|
+
});
|
|
36815
|
+
} else if (pType.includes("text/html")) {
|
|
36816
|
+
bodyHtml = decodeTransfer(pBody, cte).trim();
|
|
36817
|
+
} else if (pType.includes("text/plain")) {
|
|
36818
|
+
bodyText = decodeTransfer(pBody, cte).trim();
|
|
37173
36819
|
}
|
|
37174
36820
|
}
|
|
37175
36821
|
}
|
|
37176
36822
|
} else if (contentType.includes("text/html")) {
|
|
37177
|
-
bodyHtml = bodySection;
|
|
36823
|
+
bodyHtml = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
37178
36824
|
} else {
|
|
37179
|
-
bodyText = bodySection;
|
|
36825
|
+
bodyText = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
37180
36826
|
}
|
|
37181
|
-
|
|
37182
|
-
|
|
36827
|
+
return { headers, bodyText, bodyHtml, attachments };
|
|
36828
|
+
}
|
|
36829
|
+
function parseSummary(uid, response) {
|
|
36830
|
+
const { headers, bodyText, bodyHtml } = parseMessage(response);
|
|
36831
|
+
return {
|
|
36832
|
+
uid,
|
|
36833
|
+
subject: headers["subject"] ?? "",
|
|
36834
|
+
from: headers["from"] ?? "",
|
|
36835
|
+
to: headers["to"] ?? "",
|
|
36836
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
36837
|
+
snippet: makeSnippet(bodyText, bodyHtml),
|
|
36838
|
+
seen: /\\Seen/i.test(response)
|
|
36839
|
+
};
|
|
36840
|
+
}
|
|
36841
|
+
function parseFullMessage(uid, response) {
|
|
36842
|
+
const { headers, bodyText, bodyHtml, attachments } = parseMessage(response);
|
|
37183
36843
|
return {
|
|
37184
36844
|
uid,
|
|
37185
36845
|
subject: headers["subject"] ?? "",
|
|
37186
36846
|
from: headers["from"] ?? "",
|
|
37187
36847
|
to: headers["to"] ?? "",
|
|
37188
36848
|
cc: headers["cc"] ?? "",
|
|
37189
|
-
date: headers["date"] ?? "",
|
|
36849
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
37190
36850
|
bodyText,
|
|
37191
36851
|
bodyHtml,
|
|
36852
|
+
attachments,
|
|
37192
36853
|
headers
|
|
37193
36854
|
};
|
|
37194
36855
|
}
|
|
@@ -37304,89 +36965,89 @@ var init_messenger = __esm({
|
|
|
37304
36965
|
}
|
|
37305
36966
|
const messageId = `${randomUUID7()}@${this.host}`;
|
|
37306
36967
|
if (allRecipients.length === 0) {
|
|
37307
|
-
return { success: false, message: "No recipients specified" };
|
|
36968
|
+
return { success: false, message: "No recipients specified", id: null };
|
|
37308
36969
|
}
|
|
37309
36970
|
if (!this.fromAddress) {
|
|
37310
|
-
return { success: false, message: "No from address configured" };
|
|
36971
|
+
return { success: false, message: "No from address configured", id: null };
|
|
37311
36972
|
}
|
|
37312
36973
|
try {
|
|
37313
36974
|
let socket;
|
|
37314
36975
|
if (this.port === 465) {
|
|
37315
36976
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37316
|
-
await new Promise((
|
|
37317
|
-
socket.once("secureConnect",
|
|
36977
|
+
await new Promise((resolve30, reject) => {
|
|
36978
|
+
socket.once("secureConnect", resolve30);
|
|
37318
36979
|
socket.once("error", reject);
|
|
37319
36980
|
});
|
|
37320
36981
|
} else {
|
|
37321
36982
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
37322
|
-
await new Promise((
|
|
37323
|
-
socket.once("connect",
|
|
36983
|
+
await new Promise((resolve30, reject) => {
|
|
36984
|
+
socket.once("connect", resolve30);
|
|
37324
36985
|
socket.once("error", reject);
|
|
37325
36986
|
});
|
|
37326
36987
|
}
|
|
37327
36988
|
const greeting = await readResponse(socket);
|
|
37328
36989
|
if (greeting.code !== 220) {
|
|
37329
36990
|
socket.destroy();
|
|
37330
|
-
return { success: false, message: `SMTP greeting failed: ${greeting.text}
|
|
36991
|
+
return { success: false, message: `SMTP greeting failed: ${greeting.text}`, id: null };
|
|
37331
36992
|
}
|
|
37332
36993
|
const ehlo = await sendCommand(socket, `EHLO ${this.host}`);
|
|
37333
36994
|
if (ehlo.code !== 250) {
|
|
37334
36995
|
socket.destroy();
|
|
37335
|
-
return { success: false, message: `EHLO failed: ${ehlo.text}
|
|
36996
|
+
return { success: false, message: `EHLO failed: ${ehlo.text}`, id: null };
|
|
37336
36997
|
}
|
|
37337
36998
|
if (this.useTls && this.port !== 465 && ehlo.text.includes("STARTTLS")) {
|
|
37338
36999
|
const starttls = await sendCommand(socket, "STARTTLS");
|
|
37339
37000
|
if (starttls.code !== 220) {
|
|
37340
37001
|
socket.destroy();
|
|
37341
|
-
return { success: false, message: `STARTTLS failed: ${starttls.text}
|
|
37002
|
+
return { success: false, message: `STARTTLS failed: ${starttls.text}`, id: null };
|
|
37342
37003
|
}
|
|
37343
37004
|
const plainSocket = socket;
|
|
37344
37005
|
socket = tls2.connect(
|
|
37345
37006
|
{ socket: plainSocket, host: this.host, rejectUnauthorized: tlsRejectUnauthorized() }
|
|
37346
37007
|
);
|
|
37347
|
-
await new Promise((
|
|
37348
|
-
socket.once("secureConnect",
|
|
37008
|
+
await new Promise((resolve30, reject) => {
|
|
37009
|
+
socket.once("secureConnect", resolve30);
|
|
37349
37010
|
socket.once("error", reject);
|
|
37350
37011
|
});
|
|
37351
37012
|
const ehlo2 = await sendCommand(socket, `EHLO ${this.host}`);
|
|
37352
37013
|
if (ehlo2.code !== 250) {
|
|
37353
37014
|
socket.destroy();
|
|
37354
|
-
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}
|
|
37015
|
+
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}`, id: null };
|
|
37355
37016
|
}
|
|
37356
37017
|
}
|
|
37357
37018
|
if (this.username && this.password) {
|
|
37358
37019
|
const auth = await sendCommand(socket, "AUTH LOGIN");
|
|
37359
37020
|
if (auth.code !== 334) {
|
|
37360
37021
|
socket.destroy();
|
|
37361
|
-
return { success: false, message: `AUTH LOGIN failed: ${auth.text}
|
|
37022
|
+
return { success: false, message: `AUTH LOGIN failed: ${auth.text}`, id: null };
|
|
37362
37023
|
}
|
|
37363
37024
|
const userResp = await sendCommand(socket, Buffer.from(this.username).toString("base64"));
|
|
37364
37025
|
if (userResp.code !== 334) {
|
|
37365
37026
|
socket.destroy();
|
|
37366
|
-
return { success: false, message: `AUTH username failed: ${userResp.text}
|
|
37027
|
+
return { success: false, message: `AUTH username failed: ${userResp.text}`, id: null };
|
|
37367
37028
|
}
|
|
37368
37029
|
const passResp = await sendCommand(socket, Buffer.from(this.password).toString("base64"));
|
|
37369
37030
|
if (passResp.code !== 235) {
|
|
37370
37031
|
socket.destroy();
|
|
37371
|
-
return { success: false, message: `AUTH password failed: ${passResp.text}
|
|
37032
|
+
return { success: false, message: `AUTH password failed: ${passResp.text}`, id: null };
|
|
37372
37033
|
}
|
|
37373
37034
|
}
|
|
37374
37035
|
const mailFrom = await sendCommand(socket, `MAIL FROM:<${this.fromAddress}>`);
|
|
37375
37036
|
if (mailFrom.code !== 250) {
|
|
37376
37037
|
socket.destroy();
|
|
37377
|
-
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}
|
|
37038
|
+
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}`, id: null };
|
|
37378
37039
|
}
|
|
37379
37040
|
for (const recipient of allRecipients) {
|
|
37380
37041
|
const rcpt = await sendCommand(socket, `RCPT TO:<${recipient}>`);
|
|
37381
37042
|
if (rcpt.code !== 250 && rcpt.code !== 251) {
|
|
37382
37043
|
socket.destroy();
|
|
37383
|
-
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}
|
|
37044
|
+
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}`, id: null };
|
|
37384
37045
|
}
|
|
37385
37046
|
}
|
|
37386
37047
|
const dataCmd = await sendCommand(socket, "DATA");
|
|
37387
37048
|
if (dataCmd.code !== 354) {
|
|
37388
37049
|
socket.destroy();
|
|
37389
|
-
return { success: false, message: `DATA failed: ${dataCmd.text}
|
|
37050
|
+
return { success: false, message: `DATA failed: ${dataCmd.text}`, id: null };
|
|
37390
37051
|
}
|
|
37391
37052
|
const mimeMessage = buildMimeMessage({
|
|
37392
37053
|
from: this.fromAddress,
|
|
@@ -37405,15 +37066,30 @@ var init_messenger = __esm({
|
|
|
37405
37066
|
const endData = await sendCommand(socket, mimeMessage + "\r\n.");
|
|
37406
37067
|
if (endData.code !== 250) {
|
|
37407
37068
|
socket.destroy();
|
|
37408
|
-
return { success: false, message: `Message delivery failed: ${endData.text}
|
|
37069
|
+
return { success: false, message: `Message delivery failed: ${endData.text}`, id: null };
|
|
37409
37070
|
}
|
|
37410
37071
|
await sendCommand(socket, "QUIT");
|
|
37411
37072
|
socket.destroy();
|
|
37412
37073
|
return { success: true, message: "Email sent successfully", id: messageId };
|
|
37413
37074
|
} catch (err) {
|
|
37414
37075
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
37415
|
-
return { success: false, message: `SMTP error: ${errMsg}
|
|
37076
|
+
return { success: false, message: `SMTP error: ${errMsg}`, id: null };
|
|
37077
|
+
}
|
|
37078
|
+
}
|
|
37079
|
+
/**
|
|
37080
|
+
* Render a Frond template STRING and send it as an HTML email (G7, parity with
|
|
37081
|
+
* Python's send_template). Extra send() options (cc, bcc, replyTo, attachments,
|
|
37082
|
+
* headers) pass through. If the Frond package cannot be loaded the raw template
|
|
37083
|
+
* is sent verbatim (matches Python's ImportError fallback) rather than failing.
|
|
37084
|
+
*/
|
|
37085
|
+
async sendTemplate(to, subject, template, data = {}, cc, bcc, replyTo, attachments, headers) {
|
|
37086
|
+
let body = template;
|
|
37087
|
+
try {
|
|
37088
|
+
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
37089
|
+
body = new Frond2().renderString(template, data);
|
|
37090
|
+
} catch {
|
|
37416
37091
|
}
|
|
37092
|
+
return this.send(to, subject, body, true, void 0, cc, bcc, replyTo, attachments, headers);
|
|
37417
37093
|
}
|
|
37418
37094
|
/**
|
|
37419
37095
|
* Test the SMTP connection without sending an email.
|
|
@@ -37423,14 +37099,14 @@ var init_messenger = __esm({
|
|
|
37423
37099
|
let socket;
|
|
37424
37100
|
if (this.port === 465) {
|
|
37425
37101
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37426
|
-
await new Promise((
|
|
37427
|
-
socket.once("secureConnect",
|
|
37102
|
+
await new Promise((resolve30, reject) => {
|
|
37103
|
+
socket.once("secureConnect", resolve30);
|
|
37428
37104
|
socket.once("error", reject);
|
|
37429
37105
|
});
|
|
37430
37106
|
} else {
|
|
37431
37107
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
37432
|
-
await new Promise((
|
|
37433
|
-
socket.once("connect",
|
|
37108
|
+
await new Promise((resolve30, reject) => {
|
|
37109
|
+
socket.once("connect", resolve30);
|
|
37434
37110
|
socket.once("error", reject);
|
|
37435
37111
|
});
|
|
37436
37112
|
}
|
|
@@ -37465,14 +37141,14 @@ var init_messenger = __esm({
|
|
|
37465
37141
|
const useTls = this.imapEncryption === "tls" || this.imapEncryption === "ssl" || this.imapEncryption === "" && this.imapPort === 993;
|
|
37466
37142
|
if (useTls) {
|
|
37467
37143
|
socket = tls2.connect({ host: this.imapHost, port: this.imapPort, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37468
|
-
await new Promise((
|
|
37469
|
-
socket.once("secureConnect",
|
|
37144
|
+
await new Promise((resolve30, reject) => {
|
|
37145
|
+
socket.once("secureConnect", resolve30);
|
|
37470
37146
|
socket.once("error", reject);
|
|
37471
37147
|
});
|
|
37472
37148
|
} else {
|
|
37473
37149
|
socket = net3.createConnection({ host: this.imapHost, port: this.imapPort });
|
|
37474
|
-
await new Promise((
|
|
37475
|
-
socket.once("connect",
|
|
37150
|
+
await new Promise((resolve30, reject) => {
|
|
37151
|
+
socket.once("connect", resolve30);
|
|
37476
37152
|
socket.once("error", reject);
|
|
37477
37153
|
});
|
|
37478
37154
|
}
|
|
@@ -37509,7 +37185,7 @@ var init_messenger = __esm({
|
|
|
37509
37185
|
}
|
|
37510
37186
|
try {
|
|
37511
37187
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37512
|
-
const searchResp = await imapCommand(socket, "SEARCH ALL");
|
|
37188
|
+
const searchResp = await imapCommand(socket, "UID SEARCH ALL");
|
|
37513
37189
|
const uids = parseSearchResponse(searchResp);
|
|
37514
37190
|
if (uids.length === 0) return [];
|
|
37515
37191
|
uids.reverse();
|
|
@@ -37517,8 +37193,8 @@ var init_messenger = __esm({
|
|
|
37517
37193
|
if (selected.length === 0) return [];
|
|
37518
37194
|
const messages = [];
|
|
37519
37195
|
for (const uid of selected) {
|
|
37520
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
37521
|
-
messages.push(
|
|
37196
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
37197
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
37522
37198
|
}
|
|
37523
37199
|
return messages;
|
|
37524
37200
|
} catch (err) {
|
|
@@ -37528,7 +37204,7 @@ var init_messenger = __esm({
|
|
|
37528
37204
|
}
|
|
37529
37205
|
}
|
|
37530
37206
|
/**
|
|
37531
|
-
* Read a single message by
|
|
37207
|
+
* Read a single message by its IMAP UID.
|
|
37532
37208
|
*/
|
|
37533
37209
|
async read(uid, folder = "INBOX") {
|
|
37534
37210
|
let socket;
|
|
@@ -37539,11 +37215,11 @@ var init_messenger = __esm({
|
|
|
37539
37215
|
}
|
|
37540
37216
|
try {
|
|
37541
37217
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37542
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY[])`);
|
|
37218
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY[])`);
|
|
37543
37219
|
if (!/\{\d+\}/.test(fetchResp)) {
|
|
37544
37220
|
return null;
|
|
37545
37221
|
}
|
|
37546
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
37222
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
37547
37223
|
return parseFullMessage(uid, fetchResp);
|
|
37548
37224
|
} catch (err) {
|
|
37549
37225
|
throw imapFail("read", err);
|
|
@@ -37570,14 +37246,14 @@ var init_messenger = __esm({
|
|
|
37570
37246
|
}
|
|
37571
37247
|
try {
|
|
37572
37248
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37573
|
-
const searchResp = await imapCommand(socket, `SEARCH ${query}`);
|
|
37249
|
+
const searchResp = await imapCommand(socket, `UID SEARCH ${query}`);
|
|
37574
37250
|
const uids = parseSearchResponse(searchResp);
|
|
37575
37251
|
if (uids.length === 0) return [];
|
|
37576
37252
|
uids.reverse();
|
|
37577
37253
|
const messages = [];
|
|
37578
37254
|
for (const uid of uids.slice(0, limit)) {
|
|
37579
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
37580
|
-
messages.push(
|
|
37255
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
37256
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
37581
37257
|
}
|
|
37582
37258
|
return messages;
|
|
37583
37259
|
} catch (err) {
|
|
@@ -37587,26 +37263,46 @@ var init_messenger = __esm({
|
|
|
37587
37263
|
}
|
|
37588
37264
|
}
|
|
37589
37265
|
/**
|
|
37590
|
-
* Delete a message by UID.
|
|
37266
|
+
* Delete a message by UID (mark \Deleted, then EXPUNGE).
|
|
37267
|
+
*
|
|
37268
|
+
* `delete` is the one cross-framework name (python/php/ruby/node all spell it
|
|
37269
|
+
* `delete`). `deleteMessage` remains as a DEPRECATED alias for one release.
|
|
37591
37270
|
*/
|
|
37592
|
-
async
|
|
37271
|
+
async delete(uid, folder = "INBOX") {
|
|
37593
37272
|
const socket = await this.imapConnect();
|
|
37594
37273
|
try {
|
|
37595
37274
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37596
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Deleted)`);
|
|
37275
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Deleted)`);
|
|
37597
37276
|
await imapCommand(socket, "EXPUNGE");
|
|
37598
37277
|
} finally {
|
|
37599
37278
|
await this.imapDisconnect(socket);
|
|
37600
37279
|
}
|
|
37601
37280
|
}
|
|
37281
|
+
/** @deprecated Use {@link delete} — kept as an alias for one release (G7). */
|
|
37282
|
+
async deleteMessage(uid, folder = "INBOX") {
|
|
37283
|
+
return this.delete(uid, folder);
|
|
37284
|
+
}
|
|
37602
37285
|
/**
|
|
37603
|
-
* Mark a message as read.
|
|
37286
|
+
* Mark a message as read (+FLAGS \Seen).
|
|
37604
37287
|
*/
|
|
37605
37288
|
async markRead(uid, folder = "INBOX") {
|
|
37606
37289
|
const socket = await this.imapConnect();
|
|
37607
37290
|
try {
|
|
37608
37291
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37609
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
37292
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
37293
|
+
} finally {
|
|
37294
|
+
await this.imapDisconnect(socket);
|
|
37295
|
+
}
|
|
37296
|
+
}
|
|
37297
|
+
/**
|
|
37298
|
+
* Mark a message as unread (-FLAGS \Seen) — the inverse of markRead (G7,
|
|
37299
|
+
* parity with Python's mark_unread).
|
|
37300
|
+
*/
|
|
37301
|
+
async markUnread(uid, folder = "INBOX") {
|
|
37302
|
+
const socket = await this.imapConnect();
|
|
37303
|
+
try {
|
|
37304
|
+
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37305
|
+
await imapCommand(socket, `UID STORE ${uid} -FLAGS (\\Seen)`);
|
|
37610
37306
|
} finally {
|
|
37611
37307
|
await this.imapDisconnect(socket);
|
|
37612
37308
|
}
|
|
@@ -37623,7 +37319,7 @@ var init_messenger = __esm({
|
|
|
37623
37319
|
}
|
|
37624
37320
|
try {
|
|
37625
37321
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37626
|
-
const searchResp = await imapCommand(socket, "SEARCH UNSEEN");
|
|
37322
|
+
const searchResp = await imapCommand(socket, "UID SEARCH UNSEEN");
|
|
37627
37323
|
return parseSearchResponse(searchResp).length;
|
|
37628
37324
|
} catch (err) {
|
|
37629
37325
|
throw imapFail("unread", err);
|
|
@@ -38379,17 +38075,17 @@ __export(ai_exports, {
|
|
|
38379
38075
|
skillBlock: () => skillBlock,
|
|
38380
38076
|
writeOrMerge: () => writeOrMerge
|
|
38381
38077
|
});
|
|
38382
|
-
import { existsSync as
|
|
38078
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync19, writeFileSync as writeFileSync17, readFileSync as readFileSync25 } from "node:fs";
|
|
38383
38079
|
import { homedir } from "node:os";
|
|
38384
|
-
import { join as
|
|
38080
|
+
import { join as join29, resolve as resolve20, relative as relative10, dirname as dirname13 } from "node:path";
|
|
38385
38081
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
38386
38082
|
import { execSync as execSync2, execFileSync as execFileSync3 } from "node:child_process";
|
|
38387
38083
|
import { createInterface } from "node:readline";
|
|
38388
38084
|
function readVersion() {
|
|
38389
38085
|
try {
|
|
38390
|
-
const thisDir =
|
|
38391
|
-
const rootPkg =
|
|
38392
|
-
const pkg = JSON.parse(
|
|
38086
|
+
const thisDir = dirname13(fileURLToPath7(import.meta.url));
|
|
38087
|
+
const rootPkg = resolve20(thisDir, "..", "..", "..", "package.json");
|
|
38088
|
+
const pkg = JSON.parse(readFileSync25(rootPkg, "utf-8"));
|
|
38393
38089
|
return pkg.version ?? "0.0.0";
|
|
38394
38090
|
} catch {
|
|
38395
38091
|
return "0.0.0";
|
|
@@ -38438,8 +38134,8 @@ function downloadSkillsSync(jobs) {
|
|
|
38438
38134
|
function installSkills(root = ".", targets) {
|
|
38439
38135
|
const ref = skillsRef();
|
|
38440
38136
|
const dests = targets ?? [
|
|
38441
|
-
|
|
38442
|
-
|
|
38137
|
+
join29(resolve20(root), ".claude", "skills"),
|
|
38138
|
+
join29(homedir(), ".claude", "skills")
|
|
38443
38139
|
];
|
|
38444
38140
|
const jobs = [];
|
|
38445
38141
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -38457,9 +38153,9 @@ function installSkills(root = ".", targets) {
|
|
|
38457
38153
|
const base = `https://raw.githubusercontent.com/tina4stack/${spec.repo}/${ref}/.claude/skills/${skill}`;
|
|
38458
38154
|
skillMdUrl[skill] = `${base}/SKILL.md`;
|
|
38459
38155
|
for (const dest of dests) {
|
|
38460
|
-
add(`${base}/SKILL.md`,
|
|
38156
|
+
add(`${base}/SKILL.md`, join29(dest, skill, "SKILL.md"));
|
|
38461
38157
|
for (const r of spec.references) {
|
|
38462
|
-
add(`${base}/references/${r}`,
|
|
38158
|
+
add(`${base}/references/${r}`, join29(dest, skill, "references", r));
|
|
38463
38159
|
}
|
|
38464
38160
|
}
|
|
38465
38161
|
}
|
|
@@ -38471,10 +38167,10 @@ function installSkills(root = ".", targets) {
|
|
|
38471
38167
|
return installed;
|
|
38472
38168
|
}
|
|
38473
38169
|
function isInstalled(root, tool) {
|
|
38474
|
-
return
|
|
38170
|
+
return existsSync26(join29(resolve20(root), tool.contextFile));
|
|
38475
38171
|
}
|
|
38476
38172
|
function showMenu(root = ".") {
|
|
38477
|
-
const r =
|
|
38173
|
+
const r = resolve20(root);
|
|
38478
38174
|
console.log("\n Tina4 AI Context Installer\n");
|
|
38479
38175
|
for (let i = 0; i < AI_TOOLS.length; i++) {
|
|
38480
38176
|
const tool = AI_TOOLS[i];
|
|
@@ -38492,16 +38188,16 @@ function showMenu(root = ".") {
|
|
|
38492
38188
|
const tina4AiMarker = tina4AiInstalled ? ` ${GREEN2}[installed]${RESET2}` : "";
|
|
38493
38189
|
console.log(` 8. Install tina4-ai tools (requires Python)${tina4AiMarker}`);
|
|
38494
38190
|
console.log();
|
|
38495
|
-
return new Promise((
|
|
38191
|
+
return new Promise((resolve30) => {
|
|
38496
38192
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
38497
38193
|
rl.question(" Select (comma-separated, or 'all'): ", (answer) => {
|
|
38498
38194
|
rl.close();
|
|
38499
|
-
|
|
38195
|
+
resolve30(answer.trim());
|
|
38500
38196
|
});
|
|
38501
38197
|
});
|
|
38502
38198
|
}
|
|
38503
38199
|
function installSelected(root, selection) {
|
|
38504
|
-
const rootPath =
|
|
38200
|
+
const rootPath = resolve20(root);
|
|
38505
38201
|
const created = [];
|
|
38506
38202
|
let indices;
|
|
38507
38203
|
let doInstallTina4Ai = false;
|
|
@@ -38594,33 +38290,33 @@ function looksLikeOldFrameworkInstall(existing) {
|
|
|
38594
38290
|
function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
38595
38291
|
const block = skillBlock(contextFile);
|
|
38596
38292
|
const [start2, end] = markersFor(contextFile);
|
|
38597
|
-
if (!
|
|
38598
|
-
|
|
38293
|
+
if (!existsSync26(contextPath)) {
|
|
38294
|
+
writeFileSync17(contextPath, frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
38599
38295
|
return "Installed";
|
|
38600
38296
|
}
|
|
38601
|
-
const existing =
|
|
38297
|
+
const existing = readFileSync25(contextPath, "utf-8");
|
|
38602
38298
|
if (hasMarkers(existing, start2, end)) {
|
|
38603
|
-
|
|
38299
|
+
writeFileSync17(contextPath, replaceMarkerBlock(existing, block, start2, end), "utf-8");
|
|
38604
38300
|
return "Refreshed skill block in";
|
|
38605
38301
|
}
|
|
38606
38302
|
if (looksLikeOldFrameworkInstall(existing)) {
|
|
38607
38303
|
const head = existing.replace(/^\s+/, "");
|
|
38608
38304
|
const preamble = existing.slice(0, existing.length - head.length);
|
|
38609
38305
|
const newContent = (preamble.trim() ? preamble.replace(/\s+$/, "") + "\n\n" : "") + frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n";
|
|
38610
|
-
|
|
38306
|
+
writeFileSync17(contextPath, newContent, "utf-8");
|
|
38611
38307
|
return "Migrated (replaced old framework dump in)";
|
|
38612
38308
|
}
|
|
38613
|
-
|
|
38309
|
+
writeFileSync17(contextPath, existing.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
38614
38310
|
return "Appended skill block to";
|
|
38615
38311
|
}
|
|
38616
38312
|
function installForTool(root, tool, context) {
|
|
38617
38313
|
const created = [];
|
|
38618
|
-
const contextPath =
|
|
38314
|
+
const contextPath = join29(root, tool.contextFile);
|
|
38619
38315
|
if (tool.configDir) {
|
|
38620
|
-
|
|
38316
|
+
mkdirSync19(join29(root, tool.configDir), { recursive: true });
|
|
38621
38317
|
}
|
|
38622
|
-
const parentDir =
|
|
38623
|
-
|
|
38318
|
+
const parentDir = dirname13(contextPath);
|
|
38319
|
+
mkdirSync19(parentDir, { recursive: true });
|
|
38624
38320
|
const action = writeOrMerge(contextPath, tool.contextFile, context);
|
|
38625
38321
|
const rel = relative10(root, contextPath);
|
|
38626
38322
|
created.push(rel);
|
|
@@ -38655,7 +38351,7 @@ function installTina4Ai() {
|
|
|
38655
38351
|
function installClaudeSkills(root) {
|
|
38656
38352
|
const created = [];
|
|
38657
38353
|
for (const skill of installSkills(root)) {
|
|
38658
|
-
created.push(
|
|
38354
|
+
created.push(join29(".claude", "skills", skill));
|
|
38659
38355
|
console.log(` ${GREEN2}\u2713${RESET2} Installed .claude/skills/${skill} (project + global)`);
|
|
38660
38356
|
}
|
|
38661
38357
|
return created;
|
|
@@ -38996,11 +38692,11 @@ import { tests, assertEqual, runAll } from "tina4-nodejs";
|
|
|
38996
38692
|
}
|
|
38997
38693
|
function generateClaudeCodeContext() {
|
|
38998
38694
|
try {
|
|
38999
|
-
const thisDir =
|
|
39000
|
-
const repoRoot =
|
|
39001
|
-
const claudeMdPath =
|
|
39002
|
-
if (
|
|
39003
|
-
return
|
|
38695
|
+
const thisDir = dirname13(fileURLToPath7(import.meta.url));
|
|
38696
|
+
const repoRoot = resolve20(thisDir, "..", "..", "..");
|
|
38697
|
+
const claudeMdPath = join29(repoRoot, "CLAUDE.md");
|
|
38698
|
+
if (existsSync26(claudeMdPath)) {
|
|
38699
|
+
return readFileSync25(claudeMdPath, "utf-8");
|
|
39004
38700
|
}
|
|
39005
38701
|
} catch {
|
|
39006
38702
|
}
|
|
@@ -39516,16 +39212,8 @@ var init_rabbitmqBackend = __esm({
|
|
|
39516
39212
|
process.stdout.write(String(msgCount));
|
|
39517
39213
|
closeConnection();
|
|
39518
39214
|
}
|
|
39519
|
-
|
|
39520
|
-
|
|
39521
|
-
const qBuf = Buffer.from(queueName, "utf-8");
|
|
39522
|
-
const purgePayload = Buffer.alloc(4 + qBuf.length);
|
|
39523
|
-
purgePayload.writeUInt16BE(0, 0);
|
|
39524
|
-
purgePayload.writeUInt8(qBuf.length, 2);
|
|
39525
|
-
qBuf.copy(purgePayload, 3);
|
|
39526
|
-
purgePayload.writeUInt8(0, 3 + qBuf.length); // no-wait=false
|
|
39527
|
-
sendMethod(1, 50, 30, purgePayload);
|
|
39528
|
-
}
|
|
39215
|
+
// No "purge" operation: clear()/purge() refuse by name (ADR-0022),
|
|
39216
|
+
// so nothing ever sends Queue.Purge and the drain path is gone.
|
|
39529
39217
|
}
|
|
39530
39218
|
else if (classId === 60 && methodId === 71) {
|
|
39531
39219
|
// Basic.Get-Ok \u2014 message body will follow in content frames
|
|
@@ -39536,11 +39224,6 @@ var init_rabbitmqBackend = __esm({
|
|
|
39536
39224
|
process.stdout.write("__EMPTY__");
|
|
39537
39225
|
closeConnection();
|
|
39538
39226
|
}
|
|
39539
|
-
else if (classId === 50 && methodId === 31) {
|
|
39540
|
-
// Queue.Purge-Ok
|
|
39541
|
-
process.stdout.write("__PURGED__");
|
|
39542
|
-
closeConnection();
|
|
39543
|
-
}
|
|
39544
39227
|
else if (classId === 10 && methodId === 50) {
|
|
39545
39228
|
// Connection.Close (server-initiated, e.g. a channel/protocol error)
|
|
39546
39229
|
// \u2192 send Connection.Close-Ok and exit non-zero so the caller sees the
|
|
@@ -39670,8 +39353,15 @@ var init_rabbitmqBackend = __esm({
|
|
|
39670
39353
|
const num = parseInt(result, 10);
|
|
39671
39354
|
return isNaN(num) ? 0 : num;
|
|
39672
39355
|
}
|
|
39673
|
-
clear(
|
|
39674
|
-
|
|
39356
|
+
clear(_queue) {
|
|
39357
|
+
throw new Error(
|
|
39358
|
+
"The rabbitmq queue backend cannot perform clear(): RabbitMQ cannot address messages by status (basic.get pops the head of the queue), so a status-addressed clear would have to drain the entire live queue and destroy pending work. Use the file or mongodb backend."
|
|
39359
|
+
);
|
|
39360
|
+
}
|
|
39361
|
+
purge(_queue, _status) {
|
|
39362
|
+
throw new Error(
|
|
39363
|
+
"The rabbitmq queue backend cannot perform purge(): RabbitMQ cannot address messages by status (basic.get pops the head of the queue), so a status-addressed purge would have to drain the entire live queue and destroy pending work. Use the file or mongodb backend."
|
|
39364
|
+
);
|
|
39675
39365
|
}
|
|
39676
39366
|
};
|
|
39677
39367
|
}
|
|
@@ -40233,6 +39923,14 @@ var init_kafkaBackend = __esm({
|
|
|
40233
39923
|
return 0;
|
|
40234
39924
|
}
|
|
40235
39925
|
clear(_queue) {
|
|
39926
|
+
throw new Error(
|
|
39927
|
+
"The kafka queue backend cannot perform clear(): Kafka has no notion of job status and cannot delete records on demand. A log is read in offset order and records leave only by retention. Use the file or mongodb backend."
|
|
39928
|
+
);
|
|
39929
|
+
}
|
|
39930
|
+
purge(_queue, _status) {
|
|
39931
|
+
throw new Error(
|
|
39932
|
+
"The kafka queue backend cannot perform purge(): Kafka has no notion of job status to purge by. A log is read in offset order and records leave only by retention. Use the file or mongodb backend."
|
|
39933
|
+
);
|
|
40236
39934
|
}
|
|
40237
39935
|
};
|
|
40238
39936
|
}
|
|
@@ -40992,7 +40690,6 @@ __export(src_exports3, {
|
|
|
40992
40690
|
RouteRef: () => RouteRef,
|
|
40993
40691
|
Router: () => Router,
|
|
40994
40692
|
SafeString: () => SafeString2,
|
|
40995
|
-
ScssCompiler: () => ScssCompiler,
|
|
40996
40693
|
SecurityHeadersMiddleware: () => SecurityHeadersMiddleware,
|
|
40997
40694
|
ServiceRunner: () => ServiceRunner,
|
|
40998
40695
|
Session: () => Session,
|
|
@@ -41189,7 +40886,6 @@ var init_src3 = __esm({
|
|
|
41189
40886
|
init_session();
|
|
41190
40887
|
init_i18n();
|
|
41191
40888
|
init_fakeData();
|
|
41192
|
-
init_scss();
|
|
41193
40889
|
init_queue();
|
|
41194
40890
|
init_job();
|
|
41195
40891
|
init_mqtt();
|
|
@@ -41464,20 +41160,20 @@ ${cdStep} npm install
|
|
|
41464
41160
|
}
|
|
41465
41161
|
|
|
41466
41162
|
// src/commands/serve.ts
|
|
41467
|
-
import { resolve as
|
|
41468
|
-
import { existsSync as
|
|
41163
|
+
import { resolve as resolve21 } from "node:path";
|
|
41164
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
41469
41165
|
async function serveProject(options) {
|
|
41470
41166
|
if (options.noReload) {
|
|
41471
41167
|
process.env.TINA4_NO_RELOAD = "true";
|
|
41472
41168
|
}
|
|
41473
41169
|
const port = options.port ?? 7148;
|
|
41474
41170
|
const cwd = process.cwd();
|
|
41475
|
-
const routesDir =
|
|
41476
|
-
const ormDir =
|
|
41477
|
-
const modelsDir =
|
|
41478
|
-
const templatesDir =
|
|
41479
|
-
const staticDir =
|
|
41480
|
-
if (!
|
|
41171
|
+
const routesDir = resolve21(cwd, "src/routes");
|
|
41172
|
+
const ormDir = resolve21(cwd, "src/orm");
|
|
41173
|
+
const modelsDir = resolve21(cwd, "src/models");
|
|
41174
|
+
const templatesDir = resolve21(cwd, "src/templates");
|
|
41175
|
+
const staticDir = resolve21(cwd, "public");
|
|
41176
|
+
if (!existsSync27(routesDir) && !existsSync27(modelsDir) && !existsSync27(ormDir)) {
|
|
41481
41177
|
console.error(" Error: Not a Tina4 project. Run this from a project created with 'tina4 init'.");
|
|
41482
41178
|
process.exit(1);
|
|
41483
41179
|
}
|
|
@@ -41493,12 +41189,12 @@ async function serveProject(options) {
|
|
|
41493
41189
|
|
|
41494
41190
|
// src/commands/migrate.ts
|
|
41495
41191
|
init_dotenv();
|
|
41496
|
-
import { existsSync as
|
|
41497
|
-
import { join as
|
|
41192
|
+
import { existsSync as existsSync28, readdirSync as readdirSync19, readFileSync as readFileSync26 } from "node:fs";
|
|
41193
|
+
import { join as join30, resolve as resolve22 } from "node:path";
|
|
41498
41194
|
async function runMigrations(migrationDir) {
|
|
41499
41195
|
loadEnv();
|
|
41500
|
-
const dir =
|
|
41501
|
-
if (!
|
|
41196
|
+
const dir = resolve22(migrationDir ?? "migrations");
|
|
41197
|
+
if (!existsSync28(dir)) {
|
|
41502
41198
|
console.log(" No migrations/ directory found. Nothing to run.");
|
|
41503
41199
|
return;
|
|
41504
41200
|
}
|
|
@@ -41529,7 +41225,7 @@ async function runMigrations(migrationDir) {
|
|
|
41529
41225
|
process.exit(1);
|
|
41530
41226
|
}
|
|
41531
41227
|
await ensureMigrationTable2();
|
|
41532
|
-
const files =
|
|
41228
|
+
const files = readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql")).sort((a, b) => {
|
|
41533
41229
|
const aMatch = a.match(/^(\d+)/);
|
|
41534
41230
|
const bMatch = b.match(/^(\d+)/);
|
|
41535
41231
|
if (aMatch && bMatch) {
|
|
@@ -41551,7 +41247,7 @@ async function runMigrations(migrationDir) {
|
|
|
41551
41247
|
if (await isMigrationApplied2(name)) {
|
|
41552
41248
|
continue;
|
|
41553
41249
|
}
|
|
41554
|
-
const sql =
|
|
41250
|
+
const sql = readFileSync26(join30(dir, file), "utf-8").trim();
|
|
41555
41251
|
if (!sql) continue;
|
|
41556
41252
|
console.log(` Migrating: ${file}`);
|
|
41557
41253
|
const adapter = getAdapter2();
|
|
@@ -41576,17 +41272,17 @@ async function runMigrations(migrationDir) {
|
|
|
41576
41272
|
}
|
|
41577
41273
|
|
|
41578
41274
|
// src/commands/migrateCreate.ts
|
|
41579
|
-
import { existsSync as
|
|
41580
|
-
import { join as
|
|
41275
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync20, writeFileSync as writeFileSync18 } from "node:fs";
|
|
41276
|
+
import { join as join31, resolve as resolve23 } from "node:path";
|
|
41581
41277
|
async function createMigration2(description) {
|
|
41582
41278
|
if (!description) {
|
|
41583
41279
|
console.error(" Usage: tina4 migrate:create <description>");
|
|
41584
41280
|
console.error(' Example: tina4 migrate:create "create users table"');
|
|
41585
41281
|
process.exit(1);
|
|
41586
41282
|
}
|
|
41587
|
-
const dir =
|
|
41588
|
-
if (!
|
|
41589
|
-
|
|
41283
|
+
const dir = resolve23("migrations");
|
|
41284
|
+
if (!existsSync29(dir)) {
|
|
41285
|
+
mkdirSync20(dir, { recursive: true });
|
|
41590
41286
|
}
|
|
41591
41287
|
const safeName = description.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
41592
41288
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -41600,8 +41296,8 @@ async function createMigration2(description) {
|
|
|
41600
41296
|
].join("");
|
|
41601
41297
|
const upFileName = `${timestamp2}_${safeName}.sql`;
|
|
41602
41298
|
const downFileName = `${timestamp2}_${safeName}.down.sql`;
|
|
41603
|
-
const upPath =
|
|
41604
|
-
const downPath =
|
|
41299
|
+
const upPath = join31(dir, upFileName);
|
|
41300
|
+
const downPath = join31(dir, downFileName);
|
|
41605
41301
|
const upTemplate = `-- Migration: ${description}
|
|
41606
41302
|
-- Created: ${now.toISOString()}
|
|
41607
41303
|
|
|
@@ -41610,8 +41306,8 @@ async function createMigration2(description) {
|
|
|
41610
41306
|
-- Created: ${now.toISOString()}
|
|
41611
41307
|
|
|
41612
41308
|
`;
|
|
41613
|
-
|
|
41614
|
-
|
|
41309
|
+
writeFileSync18(upPath, upTemplate, "utf-8");
|
|
41310
|
+
writeFileSync18(downPath, downTemplate, "utf-8");
|
|
41615
41311
|
console.log(` Created migration: ${upFileName}`);
|
|
41616
41312
|
console.log(` Created rollback: ${downFileName}`);
|
|
41617
41313
|
console.log(` Path: ${dir}`);
|
|
@@ -41619,10 +41315,10 @@ async function createMigration2(description) {
|
|
|
41619
41315
|
|
|
41620
41316
|
// src/commands/migrateStatus.ts
|
|
41621
41317
|
init_dotenv();
|
|
41622
|
-
import { resolve as
|
|
41318
|
+
import { resolve as resolve24 } from "node:path";
|
|
41623
41319
|
async function migrateStatus(migrationDir) {
|
|
41624
41320
|
loadEnv();
|
|
41625
|
-
const dir =
|
|
41321
|
+
const dir = resolve24(migrationDir ?? "migrations");
|
|
41626
41322
|
let initDatabase2;
|
|
41627
41323
|
let ensureMigrationTable2;
|
|
41628
41324
|
let statusFn;
|
|
@@ -41667,10 +41363,10 @@ async function migrateStatus(migrationDir) {
|
|
|
41667
41363
|
|
|
41668
41364
|
// src/commands/migrateRollback.ts
|
|
41669
41365
|
init_dotenv();
|
|
41670
|
-
import { resolve as
|
|
41366
|
+
import { resolve as resolve25 } from "node:path";
|
|
41671
41367
|
async function migrateRollback(migrationDir) {
|
|
41672
41368
|
loadEnv();
|
|
41673
|
-
const dir =
|
|
41369
|
+
const dir = resolve25(migrationDir ?? "migrations");
|
|
41674
41370
|
let initDatabase2;
|
|
41675
41371
|
let ensureMigrationTable2;
|
|
41676
41372
|
let rollbackFn;
|
|
@@ -41710,11 +41406,11 @@ async function migrateRollback(migrationDir) {
|
|
|
41710
41406
|
}
|
|
41711
41407
|
|
|
41712
41408
|
// src/commands/routes.ts
|
|
41713
|
-
import { existsSync as
|
|
41714
|
-
import { resolve as
|
|
41409
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
41410
|
+
import { resolve as resolve26 } from "node:path";
|
|
41715
41411
|
async function listRoutes() {
|
|
41716
|
-
const routesDir =
|
|
41717
|
-
if (!
|
|
41412
|
+
const routesDir = resolve26("src/routes");
|
|
41413
|
+
if (!existsSync30(routesDir)) {
|
|
41718
41414
|
console.error(" No src/routes/ directory found. Are you in a Tina4 project?");
|
|
41719
41415
|
process.exit(1);
|
|
41720
41416
|
}
|
|
@@ -41754,14 +41450,14 @@ async function listRoutes() {
|
|
|
41754
41450
|
}
|
|
41755
41451
|
|
|
41756
41452
|
// src/commands/test.ts
|
|
41757
|
-
import { existsSync as
|
|
41758
|
-
import { resolve as
|
|
41453
|
+
import { existsSync as existsSync31, readdirSync as readdirSync21 } from "node:fs";
|
|
41454
|
+
import { resolve as resolve27, join as join32 } from "node:path";
|
|
41759
41455
|
import { execSync as execSync3 } from "node:child_process";
|
|
41760
41456
|
async function runTests(testPath) {
|
|
41761
41457
|
const cwd = process.cwd();
|
|
41762
41458
|
if (testPath) {
|
|
41763
|
-
const file =
|
|
41764
|
-
if (!
|
|
41459
|
+
const file = resolve27(testPath);
|
|
41460
|
+
if (!existsSync31(file)) {
|
|
41765
41461
|
console.error(` Error: Test file not found: ${testPath}`);
|
|
41766
41462
|
process.exit(1);
|
|
41767
41463
|
}
|
|
@@ -41781,14 +41477,14 @@ async function runTests(testPath) {
|
|
|
41781
41477
|
];
|
|
41782
41478
|
let testFiles = [];
|
|
41783
41479
|
for (const candidate of candidates) {
|
|
41784
|
-
const fullPath =
|
|
41785
|
-
if (!
|
|
41480
|
+
const fullPath = resolve27(cwd, candidate);
|
|
41481
|
+
if (!existsSync31(fullPath)) continue;
|
|
41786
41482
|
if (candidate.endsWith(".ts")) {
|
|
41787
41483
|
testFiles.push(fullPath);
|
|
41788
41484
|
break;
|
|
41789
41485
|
}
|
|
41790
41486
|
try {
|
|
41791
|
-
const files =
|
|
41487
|
+
const files = readdirSync21(fullPath).filter((f) => f.endsWith(".ts")).map((f) => join32(fullPath, f));
|
|
41792
41488
|
testFiles.push(...files);
|
|
41793
41489
|
} catch {
|
|
41794
41490
|
}
|
|
@@ -41817,8 +41513,8 @@ async function runTests(testPath) {
|
|
|
41817
41513
|
}
|
|
41818
41514
|
|
|
41819
41515
|
// src/commands/generate.ts
|
|
41820
|
-
import { existsSync as
|
|
41821
|
-
import { join as
|
|
41516
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync21, writeFileSync as writeFileSync19 } from "node:fs";
|
|
41517
|
+
import { join as join33, resolve as resolve28 } from "node:path";
|
|
41822
41518
|
var FIELD_TYPE_MAP = {
|
|
41823
41519
|
string: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
|
|
41824
41520
|
str: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
|
|
@@ -41835,16 +41531,16 @@ var FIELD_TYPE_MAP = {
|
|
|
41835
41531
|
blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" }
|
|
41836
41532
|
};
|
|
41837
41533
|
function ensureDir(dir) {
|
|
41838
|
-
if (!
|
|
41839
|
-
|
|
41534
|
+
if (!existsSync32(dir)) {
|
|
41535
|
+
mkdirSync21(dir, { recursive: true });
|
|
41840
41536
|
}
|
|
41841
41537
|
}
|
|
41842
41538
|
function writeFileSafe(path8, content) {
|
|
41843
|
-
if (
|
|
41539
|
+
if (existsSync32(path8)) {
|
|
41844
41540
|
console.log(` File already exists: ${path8}`);
|
|
41845
41541
|
return;
|
|
41846
41542
|
}
|
|
41847
|
-
|
|
41543
|
+
writeFileSync19(path8, content, "utf-8");
|
|
41848
41544
|
console.log(` Created ${path8}`);
|
|
41849
41545
|
}
|
|
41850
41546
|
function toSnake(name) {
|
|
@@ -42001,9 +41697,9 @@ async function generate2(what, name, extraArgs = []) {
|
|
|
42001
41697
|
function generateModel(name, flags, emitTest = true) {
|
|
42002
41698
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
42003
41699
|
const table2 = toTableName(name);
|
|
42004
|
-
const dir =
|
|
41700
|
+
const dir = resolve28("src/models");
|
|
42005
41701
|
ensureDir(dir);
|
|
42006
|
-
const path8 =
|
|
41702
|
+
const path8 = join33(dir, `${name}.ts`);
|
|
42007
41703
|
const fieldLines = [
|
|
42008
41704
|
` id: { type: "integer" as const, primaryKey: true, autoIncrement: true },`
|
|
42009
41705
|
];
|
|
@@ -42037,8 +41733,8 @@ function generateRoute(name, flags, emitTest = true) {
|
|
|
42037
41733
|
const singular = routePath.endsWith("s") ? routePath.slice(0, -1) : routePath;
|
|
42038
41734
|
const model = flags.model;
|
|
42039
41735
|
const isPublic = Boolean(flags.public);
|
|
42040
|
-
const base =
|
|
42041
|
-
const idDir =
|
|
41736
|
+
const base = resolve28("src/routes/api", routePath);
|
|
41737
|
+
const idDir = join33(base, "[id]");
|
|
42042
41738
|
ensureDir(base);
|
|
42043
41739
|
ensureDir(idDir);
|
|
42044
41740
|
const table2 = model ? toTableName(model) : "";
|
|
@@ -42049,7 +41745,7 @@ function generateRoute(name, flags, emitTest = true) {
|
|
|
42049
41745
|
const writeDoc = isPublic ? "Public (--public): no token required." : "Secure by default: requires a Bearer token (use --public to open).";
|
|
42050
41746
|
if (model) {
|
|
42051
41747
|
writeFileSafe(
|
|
42052
|
-
|
|
41748
|
+
join33(base, "get.ts"),
|
|
42053
41749
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42054
41750
|
${modelImportBase}
|
|
42055
41751
|
export const meta = { summary: "List all ${routePath}", tags: ["${routePath}"] };
|
|
@@ -42065,7 +41761,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42065
41761
|
);
|
|
42066
41762
|
} else {
|
|
42067
41763
|
writeFileSafe(
|
|
42068
|
-
|
|
41764
|
+
join33(base, "get.ts"),
|
|
42069
41765
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42070
41766
|
|
|
42071
41767
|
export const meta = { summary: "List all ${routePath}", tags: ["${routePath}"] };
|
|
@@ -42084,7 +41780,7 @@ ${aiFill(`list_${routePath}`, {
|
|
|
42084
41780
|
}
|
|
42085
41781
|
if (model) {
|
|
42086
41782
|
writeFileSafe(
|
|
42087
|
-
|
|
41783
|
+
join33(base, "post.ts"),
|
|
42088
41784
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42089
41785
|
${modelImportBase}${secureOptOut(isPublic)}export const meta = { summary: "Create a new ${singular}", tags: ["${routePath}"] };
|
|
42090
41786
|
|
|
@@ -42106,7 +41802,7 @@ ${extend(
|
|
|
42106
41802
|
);
|
|
42107
41803
|
} else {
|
|
42108
41804
|
writeFileSafe(
|
|
42109
|
-
|
|
41805
|
+
join33(base, "post.ts"),
|
|
42110
41806
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42111
41807
|
|
|
42112
41808
|
${secureOptOut(isPublic)}export const meta = { summary: "Create a new ${singular}", tags: ["${routePath}"] };
|
|
@@ -42126,7 +41822,7 @@ ${aiFill(`create_${singular}`, {
|
|
|
42126
41822
|
}
|
|
42127
41823
|
if (model) {
|
|
42128
41824
|
writeFileSafe(
|
|
42129
|
-
|
|
41825
|
+
join33(idDir, "get.ts"),
|
|
42130
41826
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42131
41827
|
${modelImportId}
|
|
42132
41828
|
export const meta = { summary: "Get a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -42144,7 +41840,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42144
41840
|
);
|
|
42145
41841
|
} else {
|
|
42146
41842
|
writeFileSafe(
|
|
42147
|
-
|
|
41843
|
+
join33(idDir, "get.ts"),
|
|
42148
41844
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42149
41845
|
|
|
42150
41846
|
export const meta = { summary: "Get a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -42163,7 +41859,7 @@ ${aiFill(`get_${singular}`, {
|
|
|
42163
41859
|
}
|
|
42164
41860
|
if (model) {
|
|
42165
41861
|
writeFileSafe(
|
|
42166
|
-
|
|
41862
|
+
join33(idDir, "put.ts"),
|
|
42167
41863
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42168
41864
|
${modelImportId}${secureOptOut(isPublic)}export const meta = { summary: "Update a ${singular} by ID", tags: ["${routePath}"] };
|
|
42169
41865
|
|
|
@@ -42191,7 +41887,7 @@ ${extend(
|
|
|
42191
41887
|
);
|
|
42192
41888
|
} else {
|
|
42193
41889
|
writeFileSafe(
|
|
42194
|
-
|
|
41890
|
+
join33(idDir, "put.ts"),
|
|
42195
41891
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42196
41892
|
|
|
42197
41893
|
${secureOptOut(isPublic)}export const meta = { summary: "Update a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -42211,7 +41907,7 @@ ${aiFill(`update_${singular}`, {
|
|
|
42211
41907
|
}
|
|
42212
41908
|
if (model) {
|
|
42213
41909
|
writeFileSafe(
|
|
42214
|
-
|
|
41910
|
+
join33(idDir, "delete.ts"),
|
|
42215
41911
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42216
41912
|
${modelImportId}${secureOptOut(isPublic)}export const meta = { summary: "Delete a ${singular} by ID", tags: ["${routePath}"] };
|
|
42217
41913
|
|
|
@@ -42230,7 +41926,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42230
41926
|
);
|
|
42231
41927
|
} else {
|
|
42232
41928
|
writeFileSafe(
|
|
42233
|
-
|
|
41929
|
+
join33(idDir, "delete.ts"),
|
|
42234
41930
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42235
41931
|
|
|
42236
41932
|
${secureOptOut(isPublic)}export const meta = { summary: "Delete a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -42275,7 +41971,7 @@ function generateCrud(name, flags) {
|
|
|
42275
41971
|
}
|
|
42276
41972
|
function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest = true) {
|
|
42277
41973
|
const ts = timestamp();
|
|
42278
|
-
const dir =
|
|
41974
|
+
const dir = resolve28("migrations");
|
|
42279
41975
|
ensureDir(dir);
|
|
42280
41976
|
let table2;
|
|
42281
41977
|
if (tableOverride) {
|
|
@@ -42287,7 +41983,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
|
|
|
42287
41983
|
const fields = fieldsOverride || parseFields(flags.fields || "");
|
|
42288
41984
|
const isCreate = name.startsWith("create_") || fieldsOverride !== void 0;
|
|
42289
41985
|
const fileName = `${ts}_${name}.sql`;
|
|
42290
|
-
const path8 =
|
|
41986
|
+
const path8 = join33(dir, fileName);
|
|
42291
41987
|
let upSql;
|
|
42292
41988
|
let downSql;
|
|
42293
41989
|
if (isCreate) {
|
|
@@ -42319,7 +42015,7 @@ ${upSql}
|
|
|
42319
42015
|
${downSql}
|
|
42320
42016
|
`;
|
|
42321
42017
|
writeFileSafe(path8, content);
|
|
42322
|
-
const downPath =
|
|
42018
|
+
const downPath = join33(dir, `${ts}_${name}.down.sql`);
|
|
42323
42019
|
const downContent = `-- Rollback: ${name}
|
|
42324
42020
|
-- Created: ${now}
|
|
42325
42021
|
|
|
@@ -42330,9 +42026,9 @@ ${downSql}
|
|
|
42330
42026
|
}
|
|
42331
42027
|
function generateMiddleware(name, _flags) {
|
|
42332
42028
|
const snake = toSnake(name);
|
|
42333
|
-
const dir =
|
|
42029
|
+
const dir = resolve28("src/middleware");
|
|
42334
42030
|
ensureDir(dir);
|
|
42335
|
-
const path8 =
|
|
42031
|
+
const path8 = join33(dir, `${snake}.ts`);
|
|
42336
42032
|
const content = `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42337
42033
|
|
|
42338
42034
|
/**
|
|
@@ -42371,9 +42067,9 @@ function generateTest(name, flags) {
|
|
|
42371
42067
|
const snake = toSnake(name);
|
|
42372
42068
|
const singular = snake.endsWith("s") ? snake.slice(0, -1) : snake;
|
|
42373
42069
|
const model = flags.model;
|
|
42374
|
-
const dir =
|
|
42070
|
+
const dir = resolve28("tests");
|
|
42375
42071
|
ensureDir(dir);
|
|
42376
|
-
const path8 =
|
|
42072
|
+
const path8 = join33(dir, `${snake}.test.ts`);
|
|
42377
42073
|
if (model && flags["secure-writes"]) {
|
|
42378
42074
|
const isPublic = Boolean(flags.public);
|
|
42379
42075
|
const posture = isPublic ? "open (--public)" : "gated";
|
|
@@ -42508,9 +42204,9 @@ function generateForm(name, flags) {
|
|
|
42508
42204
|
datetime: "datetime-local",
|
|
42509
42205
|
blob: "file"
|
|
42510
42206
|
};
|
|
42511
|
-
const dir =
|
|
42207
|
+
const dir = resolve28("src/templates/forms");
|
|
42512
42208
|
ensureDir(dir);
|
|
42513
|
-
const path8 =
|
|
42209
|
+
const path8 = join33(dir, `${table2}.twig`);
|
|
42514
42210
|
let fieldHtml = "";
|
|
42515
42211
|
for (const [fname, ftype] of fields) {
|
|
42516
42212
|
const itype = inputTypes[ftype] || "text";
|
|
@@ -42560,9 +42256,9 @@ function generateView(name, flags) {
|
|
|
42560
42256
|
const table2 = toTableName(name);
|
|
42561
42257
|
const routeName = toPlural(table2);
|
|
42562
42258
|
const cols = fields.map(([f]) => f);
|
|
42563
|
-
const dir =
|
|
42259
|
+
const dir = resolve28("src/templates/pages");
|
|
42564
42260
|
ensureDir(dir);
|
|
42565
|
-
const listPath =
|
|
42261
|
+
const listPath = join33(dir, `${routeName}.twig`);
|
|
42566
42262
|
const th = cols.map((c) => ` <th>${c.replace(/_/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase())}</th>`).join("\n");
|
|
42567
42263
|
const td = cols.map((c) => ` <td>{{ item.${c} }}</td>`).join("\n");
|
|
42568
42264
|
const listContent = `{% extends "base.twig" %}
|
|
@@ -42598,7 +42294,7 @@ ${td}
|
|
|
42598
42294
|
{% endblock %}
|
|
42599
42295
|
`;
|
|
42600
42296
|
writeFileSafe(listPath, listContent);
|
|
42601
|
-
const detailPath =
|
|
42297
|
+
const detailPath = join33(dir, `${table2}.twig`);
|
|
42602
42298
|
const detailFields = cols.map((c) => ` <div class="mb-3"><strong>${c.replace(/_/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase())}:</strong> {{ item.${c} }}</div>`).join("\n");
|
|
42603
42299
|
const detailContent = `{% extends "base.twig" %}
|
|
42604
42300
|
{% block title %}${name} Detail{% endblock %}
|
|
@@ -42620,14 +42316,14 @@ ${detailFields}
|
|
|
42620
42316
|
function generateAuth(_flags) {
|
|
42621
42317
|
console.log("\n Generating authentication scaffolding...\n");
|
|
42622
42318
|
generateModel("User", { fields: "email:string,password:string,role:string" }, false);
|
|
42623
|
-
const registerDir =
|
|
42624
|
-
const loginDir =
|
|
42625
|
-
const meDir =
|
|
42319
|
+
const registerDir = resolve28("src/routes/api/auth/register");
|
|
42320
|
+
const loginDir = resolve28("src/routes/api/auth/login");
|
|
42321
|
+
const meDir = resolve28("src/routes/api/auth/me");
|
|
42626
42322
|
ensureDir(registerDir);
|
|
42627
42323
|
ensureDir(loginDir);
|
|
42628
42324
|
ensureDir(meDir);
|
|
42629
42325
|
writeFileSafe(
|
|
42630
|
-
|
|
42326
|
+
join33(registerDir, "post.ts"),
|
|
42631
42327
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42632
42328
|
import { hashPassword } from "tina4-nodejs";
|
|
42633
42329
|
import User from "../../../../models/User.js";
|
|
@@ -42658,7 +42354,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42658
42354
|
`
|
|
42659
42355
|
);
|
|
42660
42356
|
writeFileSafe(
|
|
42661
|
-
|
|
42357
|
+
join33(loginDir, "post.ts"),
|
|
42662
42358
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42663
42359
|
import { checkPassword, getToken } from "tina4-nodejs";
|
|
42664
42360
|
import User from "../../../../models/User.js";
|
|
@@ -42689,7 +42385,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42689
42385
|
`
|
|
42690
42386
|
);
|
|
42691
42387
|
writeFileSafe(
|
|
42692
|
-
|
|
42388
|
+
join33(meDir, "get.ts"),
|
|
42693
42389
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42694
42390
|
import { authenticateRequest } from "tina4-nodejs";
|
|
42695
42391
|
|
|
@@ -42705,10 +42401,10 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42705
42401
|
}
|
|
42706
42402
|
`
|
|
42707
42403
|
);
|
|
42708
|
-
const formsDir =
|
|
42404
|
+
const formsDir = resolve28("src/templates/forms");
|
|
42709
42405
|
ensureDir(formsDir);
|
|
42710
42406
|
writeFileSafe(
|
|
42711
|
-
|
|
42407
|
+
join33(formsDir, "login.twig"),
|
|
42712
42408
|
`{% extends "base.twig" %}
|
|
42713
42409
|
{% block title %}Login{% endblock %}
|
|
42714
42410
|
{% block content %}
|
|
@@ -42732,7 +42428,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42732
42428
|
`
|
|
42733
42429
|
);
|
|
42734
42430
|
writeFileSafe(
|
|
42735
|
-
|
|
42431
|
+
join33(formsDir, "register.twig"),
|
|
42736
42432
|
`{% extends "base.twig" %}
|
|
42737
42433
|
{% block title %}Register{% endblock %}
|
|
42738
42434
|
{% block content %}
|
|
@@ -42766,9 +42462,9 @@ function generateService(name, flags) {
|
|
|
42766
42462
|
const snake = toSnake(name);
|
|
42767
42463
|
const camel = toCamel(toPascal(name)) || snake;
|
|
42768
42464
|
const cron = flags.cron;
|
|
42769
|
-
const dir =
|
|
42465
|
+
const dir = resolve28("src/services");
|
|
42770
42466
|
ensureDir(dir);
|
|
42771
|
-
const path8 =
|
|
42467
|
+
const path8 = join33(dir, `${snake}.ts`);
|
|
42772
42468
|
let scheduleField;
|
|
42773
42469
|
let note;
|
|
42774
42470
|
if (cron && cron !== true) {
|
|
@@ -42817,9 +42513,9 @@ function generateQueue(name, _flags) {
|
|
|
42817
42513
|
const topic = name.replace(/^\//, "");
|
|
42818
42514
|
const slug = toSnake(topic.replace(/[^0-9a-zA-Z]+/g, "_")).replace(/^_+|_+$/g, "") || "topic";
|
|
42819
42515
|
const pascal = toPascal(topic) || "Topic";
|
|
42820
|
-
const dir =
|
|
42516
|
+
const dir = resolve28("src/services");
|
|
42821
42517
|
ensureDir(dir);
|
|
42822
|
-
const path8 =
|
|
42518
|
+
const path8 = join33(dir, `${slug}_consumer.ts`);
|
|
42823
42519
|
const body = aiFill(`handle${pascal}`, {
|
|
42824
42520
|
intent: `process ONE ${topic} job payload`,
|
|
42825
42521
|
given: "payload -> the produced job data (job.payload)",
|
|
@@ -42877,9 +42573,9 @@ export default {
|
|
|
42877
42573
|
emitQueueTest(topic, slug, pascal);
|
|
42878
42574
|
}
|
|
42879
42575
|
function generateValidator(name, _flags) {
|
|
42880
|
-
const dir =
|
|
42576
|
+
const dir = resolve28("src/validators");
|
|
42881
42577
|
ensureDir(dir);
|
|
42882
|
-
const path8 =
|
|
42578
|
+
const path8 = join33(dir, `${toSnake(name)}.ts`);
|
|
42883
42579
|
const rules = extend(
|
|
42884
42580
|
"add / adjust the validation rules for this payload",
|
|
42885
42581
|
`e.g. .email("email").minLength("name", 2).integer("age"); ground: tina4_context("validate request body with Validator", "nodejs")`
|
|
@@ -42904,9 +42600,9 @@ ${rules} validator.required("name"); // starter rule (matches the model's def
|
|
|
42904
42600
|
}
|
|
42905
42601
|
function generateSeeder(name, _flags) {
|
|
42906
42602
|
const table2 = toTableName(name);
|
|
42907
|
-
const dir =
|
|
42603
|
+
const dir = resolve28("src/seeds");
|
|
42908
42604
|
ensureDir(dir);
|
|
42909
|
-
const path8 =
|
|
42605
|
+
const path8 = join33(dir, `${table2}_seeder.ts`);
|
|
42910
42606
|
const overrides = extend(
|
|
42911
42607
|
"override fields that need a specific shape (seedOrm auto-fills the rest)",
|
|
42912
42608
|
`e.g. return { email: (f) => f.email(), status: "active" }; ground: tina4_context("seed ORM model with FakeData", "nodejs")`
|
|
@@ -42948,9 +42644,9 @@ function generateWebsocket(name, _flags) {
|
|
|
42948
42644
|
let slug = toSnake(raw.replace(/^\/+|\/+$/g, "").replace(/[^0-9a-zA-Z]+/g, "_")).replace(/^_+|_+$/g, "") || "ws";
|
|
42949
42645
|
const base = slug.startsWith("ws_") ? slug.slice(3) : slug;
|
|
42950
42646
|
const handlerName = `${toCamel(toPascal(base))}Ws`;
|
|
42951
|
-
const dir =
|
|
42647
|
+
const dir = resolve28("src/routes");
|
|
42952
42648
|
ensureDir(dir);
|
|
42953
|
-
const path8 =
|
|
42649
|
+
const path8 = join33(dir, `ws_${base}.ts`);
|
|
42954
42650
|
const body = aiFill(handlerName, {
|
|
42955
42651
|
intent: `handle an inbound "message" frame on ${wsPath}`,
|
|
42956
42652
|
given: "data -> the message payload (string); connection -> WebSocketConnection",
|
|
@@ -42997,9 +42693,9 @@ function generateListener(name, _flags) {
|
|
|
42997
42693
|
const event = name.trim();
|
|
42998
42694
|
const slug = toSnake(event.replace(/[^0-9a-zA-Z]+/g, "_")).replace(/^_+|_+$/g, "") || "event";
|
|
42999
42695
|
const handlerName = `on${toPascal(slug)}`;
|
|
43000
|
-
const dir =
|
|
42696
|
+
const dir = resolve28("src/listeners");
|
|
43001
42697
|
ensureDir(dir);
|
|
43002
|
-
const path8 =
|
|
42698
|
+
const path8 = join33(dir, `${slug}.ts`);
|
|
43003
42699
|
const body = aiFill(handlerName, {
|
|
43004
42700
|
intent: `react to the '${event}' event`,
|
|
43005
42701
|
given: `args -> whatever Events.emit("${event}", ...args) passed`,
|
|
@@ -43028,9 +42724,9 @@ Events.on("${event}", ${handlerName});
|
|
|
43028
42724
|
emitListenerTest(event, slug);
|
|
43029
42725
|
}
|
|
43030
42726
|
function writeTest(testName, content) {
|
|
43031
|
-
const dir =
|
|
42727
|
+
const dir = resolve28("tests");
|
|
43032
42728
|
ensureDir(dir);
|
|
43033
|
-
writeFileSafe(
|
|
42729
|
+
writeFileSafe(join33(dir, `${testName}.test.ts`), content);
|
|
43034
42730
|
}
|
|
43035
42731
|
function standaloneTest(doc, body) {
|
|
43036
42732
|
return `${doc}
|
|
@@ -43414,15 +43110,15 @@ assert("DOWN drops the ${table2} table", db.tableExists("${table2}") === false);
|
|
|
43414
43110
|
}
|
|
43415
43111
|
|
|
43416
43112
|
// src/commands/seed.ts
|
|
43417
|
-
import { existsSync as
|
|
43418
|
-
import { resolve as
|
|
43113
|
+
import { existsSync as existsSync33, readdirSync as readdirSync22 } from "node:fs";
|
|
43114
|
+
import { resolve as resolve29, join as join34 } from "node:path";
|
|
43419
43115
|
import { execSync as execSync4 } from "node:child_process";
|
|
43420
43116
|
async function runSeeds(seedPath) {
|
|
43421
43117
|
const cwd = process.cwd();
|
|
43422
|
-
const seedDir =
|
|
43118
|
+
const seedDir = resolve29(cwd, "src/seeds");
|
|
43423
43119
|
if (seedPath) {
|
|
43424
|
-
const file =
|
|
43425
|
-
if (!
|
|
43120
|
+
const file = resolve29(seedPath);
|
|
43121
|
+
if (!existsSync33(file)) {
|
|
43426
43122
|
console.error(` Error: Seed file not found: ${seedPath}`);
|
|
43427
43123
|
process.exit(1);
|
|
43428
43124
|
}
|
|
@@ -43435,14 +43131,14 @@ async function runSeeds(seedPath) {
|
|
|
43435
43131
|
}
|
|
43436
43132
|
return;
|
|
43437
43133
|
}
|
|
43438
|
-
if (!
|
|
43134
|
+
if (!existsSync33(seedDir)) {
|
|
43439
43135
|
console.log(" No seeds directory found.");
|
|
43440
43136
|
console.log(" Create seed files in src/seeds/ (e.g. src/seeds/001-users.ts)");
|
|
43441
43137
|
return;
|
|
43442
43138
|
}
|
|
43443
43139
|
let seedFiles;
|
|
43444
43140
|
try {
|
|
43445
|
-
seedFiles =
|
|
43141
|
+
seedFiles = readdirSync22(seedDir).filter((f) => f.endsWith(".ts")).sort().map((f) => join34(seedDir, f));
|
|
43446
43142
|
} catch {
|
|
43447
43143
|
console.log(" Could not read src/seeds/ directory.");
|
|
43448
43144
|
return;
|
|
@@ -43580,8 +43276,8 @@ function runMetrics(args = []) {
|
|
|
43580
43276
|
// src/commands/queue.ts
|
|
43581
43277
|
init_dotenv();
|
|
43582
43278
|
init_queue();
|
|
43583
|
-
import { readdirSync as
|
|
43584
|
-
import { extname as extname9, join as
|
|
43279
|
+
import { readdirSync as readdirSync23, statSync as statSync19 } from "node:fs";
|
|
43280
|
+
import { extname as extname9, join as join35 } from "node:path";
|
|
43585
43281
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
43586
43282
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["once", "json"]);
|
|
43587
43283
|
function parseFlags2(args) {
|
|
@@ -43613,7 +43309,7 @@ async function resolveQueueHandler(servicesDir, topic) {
|
|
|
43613
43309
|
let entries;
|
|
43614
43310
|
try {
|
|
43615
43311
|
if (!statSync19(servicesDir).isDirectory()) return null;
|
|
43616
|
-
entries =
|
|
43312
|
+
entries = readdirSync23(servicesDir);
|
|
43617
43313
|
} catch {
|
|
43618
43314
|
return null;
|
|
43619
43315
|
}
|
|
@@ -43621,7 +43317,7 @@ async function resolveQueueHandler(servicesDir, topic) {
|
|
|
43621
43317
|
if (entry.startsWith("_")) continue;
|
|
43622
43318
|
const ext = extname9(entry);
|
|
43623
43319
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
43624
|
-
const fullPath =
|
|
43320
|
+
const fullPath = join35(servicesDir, entry);
|
|
43625
43321
|
try {
|
|
43626
43322
|
if (!statSync19(fullPath).isFile()) continue;
|
|
43627
43323
|
const mod = await import(pathToFileURL2(fullPath).href);
|
|
@@ -43773,8 +43469,8 @@ async function queueCommand(args = []) {
|
|
|
43773
43469
|
}
|
|
43774
43470
|
|
|
43775
43471
|
// src/commands/build.ts
|
|
43776
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as
|
|
43777
|
-
import { basename as
|
|
43472
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync34, statSync as statSync20 } from "node:fs";
|
|
43473
|
+
import { basename as basename7, delimiter as delimiter2, join as join36 } from "node:path";
|
|
43778
43474
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
43779
43475
|
function parseFlags3(args) {
|
|
43780
43476
|
const flags = {};
|
|
@@ -43803,7 +43499,7 @@ function whichDocker() {
|
|
|
43803
43499
|
for (const dir of pathValue.split(delimiter2)) {
|
|
43804
43500
|
if (!dir) continue;
|
|
43805
43501
|
for (const ext of exts) {
|
|
43806
|
-
const candidate =
|
|
43502
|
+
const candidate = join36(dir, `docker${ext}`);
|
|
43807
43503
|
try {
|
|
43808
43504
|
accessSync2(candidate, constants2.X_OK);
|
|
43809
43505
|
if (statSync20(candidate).isFile()) return candidate;
|
|
@@ -43817,11 +43513,11 @@ function buildImage(args) {
|
|
|
43817
43513
|
const flags = parseFlags3(args);
|
|
43818
43514
|
let tag = typeof flags.tag === "string" ? flags.tag : "";
|
|
43819
43515
|
if (!tag) {
|
|
43820
|
-
const dirName =
|
|
43516
|
+
const dirName = basename7(process.cwd()).toLowerCase();
|
|
43821
43517
|
tag = `${dirName || "tina4app"}:latest`;
|
|
43822
43518
|
}
|
|
43823
43519
|
const dockerfile = typeof flags.file === "string" && flags.file ? flags.file : "Dockerfile";
|
|
43824
|
-
if (!
|
|
43520
|
+
if (!existsSync34(dockerfile) || !statSync20(dockerfile).isFile()) {
|
|
43825
43521
|
console.log(` \u2717 No ${dockerfile} found.`);
|
|
43826
43522
|
console.log(" A Tina4 app deploys as a container. Scaffold a Dockerfile first:");
|
|
43827
43523
|
console.log(" tina4 deploy docker (or: tina4nodejs init)");
|
|
@@ -43849,30 +43545,30 @@ function buildImage(args) {
|
|
|
43849
43545
|
|
|
43850
43546
|
// src/bin.ts
|
|
43851
43547
|
import { execSync as execSync5, spawnSync as spawnSync4 } from "node:child_process";
|
|
43852
|
-
import { existsSync as
|
|
43853
|
-
import { delimiter as delimiter3, dirname as
|
|
43548
|
+
import { existsSync as existsSync35, readFileSync as readFileSync27, statSync as statSync21 } from "node:fs";
|
|
43549
|
+
import { delimiter as delimiter3, dirname as dirname14, join as join37 } from "node:path";
|
|
43854
43550
|
import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL3 } from "node:url";
|
|
43855
43551
|
function readCliVersion() {
|
|
43856
|
-
let dir =
|
|
43552
|
+
let dir = dirname14(fileURLToPath8(import.meta.url));
|
|
43857
43553
|
for (let i = 0; i < 6; i++) {
|
|
43858
|
-
const pkgPath =
|
|
43859
|
-
if (
|
|
43554
|
+
const pkgPath = join37(dir, "package.json");
|
|
43555
|
+
if (existsSync35(pkgPath)) {
|
|
43860
43556
|
try {
|
|
43861
|
-
const pkg = JSON.parse(
|
|
43557
|
+
const pkg = JSON.parse(readFileSync27(pkgPath, "utf-8"));
|
|
43862
43558
|
if (typeof pkg.version === "string" && pkg.version) return pkg.version;
|
|
43863
43559
|
} catch {
|
|
43864
43560
|
}
|
|
43865
43561
|
}
|
|
43866
|
-
const parent =
|
|
43562
|
+
const parent = dirname14(dir);
|
|
43867
43563
|
if (parent === dir) break;
|
|
43868
43564
|
dir = parent;
|
|
43869
43565
|
}
|
|
43870
43566
|
return "0.0.0";
|
|
43871
43567
|
}
|
|
43872
43568
|
function inContainer() {
|
|
43873
|
-
if (
|
|
43569
|
+
if (existsSync35("/.dockerenv") || existsSync35("/run/.containerenv")) return true;
|
|
43874
43570
|
try {
|
|
43875
|
-
const blob =
|
|
43571
|
+
const blob = readFileSync27("/proc/1/cgroup", "utf-8");
|
|
43876
43572
|
return blob.includes("docker") || blob.includes("containerd") || blob.includes("kubepods");
|
|
43877
43573
|
} catch {
|
|
43878
43574
|
return false;
|
|
@@ -44017,7 +43713,7 @@ async function openConsole() {
|
|
|
44017
43713
|
r.context.Database = Database2;
|
|
44018
43714
|
r.context.Log = Log2;
|
|
44019
43715
|
r.context.db = db;
|
|
44020
|
-
await new Promise((
|
|
43716
|
+
await new Promise((resolve30) => r.on("exit", resolve30));
|
|
44021
43717
|
}
|
|
44022
43718
|
async function installAiContext(args) {
|
|
44023
43719
|
const { showMenu: showMenu2, installSelected: installSelected2, installAll: installAll2 } = await Promise.resolve().then(() => (init_ai(), ai_exports));
|
|
@@ -44175,7 +43871,7 @@ function findClient() {
|
|
|
44175
43871
|
for (const dir of (process.env.PATH ?? "").split(delimiter3)) {
|
|
44176
43872
|
if (!dir) continue;
|
|
44177
43873
|
for (const name of names) {
|
|
44178
|
-
const candidate =
|
|
43874
|
+
const candidate = join37(dir, name);
|
|
44179
43875
|
try {
|
|
44180
43876
|
if (statSync21(candidate).isFile()) return candidate;
|
|
44181
43877
|
} catch {
|