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
|
@@ -1828,64 +1828,6 @@ var init_middleware = __esm({
|
|
|
1828
1828
|
}
|
|
1829
1829
|
});
|
|
1830
1830
|
|
|
1831
|
-
// ../orm/src/types.ts
|
|
1832
|
-
var FetchResult;
|
|
1833
|
-
var init_types = __esm({
|
|
1834
|
-
"../orm/src/types.ts"() {
|
|
1835
|
-
"use strict";
|
|
1836
|
-
FetchResult = class {
|
|
1837
|
-
records;
|
|
1838
|
-
count;
|
|
1839
|
-
sql;
|
|
1840
|
-
constructor(records, sql = "") {
|
|
1841
|
-
this.records = records;
|
|
1842
|
-
this.count = records.length;
|
|
1843
|
-
this.sql = sql;
|
|
1844
|
-
}
|
|
1845
|
-
/** Paginate the in-memory result set. */
|
|
1846
|
-
toPaginate(page = 1, perPage = 20) {
|
|
1847
|
-
const total = this.count;
|
|
1848
|
-
const totalPages = Math.max(1, Math.ceil(total / perPage));
|
|
1849
|
-
const offset = (page - 1) * perPage;
|
|
1850
|
-
const data = this.records.slice(offset, offset + perPage);
|
|
1851
|
-
return {
|
|
1852
|
-
data,
|
|
1853
|
-
page,
|
|
1854
|
-
perPage,
|
|
1855
|
-
total,
|
|
1856
|
-
totalPages,
|
|
1857
|
-
hasNext: page < totalPages,
|
|
1858
|
-
hasPrev: page > 1
|
|
1859
|
-
};
|
|
1860
|
-
}
|
|
1861
|
-
/** Return the first record or null. */
|
|
1862
|
-
first() {
|
|
1863
|
-
return this.records[0] ?? null;
|
|
1864
|
-
}
|
|
1865
|
-
/** Return the last record or null. */
|
|
1866
|
-
last() {
|
|
1867
|
-
return this.records[this.records.length - 1] ?? null;
|
|
1868
|
-
}
|
|
1869
|
-
/** Check if result is empty. */
|
|
1870
|
-
isEmpty() {
|
|
1871
|
-
return this.records.length === 0;
|
|
1872
|
-
}
|
|
1873
|
-
/** Convert to plain array. */
|
|
1874
|
-
toArray() {
|
|
1875
|
-
return [...this.records];
|
|
1876
|
-
}
|
|
1877
|
-
/** Convert to JSON string. */
|
|
1878
|
-
toJSON() {
|
|
1879
|
-
return JSON.stringify(this.records);
|
|
1880
|
-
}
|
|
1881
|
-
/** Iterate over records. */
|
|
1882
|
-
[Symbol.iterator]() {
|
|
1883
|
-
return this.records[Symbol.iterator]();
|
|
1884
|
-
}
|
|
1885
|
-
};
|
|
1886
|
-
}
|
|
1887
|
-
});
|
|
1888
|
-
|
|
1889
1831
|
// ../orm/src/databaseResult.ts
|
|
1890
1832
|
var DatabaseResult;
|
|
1891
1833
|
var init_databaseResult = __esm({
|
|
@@ -1949,75 +1891,52 @@ var init_databaseResult = __esm({
|
|
|
1949
1891
|
toArray() {
|
|
1950
1892
|
return this.records;
|
|
1951
1893
|
}
|
|
1952
|
-
/**
|
|
1894
|
+
/**
|
|
1895
|
+
* Describe the page this result IS — the canonical pagination envelope.
|
|
1953
1896
|
*
|
|
1954
|
-
*
|
|
1955
|
-
* (
|
|
1956
|
-
*
|
|
1957
|
-
*
|
|
1897
|
+
* Takes NO arguments and derives every field from the query that produced this
|
|
1898
|
+
* result (ADR-0043). Passing ANY argument RAISES: a DatabaseResult holds no
|
|
1899
|
+
* connection, so an argument could only re-slice the rows already in memory and
|
|
1900
|
+
* then report total_pages for pages it can never reach. To read page N, FETCH
|
|
1901
|
+
* page N (limit + offset) and call this with no arguments.
|
|
1958
1902
|
*
|
|
1959
|
-
*
|
|
1960
|
-
|
|
1961
|
-
/**
|
|
1962
|
-
* Describe the page this result actually IS. Takes no arguments.
|
|
1903
|
+
* The envelope is EXACTLY seven snake_case keys, identical across all four
|
|
1904
|
+
* frameworks: `records, total, page, per_page, total_pages, limit, offset`.
|
|
1963
1905
|
*
|
|
1964
|
-
*
|
|
1965
|
-
* (
|
|
1966
|
-
*
|
|
1967
|
-
*
|
|
1968
|
-
*
|
|
1969
|
-
*
|
|
1970
|
-
*
|
|
1906
|
+
* per_page = the query's limit
|
|
1907
|
+
* page = floor(offset / limit) + 1
|
|
1908
|
+
* total = the TRUE total for the filter — Database.fetch (and
|
|
1909
|
+
* QueryBuilder.get) run a COUNT probe whenever a limit was
|
|
1910
|
+
* applied — NEVER the number of rows returned
|
|
1911
|
+
* total_pages = ceil(total / per_page)
|
|
1912
|
+
* records = the rows the query returned, VERBATIM (never re-sliced)
|
|
1913
|
+
* limit = the SQL limit actually applied
|
|
1914
|
+
* offset = the SQL offset actually applied
|
|
1971
1915
|
*
|
|
1972
|
-
*
|
|
1973
|
-
*
|
|
1974
|
-
* (
|
|
1975
|
-
*
|
|
1976
|
-
* pages 1-5 of 20 were right and every page from 6 onward came back EMPTY
|
|
1977
|
-
* while totalPages reported 5,000.
|
|
1916
|
+
* The JSON payload is snake_case even though the method name is camelCase — a
|
|
1917
|
+
* JSON key is data, not a language surface (ADR-0043). The old duplicate and
|
|
1918
|
+
* camelCase keys (`data`, `count`, `perPage`, `totalPages`, `has_next`,
|
|
1919
|
+
* `has_prev`) are removed: Node emitted 13 keys, the worst offender of the four.
|
|
1978
1920
|
*
|
|
1979
|
-
*
|
|
1980
|
-
* all four frameworks - Database.fetch runs a COUNT probe whenever it applied
|
|
1981
|
-
* a limit. It used to be ROWS RETURNED here and in Ruby while Python and PHP
|
|
1982
|
-
* probed, so one query answered 20 in two frameworks and 250 in the other
|
|
1983
|
-
* two.
|
|
1921
|
+
* @throws {TypeError} if called with any argument.
|
|
1984
1922
|
*/
|
|
1985
|
-
toPaginate(
|
|
1986
|
-
if (
|
|
1923
|
+
toPaginate() {
|
|
1924
|
+
if (arguments.length > 0) {
|
|
1987
1925
|
throw new TypeError(
|
|
1988
|
-
|
|
1926
|
+
"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."
|
|
1989
1927
|
);
|
|
1990
1928
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
let rows;
|
|
1995
|
-
if (page === void 0 && perPage === void 0) {
|
|
1996
|
-
resolvedPerPage = this.limit > 0 ? this.limit : this.records.length;
|
|
1997
|
-
resolvedPage = resolvedPerPage > 0 ? Math.floor(this.offset / resolvedPerPage) + 1 : 1;
|
|
1998
|
-
offset = this.offset;
|
|
1999
|
-
rows = this.records;
|
|
2000
|
-
} else {
|
|
2001
|
-
resolvedPage = page ?? 1;
|
|
2002
|
-
resolvedPerPage = perPage ?? (this.limit > 0 ? this.limit : 10);
|
|
2003
|
-
offset = (resolvedPage - 1) * resolvedPerPage;
|
|
2004
|
-
rows = this.records.slice(offset, offset + resolvedPerPage);
|
|
2005
|
-
}
|
|
2006
|
-
const totalPages = resolvedPerPage > 0 ? Math.max(1, Math.ceil(this.count / resolvedPerPage)) : 1;
|
|
1929
|
+
const perPage = this.limit > 0 ? this.limit : this.records.length;
|
|
1930
|
+
const page = perPage > 0 ? Math.floor(this.offset / perPage) + 1 : 1;
|
|
1931
|
+
const totalPages = perPage > 0 ? Math.max(1, Math.ceil(this.count / perPage)) : 1;
|
|
2007
1932
|
return {
|
|
2008
|
-
records:
|
|
2009
|
-
data: rows,
|
|
2010
|
-
count: this.count,
|
|
1933
|
+
records: this.records,
|
|
2011
1934
|
total: this.count,
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
page: resolvedPage,
|
|
2015
|
-
per_page: resolvedPerPage,
|
|
2016
|
-
perPage: resolvedPerPage,
|
|
2017
|
-
totalPages,
|
|
1935
|
+
page,
|
|
1936
|
+
per_page: perPage,
|
|
2018
1937
|
total_pages: totalPages,
|
|
2019
|
-
|
|
2020
|
-
|
|
1938
|
+
limit: perPage,
|
|
1939
|
+
offset: this.offset
|
|
2021
1940
|
};
|
|
2022
1941
|
}
|
|
2023
1942
|
/** Iterable — for (const row of result) */
|
|
@@ -3861,7 +3780,7 @@ function withConnectTimeout(attempt, budgetMs, host, port, abandon) {
|
|
|
3861
3780
|
const elapsedMs = () => performance.now() - startedAt;
|
|
3862
3781
|
if (budgetMs === null) return attempt();
|
|
3863
3782
|
const started = attempt();
|
|
3864
|
-
return new Promise((
|
|
3783
|
+
return new Promise((resolve20, reject) => {
|
|
3865
3784
|
let expired = false;
|
|
3866
3785
|
const timer = setTimeout(() => {
|
|
3867
3786
|
expired = true;
|
|
@@ -3871,7 +3790,7 @@ function withConnectTimeout(attempt, budgetMs, host, port, abandon) {
|
|
|
3871
3790
|
(arrived) => {
|
|
3872
3791
|
clearTimeout(timer);
|
|
3873
3792
|
if (expired) abandon?.(arrived);
|
|
3874
|
-
else
|
|
3793
|
+
else resolve20(arrived);
|
|
3875
3794
|
},
|
|
3876
3795
|
(failure) => {
|
|
3877
3796
|
clearTimeout(timer);
|
|
@@ -4428,10 +4347,10 @@ var init_mysql = __esm({
|
|
|
4428
4347
|
...timeoutOption
|
|
4429
4348
|
});
|
|
4430
4349
|
}
|
|
4431
|
-
return new Promise((
|
|
4350
|
+
return new Promise((resolve20, reject) => {
|
|
4432
4351
|
this.connection.connect((err) => {
|
|
4433
4352
|
if (err) reject(err);
|
|
4434
|
-
else
|
|
4353
|
+
else resolve20();
|
|
4435
4354
|
});
|
|
4436
4355
|
});
|
|
4437
4356
|
},
|
|
@@ -4453,10 +4372,10 @@ var init_mysql = __esm({
|
|
|
4453
4372
|
}
|
|
4454
4373
|
}
|
|
4455
4374
|
queryPromise(sql, params) {
|
|
4456
|
-
return new Promise((
|
|
4375
|
+
return new Promise((resolve20, reject) => {
|
|
4457
4376
|
this.connection.query(sql, params ?? [], (err, results) => {
|
|
4458
4377
|
if (err) reject(err);
|
|
4459
|
-
else
|
|
4378
|
+
else resolve20(results);
|
|
4460
4379
|
});
|
|
4461
4380
|
});
|
|
4462
4381
|
}
|
|
@@ -4826,11 +4745,11 @@ var init_mssql = __esm({
|
|
|
4826
4745
|
};
|
|
4827
4746
|
}
|
|
4828
4747
|
await withConnectTimeout(
|
|
4829
|
-
() => new Promise((
|
|
4748
|
+
() => new Promise((resolve20, reject) => {
|
|
4830
4749
|
this.connection = new Connection(tediousConfig);
|
|
4831
4750
|
this.connection.on("connect", (err) => {
|
|
4832
4751
|
if (err) reject(err);
|
|
4833
|
-
else
|
|
4752
|
+
else resolve20();
|
|
4834
4753
|
});
|
|
4835
4754
|
this.connection.connect();
|
|
4836
4755
|
}),
|
|
@@ -4875,11 +4794,11 @@ var init_mssql = __esm({
|
|
|
4875
4794
|
const tediousModule = requireTedious();
|
|
4876
4795
|
const Request = tediousModule.Request;
|
|
4877
4796
|
const TYPES = tediousModule.TYPES;
|
|
4878
|
-
return new Promise((
|
|
4797
|
+
return new Promise((resolve20, reject) => {
|
|
4879
4798
|
const rows = [];
|
|
4880
4799
|
const request = new Request(sql, (err, rowCount) => {
|
|
4881
4800
|
if (err) reject(err);
|
|
4882
|
-
else
|
|
4801
|
+
else resolve20({ rows, rowCount });
|
|
4883
4802
|
});
|
|
4884
4803
|
if (params) {
|
|
4885
4804
|
params.forEach((p, i) => {
|
|
@@ -5082,8 +5001,8 @@ var init_mssql = __esm({
|
|
|
5082
5001
|
throw new Error("Use startTransactionAsync() for MSSQL.");
|
|
5083
5002
|
}
|
|
5084
5003
|
async startTransactionAsync() {
|
|
5085
|
-
await new Promise((
|
|
5086
|
-
this.connection.beginTransaction((err) => err ? reject(err) :
|
|
5004
|
+
await new Promise((resolve20, reject) => {
|
|
5005
|
+
this.connection.beginTransaction((err) => err ? reject(err) : resolve20());
|
|
5087
5006
|
});
|
|
5088
5007
|
this._inTransaction = true;
|
|
5089
5008
|
}
|
|
@@ -5091,8 +5010,8 @@ var init_mssql = __esm({
|
|
|
5091
5010
|
throw new Error("Use commitAsync() for MSSQL.");
|
|
5092
5011
|
}
|
|
5093
5012
|
async commitAsync() {
|
|
5094
|
-
await new Promise((
|
|
5095
|
-
this.connection.commitTransaction((err) => err ? reject(err) :
|
|
5013
|
+
await new Promise((resolve20, reject) => {
|
|
5014
|
+
this.connection.commitTransaction((err) => err ? reject(err) : resolve20());
|
|
5096
5015
|
});
|
|
5097
5016
|
this._inTransaction = false;
|
|
5098
5017
|
}
|
|
@@ -5100,8 +5019,8 @@ var init_mssql = __esm({
|
|
|
5100
5019
|
throw new Error("Use rollbackAsync() for MSSQL.");
|
|
5101
5020
|
}
|
|
5102
5021
|
async rollbackAsync() {
|
|
5103
|
-
await new Promise((
|
|
5104
|
-
this.connection.rollbackTransaction((err) => err ? reject(err) :
|
|
5022
|
+
await new Promise((resolve20, reject) => {
|
|
5023
|
+
this.connection.rollbackTransaction((err) => err ? reject(err) : resolve20());
|
|
5105
5024
|
});
|
|
5106
5025
|
this._inTransaction = false;
|
|
5107
5026
|
}
|
|
@@ -5371,8 +5290,8 @@ var init_firebird = __esm({
|
|
|
5371
5290
|
fbConfig.database = normalizeFirebirdDbIdentifier(fbConfig.database);
|
|
5372
5291
|
}
|
|
5373
5292
|
this.db = await withConnectTimeout(
|
|
5374
|
-
() => new Promise((
|
|
5375
|
-
fb.attach(fbConfig, (err, db) => err ? reject(err) :
|
|
5293
|
+
() => new Promise((resolve20, reject) => {
|
|
5294
|
+
fb.attach(fbConfig, (err, db) => err ? reject(err) : resolve20(db));
|
|
5376
5295
|
}),
|
|
5377
5296
|
connectTimeoutMillis(),
|
|
5378
5297
|
fbConfig.host,
|
|
@@ -5439,20 +5358,20 @@ var init_firebird = __esm({
|
|
|
5439
5358
|
return this.transaction ?? this.db;
|
|
5440
5359
|
}
|
|
5441
5360
|
queryPromise(sql, params) {
|
|
5442
|
-
return new Promise((
|
|
5361
|
+
return new Promise((resolve20, reject) => {
|
|
5443
5362
|
const translated = this.translateSql(sql);
|
|
5444
5363
|
this.statementHandle().query(translated, params ?? [], (err, result) => {
|
|
5445
5364
|
if (err) reject(err);
|
|
5446
|
-
else
|
|
5365
|
+
else resolve20(result ?? []);
|
|
5447
5366
|
});
|
|
5448
5367
|
});
|
|
5449
5368
|
}
|
|
5450
5369
|
executePromise(sql, params) {
|
|
5451
|
-
return new Promise((
|
|
5370
|
+
return new Promise((resolve20, reject) => {
|
|
5452
5371
|
const translated = this.translateSql(sql);
|
|
5453
5372
|
this.statementHandle().execute(translated, params ?? [], (err) => {
|
|
5454
5373
|
if (err) reject(err);
|
|
5455
|
-
else
|
|
5374
|
+
else resolve20();
|
|
5456
5375
|
});
|
|
5457
5376
|
});
|
|
5458
5377
|
}
|
|
@@ -5568,12 +5487,12 @@ var init_firebird = __esm({
|
|
|
5568
5487
|
}
|
|
5569
5488
|
async startTransactionAsync() {
|
|
5570
5489
|
this.ensureConnected();
|
|
5571
|
-
await new Promise((
|
|
5490
|
+
await new Promise((resolve20, reject) => {
|
|
5572
5491
|
this.db.transaction(0, (err, transaction) => {
|
|
5573
5492
|
if (err) reject(err);
|
|
5574
5493
|
else {
|
|
5575
5494
|
this.transaction = transaction;
|
|
5576
|
-
|
|
5495
|
+
resolve20();
|
|
5577
5496
|
}
|
|
5578
5497
|
});
|
|
5579
5498
|
});
|
|
@@ -5583,12 +5502,12 @@ var init_firebird = __esm({
|
|
|
5583
5502
|
}
|
|
5584
5503
|
async commitAsync() {
|
|
5585
5504
|
if (!this.transaction) throw new Error("No active transaction to commit.");
|
|
5586
|
-
await new Promise((
|
|
5505
|
+
await new Promise((resolve20, reject) => {
|
|
5587
5506
|
this.transaction.commit((err) => {
|
|
5588
5507
|
if (err) reject(err);
|
|
5589
5508
|
else {
|
|
5590
5509
|
this.transaction = null;
|
|
5591
|
-
|
|
5510
|
+
resolve20();
|
|
5592
5511
|
}
|
|
5593
5512
|
});
|
|
5594
5513
|
});
|
|
@@ -5598,12 +5517,12 @@ var init_firebird = __esm({
|
|
|
5598
5517
|
}
|
|
5599
5518
|
async rollbackAsync() {
|
|
5600
5519
|
if (!this.transaction) throw new Error("No active transaction to rollback.");
|
|
5601
|
-
await new Promise((
|
|
5520
|
+
await new Promise((resolve20, reject) => {
|
|
5602
5521
|
this.transaction.rollback((err) => {
|
|
5603
5522
|
if (err) reject(err);
|
|
5604
5523
|
else {
|
|
5605
5524
|
this.transaction = null;
|
|
5606
|
-
|
|
5525
|
+
resolve20();
|
|
5607
5526
|
}
|
|
5608
5527
|
});
|
|
5609
5528
|
});
|
|
@@ -6664,6 +6583,7 @@ __export(database_exports, {
|
|
|
6664
6583
|
getNamedAdapter: () => getNamedAdapter,
|
|
6665
6584
|
initDatabase: () => initDatabase,
|
|
6666
6585
|
parseDatabaseUrl: () => parseDatabaseUrl,
|
|
6586
|
+
probeTotal: () => probeTotal,
|
|
6667
6587
|
resetRequestCaches: () => resetRequestCaches,
|
|
6668
6588
|
resolveDbPool: () => resolveDbPool,
|
|
6669
6589
|
setAdapter: () => setAdapter,
|
|
@@ -6717,6 +6637,29 @@ async function adapterCreateTable(adapter, name, columns) {
|
|
|
6717
6637
|
if (adapter.createTableAsync) await adapter.createTableAsync(name, columns);
|
|
6718
6638
|
else adapter.createTable(name, columns);
|
|
6719
6639
|
}
|
|
6640
|
+
async function probeTotal(adapter, sql, params, limit) {
|
|
6641
|
+
if (limit === void 0 || limit <= 0) return void 0;
|
|
6642
|
+
try {
|
|
6643
|
+
const alias = adapter.countSubqueryAlias;
|
|
6644
|
+
const suffix = alias ? ` AS ${alias}` : "";
|
|
6645
|
+
const rows = await adapterFetch(
|
|
6646
|
+
adapter,
|
|
6647
|
+
`SELECT COUNT(*) AS tina4_total FROM (${sql}
|
|
6648
|
+
)${suffix}`,
|
|
6649
|
+
params,
|
|
6650
|
+
void 0,
|
|
6651
|
+
void 0,
|
|
6652
|
+
true
|
|
6653
|
+
);
|
|
6654
|
+
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
6655
|
+
if (!row) return void 0;
|
|
6656
|
+
const value = row["tina4_total"] ?? row["TINA4_TOTAL"] ?? Object.values(row)[0];
|
|
6657
|
+
const n = Number(value);
|
|
6658
|
+
return Number.isFinite(n) ? n : void 0;
|
|
6659
|
+
} catch {
|
|
6660
|
+
return void 0;
|
|
6661
|
+
}
|
|
6662
|
+
}
|
|
6720
6663
|
function extractLastInsertId(result) {
|
|
6721
6664
|
if (result && typeof result === "object") {
|
|
6722
6665
|
const r = result;
|
|
@@ -7175,65 +7118,13 @@ var init_database = __esm({
|
|
|
7175
7118
|
try {
|
|
7176
7119
|
const rows = await adapterFetch(adapter, sql, params, limit, offset, opts?.noCache);
|
|
7177
7120
|
this.lastError = null;
|
|
7178
|
-
const total = await
|
|
7121
|
+
const total = await probeTotal(adapter, sql, params, limit);
|
|
7179
7122
|
return new DatabaseResult(rows, void 0, total, limit, offset, adapter, sql);
|
|
7180
7123
|
} catch (e) {
|
|
7181
7124
|
this.lastError = e?.message ?? String(e);
|
|
7182
7125
|
throw e;
|
|
7183
7126
|
}
|
|
7184
7127
|
}
|
|
7185
|
-
/**
|
|
7186
|
-
* The true row count for `sql`, ignoring the pagination we appended.
|
|
7187
|
-
*
|
|
7188
|
-
* `count` is the TRUE TOTAL for the filter, not the number of rows this page
|
|
7189
|
-
* returned. Node and Ruby used to populate it with `records.length` while
|
|
7190
|
-
* Python and PHP populated it from a probe, so `db.fetch(sql).count` answered
|
|
7191
|
-
* 20 here and 250 there for one query against one table, and every paginated
|
|
7192
|
-
* response built on it under-reported. MEASURED 2026-08-05 on a 250-row table
|
|
7193
|
-
* read with limit=20: Node reported total 20 over 2 pages against Python's
|
|
7194
|
-
* 250 over 13.
|
|
7195
|
-
*
|
|
7196
|
-
* Only probed when a limit was actually applied. With no limit the rows
|
|
7197
|
-
* returned ARE the whole answer for this SQL, so `records.length` is already
|
|
7198
|
-
* the true total and a second round-trip would buy nothing — which is also
|
|
7199
|
-
* what keeps `fetchAll()` at one query.
|
|
7200
|
-
*
|
|
7201
|
-
* BEST EFFORT, and it can never mask a real failure: it runs AFTER the main
|
|
7202
|
-
* query (which has already thrown on bad SQL) and returns undefined on any
|
|
7203
|
-
* error. `undefined` — not 0 — is the miss value, so DatabaseResult falls
|
|
7204
|
-
* back to records.length, a true lower bound. Reporting 0 next to 100 real
|
|
7205
|
-
* records would be the same "states a wrong number authoritatively" defect
|
|
7206
|
-
* this change exists to remove.
|
|
7207
|
-
*
|
|
7208
|
-
* The closing paren goes on its OWN LINE: appended inline, a trailing
|
|
7209
|
-
* `-- comment` in the caller's SQL comments it out and the probe dies with
|
|
7210
|
-
* "incomplete input". Postgres, MySQL and MSSQL additionally require a name
|
|
7211
|
-
* for the derived table; SQLite and Firebird do not, and Firebird rejects
|
|
7212
|
-
* `AS` there — so the alias comes from the adapter, not an assumption.
|
|
7213
|
-
*/
|
|
7214
|
-
async countProbe(adapter, sql, params, limit) {
|
|
7215
|
-
if (limit === void 0 || limit <= 0) return void 0;
|
|
7216
|
-
try {
|
|
7217
|
-
const alias = adapter.countSubqueryAlias;
|
|
7218
|
-
const suffix = alias ? ` AS ${alias}` : "";
|
|
7219
|
-
const rows = await adapterFetch(
|
|
7220
|
-
adapter,
|
|
7221
|
-
`SELECT COUNT(*) AS tina4_total FROM (${sql}
|
|
7222
|
-
)${suffix}`,
|
|
7223
|
-
params,
|
|
7224
|
-
void 0,
|
|
7225
|
-
void 0,
|
|
7226
|
-
true
|
|
7227
|
-
);
|
|
7228
|
-
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
7229
|
-
if (!row) return void 0;
|
|
7230
|
-
const value = row["tina4_total"] ?? row["TINA4_TOTAL"] ?? Object.values(row)[0];
|
|
7231
|
-
const n = Number(value);
|
|
7232
|
-
return Number.isFinite(n) ? n : void 0;
|
|
7233
|
-
} catch {
|
|
7234
|
-
return void 0;
|
|
7235
|
-
}
|
|
7236
|
-
}
|
|
7237
7128
|
/**
|
|
7238
7129
|
* Fetch a single row or null.
|
|
7239
7130
|
*
|
|
@@ -7945,6 +7836,10 @@ async function discoverModels(modelsDir) {
|
|
|
7945
7836
|
}
|
|
7946
7837
|
const definition = {
|
|
7947
7838
|
tableName: ModelClass.tableName,
|
|
7839
|
+
// The class name is the type name a generated OpenAPI client wants
|
|
7840
|
+
// (`Item`, not `items`). Carry it so Swagger keys components.schemas by
|
|
7841
|
+
// it. A model exported as `default` keeps its declared class name here.
|
|
7842
|
+
className: typeof ModelClass.name === "string" && ModelClass.name ? ModelClass.name : void 0,
|
|
7948
7843
|
fields: ModelClass.fields,
|
|
7949
7844
|
fieldMapping: ModelClass.fieldMapping,
|
|
7950
7845
|
softDelete: ModelClass.softDelete ?? false,
|
|
@@ -8559,7 +8454,13 @@ async function status(adapter, options) {
|
|
|
8559
8454
|
return result;
|
|
8560
8455
|
}
|
|
8561
8456
|
async function createMigration(description, options) {
|
|
8562
|
-
|
|
8457
|
+
const kind = (options?.kind ?? "sql").trim().toLowerCase();
|
|
8458
|
+
if (!["sql", "code", "class"].includes(kind)) {
|
|
8459
|
+
throw new Error(
|
|
8460
|
+
`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.`
|
|
8461
|
+
);
|
|
8462
|
+
}
|
|
8463
|
+
if (kind === "code" || kind === "class") {
|
|
8563
8464
|
return createClassMigration(description, options);
|
|
8564
8465
|
}
|
|
8565
8466
|
const dir = resolve3(options?.migrationsDir ?? "migrations");
|
|
@@ -8686,15 +8587,13 @@ var init_migration = __esm({
|
|
|
8686
8587
|
* Scaffold a new migration file.
|
|
8687
8588
|
*
|
|
8688
8589
|
* kind="sql" — creates {timestamp}_{description}.sql + .down.sql (default)
|
|
8689
|
-
* kind="
|
|
8590
|
+
* kind="code" — creates {timestamp}_{description}.ts with a TypeScript class
|
|
8591
|
+
* template. "class" is accepted as a legacy alias.
|
|
8690
8592
|
*
|
|
8691
8593
|
* Returns the path to the created up file (or class file).
|
|
8692
8594
|
*/
|
|
8693
8595
|
async create(description, kind = "sql") {
|
|
8694
|
-
|
|
8695
|
-
return createClassMigration(description, { migrationsDir: this.dir });
|
|
8696
|
-
}
|
|
8697
|
-
return createMigration(description, { migrationsDir: this.dir });
|
|
8596
|
+
return createMigration(description, { migrationsDir: this.dir, kind });
|
|
8698
8597
|
}
|
|
8699
8598
|
/** Return list of completed (applied) migration filenames. */
|
|
8700
8599
|
async getApplied() {
|
|
@@ -8937,15 +8836,8 @@ function generateCrudRoutes(models, options = {}) {
|
|
|
8937
8836
|
const total = Number(countRow[0]?.total ?? 0);
|
|
8938
8837
|
const limit = qp.limit ?? 100;
|
|
8939
8838
|
const page = qp.page ?? 1;
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
meta: {
|
|
8943
|
-
total,
|
|
8944
|
-
page,
|
|
8945
|
-
limit,
|
|
8946
|
-
totalPages: Math.ceil(total / limit)
|
|
8947
|
-
}
|
|
8948
|
-
});
|
|
8839
|
+
const offset = (page - 1) * limit;
|
|
8840
|
+
res.json(new DatabaseResult(rows, void 0, total, limit, offset).toPaginate());
|
|
8949
8841
|
}
|
|
8950
8842
|
});
|
|
8951
8843
|
routes.push({
|
|
@@ -9099,6 +8991,7 @@ var init_autoCrud = __esm({
|
|
|
9099
8991
|
"../orm/src/autoCrud.ts"() {
|
|
9100
8992
|
"use strict";
|
|
9101
8993
|
init_database();
|
|
8994
|
+
init_databaseResult();
|
|
9102
8995
|
init_query();
|
|
9103
8996
|
init_validation();
|
|
9104
8997
|
AutoCrud = class _AutoCrud {
|
|
@@ -9353,17 +9246,19 @@ var init_queryBuilder = __esm({
|
|
|
9353
9246
|
this.ensureDb();
|
|
9354
9247
|
const sql = this.toSql();
|
|
9355
9248
|
const allParams = [...this.params, ...this.havingParams];
|
|
9249
|
+
const queryParams = allParams.length > 0 ? allParams : void 0;
|
|
9356
9250
|
const rows = await adapterFetch(
|
|
9357
9251
|
this.db,
|
|
9358
9252
|
sql,
|
|
9359
|
-
|
|
9253
|
+
queryParams,
|
|
9360
9254
|
this.limitVal,
|
|
9361
9255
|
this.offsetVal
|
|
9362
9256
|
);
|
|
9257
|
+
const total = await probeTotal(this.db, sql, queryParams, this.limitVal);
|
|
9363
9258
|
return new DatabaseResult(
|
|
9364
9259
|
rows,
|
|
9365
9260
|
void 0,
|
|
9366
|
-
|
|
9261
|
+
total,
|
|
9367
9262
|
this.limitVal,
|
|
9368
9263
|
this.offsetVal,
|
|
9369
9264
|
this.db,
|
|
@@ -12940,7 +12835,6 @@ __export(src_exports, {
|
|
|
12940
12835
|
DatabaseUrl: () => DatabaseUrl,
|
|
12941
12836
|
DocStoreDriverMissing: () => DocStoreDriverMissing,
|
|
12942
12837
|
FakeData: () => FakeData2,
|
|
12943
|
-
FetchResult: () => FetchResult,
|
|
12944
12838
|
FirebirdAdapter: () => FirebirdAdapter,
|
|
12945
12839
|
InvalidId: () => InvalidId,
|
|
12946
12840
|
LocalStorage: () => LocalStorage,
|
|
@@ -13040,7 +12934,6 @@ __export(src_exports, {
|
|
|
13040
12934
|
var init_src = __esm({
|
|
13041
12935
|
"../orm/src/index.ts"() {
|
|
13042
12936
|
"use strict";
|
|
13043
|
-
init_types();
|
|
13044
12937
|
init_databaseResult();
|
|
13045
12938
|
init_database();
|
|
13046
12939
|
init_database();
|
|
@@ -13450,7 +13343,7 @@ ${s}\r
|
|
|
13450
13343
|
connect() {
|
|
13451
13344
|
if (this.connected) return Promise.resolve();
|
|
13452
13345
|
if (this.connecting) return this.connecting;
|
|
13453
|
-
this.connecting = new Promise((
|
|
13346
|
+
this.connecting = new Promise((resolve20, reject) => {
|
|
13454
13347
|
const sock = net.createConnection({ host: this.host, port: this.port });
|
|
13455
13348
|
sock.setNoDelay(true);
|
|
13456
13349
|
const onError = (err) => {
|
|
@@ -13487,7 +13380,7 @@ ${s}\r
|
|
|
13487
13380
|
sock.on("error", (e) => {
|
|
13488
13381
|
this.brokenError = e;
|
|
13489
13382
|
});
|
|
13490
|
-
|
|
13383
|
+
resolve20();
|
|
13491
13384
|
} catch (e) {
|
|
13492
13385
|
onError(e);
|
|
13493
13386
|
}
|
|
@@ -13550,12 +13443,12 @@ ${s}\r
|
|
|
13550
13443
|
}
|
|
13551
13444
|
/** Send one command and await its reply (assumes socket is up). */
|
|
13552
13445
|
raw(args) {
|
|
13553
|
-
return new Promise((
|
|
13446
|
+
return new Promise((resolve20, reject) => {
|
|
13554
13447
|
if (!this.sock || this.sock.destroyed) {
|
|
13555
13448
|
reject(this.brokenError ?? new Error("redis socket not connected"));
|
|
13556
13449
|
return;
|
|
13557
13450
|
}
|
|
13558
|
-
this.waiters.push({ resolve:
|
|
13451
|
+
this.waiters.push({ resolve: resolve20, reject });
|
|
13559
13452
|
this.sock.write(_RespClient.encode(args));
|
|
13560
13453
|
});
|
|
13561
13454
|
}
|
|
@@ -13897,7 +13790,7 @@ ${s}\r
|
|
|
13897
13790
|
connect() {
|
|
13898
13791
|
if (this.connected) return Promise.resolve();
|
|
13899
13792
|
if (this.connecting) return this.connecting;
|
|
13900
|
-
this.connecting = new Promise((
|
|
13793
|
+
this.connecting = new Promise((resolve20, reject) => {
|
|
13901
13794
|
const sock = net.createConnection({ host: this.host, port: this.port });
|
|
13902
13795
|
sock.setNoDelay(true);
|
|
13903
13796
|
sock.once("error", (err) => {
|
|
@@ -13918,7 +13811,7 @@ ${s}\r
|
|
|
13918
13811
|
p.resolve(this.buffer.toString("utf-8"));
|
|
13919
13812
|
}
|
|
13920
13813
|
});
|
|
13921
|
-
|
|
13814
|
+
resolve20();
|
|
13922
13815
|
});
|
|
13923
13816
|
});
|
|
13924
13817
|
return this.connecting;
|
|
@@ -13951,13 +13844,13 @@ ${s}\r
|
|
|
13951
13844
|
async send(payload, terminator) {
|
|
13952
13845
|
await this.connect();
|
|
13953
13846
|
if (!this.sock || this.sock.destroyed) return "";
|
|
13954
|
-
return new Promise((
|
|
13847
|
+
return new Promise((resolve20) => {
|
|
13955
13848
|
this.buffer = Buffer.alloc(0);
|
|
13956
|
-
this.pending = { terminator, resolve:
|
|
13849
|
+
this.pending = { terminator, resolve: resolve20 };
|
|
13957
13850
|
const timer = setTimeout(() => {
|
|
13958
|
-
if (this.pending && this.pending.resolve ===
|
|
13851
|
+
if (this.pending && this.pending.resolve === resolve20) {
|
|
13959
13852
|
this.pending = null;
|
|
13960
|
-
|
|
13853
|
+
resolve20(this.buffer.toString("utf-8"));
|
|
13961
13854
|
}
|
|
13962
13855
|
}, 4e3);
|
|
13963
13856
|
if (timer.unref) timer.unref();
|
|
@@ -15483,16 +15376,27 @@ async function parseBody(req2) {
|
|
|
15483
15376
|
}
|
|
15484
15377
|
const contentType = req2.headers["content-type"] ?? "";
|
|
15485
15378
|
const chunks = [];
|
|
15486
|
-
await new Promise((
|
|
15487
|
-
|
|
15488
|
-
|
|
15379
|
+
await new Promise((resolve20, reject) => {
|
|
15380
|
+
let received = 0;
|
|
15381
|
+
let refused = false;
|
|
15382
|
+
req2.on("data", (chunk) => {
|
|
15383
|
+
if (refused) return;
|
|
15384
|
+
received += chunk.length;
|
|
15385
|
+
if (received > TINA4_MAX_UPLOAD_SIZE) {
|
|
15386
|
+
refused = true;
|
|
15387
|
+
chunks.length = 0;
|
|
15388
|
+
reject(new PayloadTooLargeError(received, TINA4_MAX_UPLOAD_SIZE));
|
|
15389
|
+
return;
|
|
15390
|
+
}
|
|
15391
|
+
chunks.push(chunk);
|
|
15392
|
+
});
|
|
15393
|
+
req2.on("end", () => {
|
|
15394
|
+
if (!refused) resolve20();
|
|
15395
|
+
});
|
|
15489
15396
|
req2.on("error", reject);
|
|
15490
15397
|
});
|
|
15491
15398
|
const raw = Buffer.concat(chunks);
|
|
15492
15399
|
if (raw.length === 0) return;
|
|
15493
|
-
if (raw.length > TINA4_MAX_UPLOAD_SIZE) {
|
|
15494
|
-
throw new PayloadTooLargeError(raw.length, TINA4_MAX_UPLOAD_SIZE);
|
|
15495
|
-
}
|
|
15496
15400
|
if (contentType.includes("multipart/form-data")) {
|
|
15497
15401
|
const boundary = extractBoundary(contentType);
|
|
15498
15402
|
if (boundary) {
|
|
@@ -17441,10 +17345,17 @@ var init_session = __esm({
|
|
|
17441
17345
|
*
|
|
17442
17346
|
* session.flash("message", "Saved!") // set
|
|
17443
17347
|
* session.flash("message") // get + auto-remove → "Saved!"
|
|
17348
|
+
* session.flash("message", null) // get + auto-remove (null is a GET sentinel)
|
|
17349
|
+
*
|
|
17350
|
+
* `null` — NOT just `undefined` — is the GET sentinel, so `flash(key, null)`
|
|
17351
|
+
* READS and clears rather than STORING null. This matches the Python master
|
|
17352
|
+
* (`if value is not None`), PHP (`if ($value !== null)`) and Ruby
|
|
17353
|
+
* (`if value.nil?`): passing the language's "no value" literal means GET. A
|
|
17354
|
+
* caller wanting to persist an explicit null should store it with `set()`.
|
|
17444
17355
|
*/
|
|
17445
17356
|
flash(key, value) {
|
|
17446
17357
|
const flashKey = `${FLASH_PREFIX}${key}`;
|
|
17447
|
-
if (value !== void 0) {
|
|
17358
|
+
if (value !== void 0 && value !== null) {
|
|
17448
17359
|
this.set(flashKey, value);
|
|
17449
17360
|
return void 0;
|
|
17450
17361
|
}
|
|
@@ -23315,14 +23226,14 @@ data: ${channel.buffer.shift()}
|
|
|
23315
23226
|
`;
|
|
23316
23227
|
continue;
|
|
23317
23228
|
}
|
|
23318
|
-
const gotMessage = await new Promise((
|
|
23229
|
+
const gotMessage = await new Promise((resolve20) => {
|
|
23319
23230
|
const timer = setTimeout(() => {
|
|
23320
23231
|
channel.wake = null;
|
|
23321
|
-
|
|
23232
|
+
resolve20(false);
|
|
23322
23233
|
}, keepaliveMs);
|
|
23323
23234
|
channel.wake = () => {
|
|
23324
23235
|
clearTimeout(timer);
|
|
23325
|
-
|
|
23236
|
+
resolve20(true);
|
|
23326
23237
|
};
|
|
23327
23238
|
});
|
|
23328
23239
|
if (!gotMessage) yield `: keep-alive
|
|
@@ -25837,7 +25748,7 @@ var init_websocket = __esm({
|
|
|
25837
25748
|
* Start the WebSocket server.
|
|
25838
25749
|
*/
|
|
25839
25750
|
async start() {
|
|
25840
|
-
return new Promise((
|
|
25751
|
+
return new Promise((resolve20, reject) => {
|
|
25841
25752
|
this.server = createServer((req2, res) => {
|
|
25842
25753
|
res.writeHead(426, { "Content-Type": "text/plain" });
|
|
25843
25754
|
res.end("Upgrade Required");
|
|
@@ -25847,7 +25758,7 @@ var init_websocket = __esm({
|
|
|
25847
25758
|
});
|
|
25848
25759
|
this.server.listen(this.port, () => {
|
|
25849
25760
|
this.startIdleReaper();
|
|
25850
|
-
|
|
25761
|
+
resolve20();
|
|
25851
25762
|
});
|
|
25852
25763
|
this.server.on("error", (err) => {
|
|
25853
25764
|
this.emit("error", err);
|
|
@@ -26411,7 +26322,7 @@ var init_websocket = __esm({
|
|
|
26411
26322
|
client.trackerId = this.onAdd(socket.remoteAddress ?? "unknown", "/__dev_reload");
|
|
26412
26323
|
}
|
|
26413
26324
|
this.clients.add(client);
|
|
26414
|
-
const
|
|
26325
|
+
const cleanup = () => {
|
|
26415
26326
|
if (!this.clients.has(client)) return;
|
|
26416
26327
|
this.clients.delete(client);
|
|
26417
26328
|
if (client.trackerId && this.onRemove) this.onRemove(client.trackerId);
|
|
@@ -26434,13 +26345,13 @@ var init_websocket = __esm({
|
|
|
26434
26345
|
socket.end();
|
|
26435
26346
|
} catch {
|
|
26436
26347
|
}
|
|
26437
|
-
|
|
26348
|
+
cleanup();
|
|
26438
26349
|
return;
|
|
26439
26350
|
}
|
|
26440
26351
|
}
|
|
26441
26352
|
});
|
|
26442
|
-
socket.on("close",
|
|
26443
|
-
socket.on("error",
|
|
26353
|
+
socket.on("close", cleanup);
|
|
26354
|
+
socket.on("error", cleanup);
|
|
26444
26355
|
return true;
|
|
26445
26356
|
}
|
|
26446
26357
|
/**
|
|
@@ -27958,7 +27869,7 @@ var init_queue = __esm({
|
|
|
27958
27869
|
const jobs = this.popBatch(resolvedBatchSize);
|
|
27959
27870
|
if (jobs.length === 0) {
|
|
27960
27871
|
if (resolvedPollInterval <= 0) break;
|
|
27961
|
-
await new Promise((
|
|
27872
|
+
await new Promise((resolve20) => setTimeout(resolve20, resolvedPollInterval));
|
|
27962
27873
|
continue;
|
|
27963
27874
|
}
|
|
27964
27875
|
yield jobs;
|
|
@@ -27968,7 +27879,7 @@ var init_queue = __esm({
|
|
|
27968
27879
|
const raw = this.pop();
|
|
27969
27880
|
if (raw === null) {
|
|
27970
27881
|
if (resolvedPollInterval <= 0) break;
|
|
27971
|
-
await new Promise((
|
|
27882
|
+
await new Promise((resolve20) => setTimeout(resolve20, resolvedPollInterval));
|
|
27972
27883
|
continue;
|
|
27973
27884
|
}
|
|
27974
27885
|
yield createJob(raw, this);
|
|
@@ -32488,23 +32399,23 @@ var init_devAdmin = __esm({
|
|
|
32488
32399
|
});
|
|
32489
32400
|
};
|
|
32490
32401
|
handleDevAdminJs = async (_req, res) => {
|
|
32491
|
-
const { readFileSync:
|
|
32492
|
-
const { dirname:
|
|
32402
|
+
const { readFileSync: readFileSync26, existsSync: existsSync26 } = await import("node:fs");
|
|
32403
|
+
const { dirname: dirname13, join: join29, resolve: resolve20 } = await import("node:path");
|
|
32493
32404
|
const { fileURLToPath: fileURLToPath7 } = await import("node:url");
|
|
32494
|
-
const dir =
|
|
32405
|
+
const dir = dirname13(fileURLToPath7(import.meta.url));
|
|
32495
32406
|
const candidates = [
|
|
32496
|
-
|
|
32407
|
+
join29(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
32497
32408
|
// src/../public/js/
|
|
32498
|
-
|
|
32409
|
+
join29(dir, "..", "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
32499
32410
|
// deeper nesting
|
|
32500
|
-
|
|
32501
|
-
|
|
32411
|
+
resolve20(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin.min.js"),
|
|
32412
|
+
resolve20(process.cwd(), "public", "js", "tina4-dev-admin.min.js")
|
|
32502
32413
|
// project public/
|
|
32503
32414
|
];
|
|
32504
32415
|
for (const jsPath of candidates) {
|
|
32505
|
-
if (
|
|
32416
|
+
if (existsSync26(jsPath)) {
|
|
32506
32417
|
try {
|
|
32507
|
-
const content =
|
|
32418
|
+
const content = readFileSync26(jsPath, "utf-8");
|
|
32508
32419
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
32509
32420
|
res.raw.end(content);
|
|
32510
32421
|
return;
|
|
@@ -32965,8 +32876,12 @@ function sanitizeSecurity(reqs, schemes) {
|
|
|
32965
32876
|
function generate(routes, models = []) {
|
|
32966
32877
|
const info = {
|
|
32967
32878
|
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
32968
|
-
version
|
|
32969
|
-
|
|
32879
|
+
// The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
|
|
32880
|
+
// 0.0.1). description defaults to the empty string, not a canned sentence.
|
|
32881
|
+
// Both are the settled cross-framework defaults (parity with the Python
|
|
32882
|
+
// master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
|
|
32883
|
+
version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
|
|
32884
|
+
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? ""
|
|
32970
32885
|
};
|
|
32971
32886
|
const contactEmail = (process.env.TINA4_SWAGGER_CONTACT_EMAIL ?? "").trim();
|
|
32972
32887
|
const contactName = (process.env.TINA4_SWAGGER_CONTACT_TEAM ?? "").trim();
|
|
@@ -32999,9 +32914,11 @@ function generate(routes, models = []) {
|
|
|
32999
32914
|
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
33000
32915
|
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
33001
32916
|
const refSchemas = /* @__PURE__ */ new Set();
|
|
32917
|
+
const tableToSchema = /* @__PURE__ */ new Map();
|
|
33002
32918
|
for (const model of models) {
|
|
33003
|
-
const
|
|
33004
|
-
|
|
32919
|
+
const schemaKey = schemaNameForModel(model);
|
|
32920
|
+
tableToSchema.set(model.tableName, schemaKey);
|
|
32921
|
+
spec.components.schemas[schemaKey] = modelToSchema(model);
|
|
33005
32922
|
}
|
|
33006
32923
|
const usedTags = [];
|
|
33007
32924
|
const seenIds = /* @__PURE__ */ new Set();
|
|
@@ -33028,11 +32945,11 @@ function generate(routes, models = []) {
|
|
|
33028
32945
|
if (route.meta?.deprecated) operation.deprecated = true;
|
|
33029
32946
|
const pathParams = extractPathParams(route.pattern);
|
|
33030
32947
|
if (pathParams.length > 0) {
|
|
33031
|
-
operation.parameters = pathParams.map((name) => ({
|
|
32948
|
+
operation.parameters = pathParams.map(({ name, schema }) => ({
|
|
33032
32949
|
name,
|
|
33033
32950
|
in: "path",
|
|
33034
32951
|
required: true,
|
|
33035
|
-
schema
|
|
32952
|
+
schema
|
|
33036
32953
|
}));
|
|
33037
32954
|
}
|
|
33038
32955
|
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
@@ -33058,19 +32975,20 @@ function generate(routes, models = []) {
|
|
|
33058
32975
|
};
|
|
33059
32976
|
} else if (method === "post" || method === "put") {
|
|
33060
32977
|
const modelName = inferModelFromPath(route.pattern);
|
|
33061
|
-
|
|
33062
|
-
|
|
33063
|
-
|
|
33064
|
-
};
|
|
32978
|
+
const schemaKey = modelName ? tableToSchema.get(modelName) : void 0;
|
|
32979
|
+
if (schemaKey) {
|
|
32980
|
+
const sref = `#/components/schemas/${schemaKey}`;
|
|
32981
|
+
const media = { schema: { $ref: sref } };
|
|
33065
32982
|
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
33066
32983
|
operation.requestBody = {
|
|
33067
32984
|
required: true,
|
|
33068
32985
|
content: { "application/json": media }
|
|
33069
32986
|
};
|
|
33070
|
-
|
|
33071
|
-
|
|
33072
|
-
|
|
33073
|
-
|
|
32987
|
+
if (route.meta?.responses === void 0) {
|
|
32988
|
+
operation.responses = {
|
|
32989
|
+
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
32990
|
+
};
|
|
32991
|
+
}
|
|
33074
32992
|
} else if (route.meta?.example !== void 0) {
|
|
33075
32993
|
operation.requestBody = {
|
|
33076
32994
|
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
@@ -33157,6 +33075,21 @@ function resolveServers() {
|
|
|
33157
33075
|
const dev = (process.env.SWAGGER_DEV_URL ?? "").trim();
|
|
33158
33076
|
return dev.length > 0 ? [{ url: dev }] : [{ url: "/" }];
|
|
33159
33077
|
}
|
|
33078
|
+
function schemaNameForModel(model) {
|
|
33079
|
+
const explicit = model.className?.trim();
|
|
33080
|
+
if (explicit) return explicit;
|
|
33081
|
+
return deriveClassName(model.tableName);
|
|
33082
|
+
}
|
|
33083
|
+
function deriveClassName(tableName) {
|
|
33084
|
+
return singularize(tableName).split(/[_\s-]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("") || tableName;
|
|
33085
|
+
}
|
|
33086
|
+
function singularize(word) {
|
|
33087
|
+
if (/ies$/i.test(word) && word.length > 3) return word.slice(0, -3) + "y";
|
|
33088
|
+
if (/(ses|xes|zes|ches|shes)$/i.test(word)) return word.slice(0, -2);
|
|
33089
|
+
if (/ss$/i.test(word)) return word;
|
|
33090
|
+
if (/s$/i.test(word) && word.length > 1) return word.slice(0, -1);
|
|
33091
|
+
return word;
|
|
33092
|
+
}
|
|
33160
33093
|
function modelToSchema(model) {
|
|
33161
33094
|
const properties = {};
|
|
33162
33095
|
const required = [];
|
|
@@ -33230,15 +33163,35 @@ function inferSchema(value) {
|
|
|
33230
33163
|
if (typeof value === "number") return { type: Number.isInteger(value) ? "integer" : "number" };
|
|
33231
33164
|
return { type: "string" };
|
|
33232
33165
|
}
|
|
33166
|
+
function segmentParam(segment) {
|
|
33167
|
+
if (segment.startsWith("{") && segment.endsWith("}")) {
|
|
33168
|
+
const inner = segment.slice(1, -1);
|
|
33169
|
+
if (inner.startsWith("...")) return { name: inner.slice(3), type: "string" };
|
|
33170
|
+
const colon = inner.indexOf(":");
|
|
33171
|
+
if (colon >= 0) return { name: inner.slice(0, colon), type: inner.slice(colon + 1) };
|
|
33172
|
+
return { name: inner, type: "string" };
|
|
33173
|
+
}
|
|
33174
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
33175
|
+
const inner = segment.slice(1, -1);
|
|
33176
|
+
return { name: inner.startsWith("...") ? inner.slice(3) : inner, type: "string" };
|
|
33177
|
+
}
|
|
33178
|
+
if (segment.startsWith(":") && segment.length > 1) {
|
|
33179
|
+
return { name: segment.slice(1), type: "string" };
|
|
33180
|
+
}
|
|
33181
|
+
return null;
|
|
33182
|
+
}
|
|
33233
33183
|
function patternToOpenAPI(pattern) {
|
|
33234
|
-
return pattern.
|
|
33184
|
+
return pattern.split("/").map((segment) => {
|
|
33185
|
+
const p = segmentParam(segment);
|
|
33186
|
+
return p ? `{${p.name}}` : segment;
|
|
33187
|
+
}).join("/");
|
|
33235
33188
|
}
|
|
33236
33189
|
function extractPathParams(pattern) {
|
|
33237
33190
|
const params = [];
|
|
33238
|
-
const
|
|
33239
|
-
|
|
33240
|
-
|
|
33241
|
-
params.push(
|
|
33191
|
+
for (const segment of pattern.split("/")) {
|
|
33192
|
+
const p = segmentParam(segment);
|
|
33193
|
+
if (!p) continue;
|
|
33194
|
+
params.push({ name: p.name, schema: { ...PARAM_TYPE_SCHEMA[p.type] ?? { type: "string" } } });
|
|
33242
33195
|
}
|
|
33243
33196
|
return params;
|
|
33244
33197
|
}
|
|
@@ -33260,8 +33213,12 @@ function inferModelFromPath(pattern) {
|
|
|
33260
33213
|
if (rest.length === 1 && /^[[{]\.{0,3}\w+[\]}]$/.test(rest[0])) return candidate;
|
|
33261
33214
|
return null;
|
|
33262
33215
|
}
|
|
33216
|
+
function operationIdBase(method, openApiPath) {
|
|
33217
|
+
const clean = openApiPath.replace(/^\/+|\/+$/g, "").replace(/\//g, "_").replace(/\.\.\./g, "").replace(/[{}]/g, "").replace(/\*/g, "wildcard");
|
|
33218
|
+
return clean ? `${method}_${clean}` : method;
|
|
33219
|
+
}
|
|
33263
33220
|
function uniqueOperationId(method, openApiPath, seen) {
|
|
33264
|
-
const base = (method
|
|
33221
|
+
const base = operationIdBase(method, openApiPath);
|
|
33265
33222
|
let oid = base;
|
|
33266
33223
|
let n = 2;
|
|
33267
33224
|
while (seen.has(oid)) {
|
|
@@ -33271,13 +33228,25 @@ function uniqueOperationId(method, openApiPath, seen) {
|
|
|
33271
33228
|
seen.add(oid);
|
|
33272
33229
|
return oid;
|
|
33273
33230
|
}
|
|
33274
|
-
var WRITE_METHODS, registeredSchemes, registeredSchemas;
|
|
33231
|
+
var WRITE_METHODS, registeredSchemes, registeredSchemas, PARAM_TYPE_SCHEMA;
|
|
33275
33232
|
var init_generator = __esm({
|
|
33276
33233
|
"../swagger/src/generator.ts"() {
|
|
33277
33234
|
"use strict";
|
|
33278
33235
|
WRITE_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
|
|
33279
33236
|
registeredSchemes = {};
|
|
33280
33237
|
registeredSchemas = {};
|
|
33238
|
+
PARAM_TYPE_SCHEMA = {
|
|
33239
|
+
int: { type: "integer" },
|
|
33240
|
+
integer: { type: "integer" },
|
|
33241
|
+
float: { type: "number" },
|
|
33242
|
+
number: { type: "number" },
|
|
33243
|
+
uuid: { type: "string", format: "uuid" },
|
|
33244
|
+
slug: { type: "string", pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
|
|
33245
|
+
alpha: { type: "string", pattern: "^[A-Za-z]+$" },
|
|
33246
|
+
alnum: { type: "string", pattern: "^[A-Za-z0-9]+$" },
|
|
33247
|
+
path: { type: "string" },
|
|
33248
|
+
string: { type: "string" }
|
|
33249
|
+
};
|
|
33281
33250
|
}
|
|
33282
33251
|
});
|
|
33283
33252
|
|
|
@@ -33597,10 +33566,29 @@ function openBrowser(url) {
|
|
|
33597
33566
|
}, 2e3);
|
|
33598
33567
|
}
|
|
33599
33568
|
function resolvePortAndHost(config) {
|
|
33600
|
-
const
|
|
33569
|
+
const tina4Port = process.env.TINA4_PORT;
|
|
33570
|
+
const legacyPort = process.env.PORT;
|
|
33571
|
+
let port;
|
|
33572
|
+
if (config?.port !== void 0) {
|
|
33573
|
+
port = config.port;
|
|
33574
|
+
} else if (tina4Port && /^\d+$/.test(tina4Port)) {
|
|
33575
|
+
port = parseInt(tina4Port, 10);
|
|
33576
|
+
} else if (legacyPort && /^\d+$/.test(legacyPort)) {
|
|
33577
|
+
port = parseInt(legacyPort, 10);
|
|
33578
|
+
warnDeprecatedPort(port);
|
|
33579
|
+
} else {
|
|
33580
|
+
port = 7148;
|
|
33581
|
+
}
|
|
33601
33582
|
const host = config?.host ?? process.env.TINA4_HOST ?? process.env.HOST ?? "0.0.0.0";
|
|
33602
33583
|
return { port, host };
|
|
33603
33584
|
}
|
|
33585
|
+
function warnDeprecatedPort(port) {
|
|
33586
|
+
if (portDeprecationWarned) return;
|
|
33587
|
+
portDeprecationWarned = true;
|
|
33588
|
+
Log.warning(
|
|
33589
|
+
`PORT is deprecated and will be removed in 3.14 - use TINA4_PORT instead (binding port ${port} from PORT)`
|
|
33590
|
+
);
|
|
33591
|
+
}
|
|
33604
33592
|
function isBannerSuppressed() {
|
|
33605
33593
|
return isTruthy(process.env.TINA4_SUPPRESS);
|
|
33606
33594
|
}
|
|
@@ -33872,6 +33860,29 @@ function deployGallery(name) {
|
|
|
33872
33860
|
</body>
|
|
33873
33861
|
</html>`;
|
|
33874
33862
|
}
|
|
33863
|
+
function startLoopWatchdog() {
|
|
33864
|
+
const raw = (process.env.TINA4_LOOP_LAG_WARN_MS ?? "").trim();
|
|
33865
|
+
const threshold = /^\d+$/.test(raw) ? parseInt(raw, 10) : 250;
|
|
33866
|
+
if (threshold <= 0) {
|
|
33867
|
+
return { stop: () => {
|
|
33868
|
+
} };
|
|
33869
|
+
}
|
|
33870
|
+
let last = Date.now();
|
|
33871
|
+
let warned = 0;
|
|
33872
|
+
const timer = setInterval(() => {
|
|
33873
|
+
const now = Date.now();
|
|
33874
|
+
const lag = now - last - LOOP_WATCHDOG_TICK_MS;
|
|
33875
|
+
last = now;
|
|
33876
|
+
if (lag < threshold) return;
|
|
33877
|
+
warned++;
|
|
33878
|
+
if (warned > 5 && warned % 20 !== 0) return;
|
|
33879
|
+
Log.warning(
|
|
33880
|
+
`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.`
|
|
33881
|
+
);
|
|
33882
|
+
}, LOOP_WATCHDOG_TICK_MS);
|
|
33883
|
+
timer.unref();
|
|
33884
|
+
return { stop: () => clearInterval(timer) };
|
|
33885
|
+
}
|
|
33875
33886
|
async function start(config) {
|
|
33876
33887
|
const isManaged = process.argv.includes("--managed");
|
|
33877
33888
|
if (!isManaged && process.env.TINA4_OVERRIDE_CLIENT !== "true") {
|
|
@@ -34121,7 +34132,9 @@ async function startServer(config) {
|
|
|
34121
34132
|
const resolved = resolvePortAndHost(config);
|
|
34122
34133
|
const host = resolved.host;
|
|
34123
34134
|
let port = resolved.port;
|
|
34124
|
-
|
|
34135
|
+
if (!cluster.isWorker) {
|
|
34136
|
+
port = findAvailablePort(port);
|
|
34137
|
+
}
|
|
34125
34138
|
const isProduction = (process.env.TINA4_PRODUCTION ?? "").toLowerCase() === "true";
|
|
34126
34139
|
if (cluster.isPrimary && isProduction) {
|
|
34127
34140
|
const numCPUs = os2.cpus().length;
|
|
@@ -34337,7 +34350,20 @@ ${reset2}
|
|
|
34337
34350
|
await sessionAutoStart(rawReq, rawRes, req2);
|
|
34338
34351
|
await middleware.run(req2, res);
|
|
34339
34352
|
if (res.raw.writableEnded) return;
|
|
34340
|
-
|
|
34353
|
+
try {
|
|
34354
|
+
await req2.parseBody();
|
|
34355
|
+
} catch (err) {
|
|
34356
|
+
const status2 = err?.statusCode;
|
|
34357
|
+
if (typeof status2 === "number" && status2 >= 400 && status2 < 500) {
|
|
34358
|
+
if (!rawRes.writableEnded) {
|
|
34359
|
+
rawRes.statusCode = status2;
|
|
34360
|
+
rawRes.setHeader("content-type", "application/json");
|
|
34361
|
+
rawRes.end(JSON.stringify({ error: err.message }));
|
|
34362
|
+
}
|
|
34363
|
+
return;
|
|
34364
|
+
}
|
|
34365
|
+
throw err;
|
|
34366
|
+
}
|
|
34341
34367
|
const pathname = req2.path;
|
|
34342
34368
|
const reqStartTime = DevAdmin.isEnabled() ? Date.now() : 0;
|
|
34343
34369
|
const matchedPattern = { value: "" };
|
|
@@ -34536,8 +34562,10 @@ ${reset2}
|
|
|
34536
34562
|
};
|
|
34537
34563
|
process.on("SIGTERM", onSigterm);
|
|
34538
34564
|
process.on("SIGINT", onSigint);
|
|
34565
|
+
const loopWatchdog = startLoopWatchdog();
|
|
34539
34566
|
resolvePromise({
|
|
34540
34567
|
close: () => {
|
|
34568
|
+
loopWatchdog.stop();
|
|
34541
34569
|
process.off("SIGTERM", onSigterm);
|
|
34542
34570
|
process.off("SIGINT", onSigint);
|
|
34543
34571
|
stopAllBackgroundTasks();
|
|
@@ -34552,7 +34580,7 @@ ${reset2}
|
|
|
34552
34580
|
});
|
|
34553
34581
|
});
|
|
34554
34582
|
}
|
|
34555
|
-
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;
|
|
34583
|
+
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;
|
|
34556
34584
|
var init_server = __esm({
|
|
34557
34585
|
"src/server.ts"() {
|
|
34558
34586
|
"use strict";
|
|
@@ -34605,6 +34633,7 @@ var init_server = __esm({
|
|
|
34605
34633
|
SWAGGER_VERSION: "TINA4_SWAGGER_VERSION",
|
|
34606
34634
|
ORM_PLURAL_TABLE_NAMES: "TINA4_ORM_PLURAL_TABLE_NAMES"
|
|
34607
34635
|
};
|
|
34636
|
+
portDeprecationWarned = false;
|
|
34608
34637
|
TEMPLATE_PAGES_DIR = "pages";
|
|
34609
34638
|
HTTP_REASON_PHRASES = {
|
|
34610
34639
|
100: "Continue",
|
|
@@ -34641,6 +34670,7 @@ var init_server = __esm({
|
|
|
34641
34670
|
templateCache = null;
|
|
34642
34671
|
_dispatchFn = null;
|
|
34643
34672
|
_serverHandle = null;
|
|
34673
|
+
LOOP_WATCHDOG_TICK_MS = 100;
|
|
34644
34674
|
FALLBACK_STAGES = [
|
|
34645
34675
|
serveTemplateFallback,
|
|
34646
34676
|
serveLandingPage,
|
|
@@ -34728,433 +34758,6 @@ var init_env = __esm({
|
|
|
34728
34758
|
}
|
|
34729
34759
|
});
|
|
34730
34760
|
|
|
34731
|
-
// src/scss.ts
|
|
34732
|
-
import { readFileSync as readFileSync23, writeFileSync as writeFileSync16, existsSync as existsSync24, mkdirSync as mkdirSync18, readdirSync as readdirSync18 } from "node:fs";
|
|
34733
|
-
import { join as join27, resolve as resolve19, dirname as dirname12 } from "node:path";
|
|
34734
|
-
function compileString(scss, importPaths, variables) {
|
|
34735
|
-
const imported = /* @__PURE__ */ new Set();
|
|
34736
|
-
scss = resolveImports(scss, importPaths, imported);
|
|
34737
|
-
scss = scss.replace(/(?<![:"'])\/\/[^\n]*/g, "");
|
|
34738
|
-
scss = extractVariables(scss, variables);
|
|
34739
|
-
const mixins = {};
|
|
34740
|
-
scss = extractMixins(scss, mixins);
|
|
34741
|
-
scss = resolveIncludes(scss, mixins);
|
|
34742
|
-
scss = resolveInterpolation(scss, variables);
|
|
34743
|
-
scss = substituteVariables(scss, variables);
|
|
34744
|
-
scss = evalMath(scss);
|
|
34745
|
-
scss = resolveColorFunctions(scss);
|
|
34746
|
-
const css = flattenNesting(scss);
|
|
34747
|
-
return cleanup(css);
|
|
34748
|
-
}
|
|
34749
|
-
function resolveImports(content, paths, imported) {
|
|
34750
|
-
return content.replace(/@import\s+["']?([^"';\n]+)["']?\s*;/g, (_match, name) => {
|
|
34751
|
-
name = name.trim();
|
|
34752
|
-
const candidates = [];
|
|
34753
|
-
for (const base of paths) {
|
|
34754
|
-
candidates.push(
|
|
34755
|
-
join27(base, `${name}.scss`),
|
|
34756
|
-
join27(base, `_${name}.scss`),
|
|
34757
|
-
join27(base, name)
|
|
34758
|
-
);
|
|
34759
|
-
}
|
|
34760
|
-
for (const candidate of candidates) {
|
|
34761
|
-
if (existsSync24(candidate) && !imported.has(candidate)) {
|
|
34762
|
-
imported.add(candidate);
|
|
34763
|
-
const fileContent = readFileSync23(candidate, "utf-8");
|
|
34764
|
-
return resolveImports(fileContent, [dirname12(candidate), ...paths], imported);
|
|
34765
|
-
}
|
|
34766
|
-
}
|
|
34767
|
-
return `/* IMPORT NOT FOUND: ${name} */`;
|
|
34768
|
-
});
|
|
34769
|
-
}
|
|
34770
|
-
function stripVariableFlags(value) {
|
|
34771
|
-
let declaresDefault = false;
|
|
34772
|
-
for (; ; ) {
|
|
34773
|
-
const match = VARIABLE_FLAG.exec(value);
|
|
34774
|
-
if (match === null) return [value.trim(), declaresDefault];
|
|
34775
|
-
if (match[1] === "default") declaresDefault = true;
|
|
34776
|
-
value = value.slice(0, match.index);
|
|
34777
|
-
}
|
|
34778
|
-
}
|
|
34779
|
-
function extractVariables(scss, variables) {
|
|
34780
|
-
return scss.replace(/\$([a-zA-Z_][\w-]*)\s*:\s*([^;]+);/g, (_m, name, value) => {
|
|
34781
|
-
const [stripped, declaresDefault] = stripVariableFlags(value.trim());
|
|
34782
|
-
if (declaresDefault && (variables[name] ?? "null") !== "null") {
|
|
34783
|
-
return "";
|
|
34784
|
-
}
|
|
34785
|
-
let resolved = stripped;
|
|
34786
|
-
for (const [vName, vVal] of Object.entries(variables)) {
|
|
34787
|
-
resolved = resolved.replaceAll(`$${vName}`, vVal);
|
|
34788
|
-
}
|
|
34789
|
-
variables[name] = resolved;
|
|
34790
|
-
return "";
|
|
34791
|
-
});
|
|
34792
|
-
}
|
|
34793
|
-
function substituteVariables(scss, variables) {
|
|
34794
|
-
const sorted = Object.keys(variables).sort((a, b) => b.length - a.length);
|
|
34795
|
-
for (const name of sorted) {
|
|
34796
|
-
scss = scss.replaceAll(`$${name}`, variables[name]);
|
|
34797
|
-
}
|
|
34798
|
-
return scss;
|
|
34799
|
-
}
|
|
34800
|
-
function resolveInterpolation(scss, variables) {
|
|
34801
|
-
const sorted = Object.keys(variables).sort((a, b) => b.length - a.length);
|
|
34802
|
-
return scss.replace(/#\{([^{}]*)\}/g, (_m, inner) => {
|
|
34803
|
-
let resolved = inner.trim();
|
|
34804
|
-
for (const name of sorted) {
|
|
34805
|
-
resolved = resolved.replaceAll(`$${name}`, variables[name]);
|
|
34806
|
-
}
|
|
34807
|
-
return resolved;
|
|
34808
|
-
});
|
|
34809
|
-
}
|
|
34810
|
-
function extractMixins(scss, mixins) {
|
|
34811
|
-
const pattern = /@mixin\s+([\w-]+)\s*(?:\(([^)]*)\))?\s*\{/g;
|
|
34812
|
-
let match;
|
|
34813
|
-
const locations = [];
|
|
34814
|
-
while ((match = pattern.exec(scss)) !== null) {
|
|
34815
|
-
const name = match[1];
|
|
34816
|
-
const paramsStr = match[2] ?? "";
|
|
34817
|
-
const params = paramsStr.split(",").map((p) => p.trim().replace(/^\$/, "")).filter(Boolean);
|
|
34818
|
-
const bodyStart = match.index + match[0].length;
|
|
34819
|
-
const body = findBlock(scss, bodyStart);
|
|
34820
|
-
if (body !== null) {
|
|
34821
|
-
mixins[name] = { params, body };
|
|
34822
|
-
locations.push({
|
|
34823
|
-
start: match.index,
|
|
34824
|
-
end: bodyStart + body.length + 1,
|
|
34825
|
-
name
|
|
34826
|
-
});
|
|
34827
|
-
}
|
|
34828
|
-
}
|
|
34829
|
-
let result = scss;
|
|
34830
|
-
for (const loc of locations.reverse()) {
|
|
34831
|
-
result = result.slice(0, loc.start) + result.slice(loc.end);
|
|
34832
|
-
}
|
|
34833
|
-
return result;
|
|
34834
|
-
}
|
|
34835
|
-
function resolveIncludes(scss, mixins) {
|
|
34836
|
-
return scss.replace(
|
|
34837
|
-
/@include\s+([\w-]+)\s*(?:\(([^)]*)\))?\s*;/g,
|
|
34838
|
-
(_m, name, argsStr) => {
|
|
34839
|
-
if (!(name in mixins)) {
|
|
34840
|
-
return `/* MIXIN NOT FOUND: ${name} */`;
|
|
34841
|
-
}
|
|
34842
|
-
const mixin = mixins[name];
|
|
34843
|
-
const args = argsStr ? argsStr.split(",").map((a) => a.trim()).filter(Boolean) : [];
|
|
34844
|
-
let body = mixin.body;
|
|
34845
|
-
for (let i = 0; i < mixin.params.length; i++) {
|
|
34846
|
-
const paramName = mixin.params[i].split(":")[0].trim();
|
|
34847
|
-
const defaultVal = mixin.params[i].includes(":") ? mixin.params[i].split(":").slice(1).join(":").trim() : "";
|
|
34848
|
-
const value = i < args.length ? args[i] : defaultVal;
|
|
34849
|
-
body = body.replaceAll(`$${paramName}`, value);
|
|
34850
|
-
}
|
|
34851
|
-
return body;
|
|
34852
|
-
}
|
|
34853
|
-
);
|
|
34854
|
-
}
|
|
34855
|
-
function evalMath(scss) {
|
|
34856
|
-
const placeholders = [];
|
|
34857
|
-
const masked = scss.replace(/calc\([^()]*\)/g, (m) => {
|
|
34858
|
-
placeholders.push(m);
|
|
34859
|
-
return `\0CALC${placeholders.length - 1}\0`;
|
|
34860
|
-
});
|
|
34861
|
-
const folded = masked.replace(
|
|
34862
|
-
/([\d.]+)([a-z%]*)\s*([+\-*/])\s*([\d.]+)([a-z%]*)/g,
|
|
34863
|
-
(full, n1, u1, op, n2, u2) => {
|
|
34864
|
-
const num1 = parseFloat(n1);
|
|
34865
|
-
const num2 = parseFloat(n2);
|
|
34866
|
-
if (Number.isNaN(num1) || Number.isNaN(num2)) return full;
|
|
34867
|
-
const unit1 = u1 || "";
|
|
34868
|
-
const unit2 = u2 || "";
|
|
34869
|
-
let unit;
|
|
34870
|
-
if (unit1 === unit2) {
|
|
34871
|
-
unit = unit1;
|
|
34872
|
-
} else if ((op === "*" || op === "/") && unit1 === "") {
|
|
34873
|
-
unit = unit2;
|
|
34874
|
-
} else if ((op === "*" || op === "/") && unit2 === "") {
|
|
34875
|
-
unit = unit1;
|
|
34876
|
-
} else {
|
|
34877
|
-
return full;
|
|
34878
|
-
}
|
|
34879
|
-
let result;
|
|
34880
|
-
switch (op) {
|
|
34881
|
-
case "+":
|
|
34882
|
-
result = num1 + num2;
|
|
34883
|
-
break;
|
|
34884
|
-
case "-":
|
|
34885
|
-
result = num1 - num2;
|
|
34886
|
-
break;
|
|
34887
|
-
case "*":
|
|
34888
|
-
result = num1 * num2;
|
|
34889
|
-
break;
|
|
34890
|
-
case "/":
|
|
34891
|
-
if (num2 === 0) return full;
|
|
34892
|
-
result = num1 / num2;
|
|
34893
|
-
break;
|
|
34894
|
-
default:
|
|
34895
|
-
return full;
|
|
34896
|
-
}
|
|
34897
|
-
if (result === Math.floor(result)) {
|
|
34898
|
-
return `${Math.floor(result)}${unit}`;
|
|
34899
|
-
}
|
|
34900
|
-
return `${result.toFixed(2)}${unit}`;
|
|
34901
|
-
}
|
|
34902
|
-
);
|
|
34903
|
-
return folded.replace(/\x00CALC(\d+)\x00/g, (_m, idx) => {
|
|
34904
|
-
return placeholders[parseInt(idx, 10)];
|
|
34905
|
-
});
|
|
34906
|
-
}
|
|
34907
|
-
function resolveColorFunctions(scss) {
|
|
34908
|
-
scss = scss.replace(
|
|
34909
|
-
/lighten\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/g,
|
|
34910
|
-
(_m, color, amt) => adjustLightness(color.trim(), parseFloat(amt.trim().replace(/%$/, "")) / 100)
|
|
34911
|
-
);
|
|
34912
|
-
scss = scss.replace(
|
|
34913
|
-
/darken\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/g,
|
|
34914
|
-
(_m, color, amt) => adjustLightness(color.trim(), -(parseFloat(amt.trim().replace(/%$/, "")) / 100))
|
|
34915
|
-
);
|
|
34916
|
-
scss = scss.replace(/rgba\(\s*(#[0-9a-fA-F]{3,8})\s*,\s*([\d.]+)\s*\)/g, (whole, hex, alpha) => {
|
|
34917
|
-
const rgb = hexToRgb(hex);
|
|
34918
|
-
return rgb === null ? whole : `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${alpha.trim()})`;
|
|
34919
|
-
});
|
|
34920
|
-
scss = scss.replace(/rgb\(\s*(#[0-9a-fA-F]{3,8})\s*\)/g, (whole, hex) => {
|
|
34921
|
-
const rgb = hexToRgb(hex);
|
|
34922
|
-
return rgb === null ? whole : `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
|
|
34923
|
-
});
|
|
34924
|
-
scss = scss.replace(
|
|
34925
|
-
/mix\(\s*(#[0-9a-fA-F]{3,8})\s*,\s*(#[0-9a-fA-F]{3,8})\s*(?:,\s*([\d.]+%?)\s*)?\)/g,
|
|
34926
|
-
(whole, h1, h2, weight) => {
|
|
34927
|
-
const c1 = hexToRgb(h1);
|
|
34928
|
-
const c2 = hexToRgb(h2);
|
|
34929
|
-
if (c1 === null || c2 === null) return whole;
|
|
34930
|
-
const w = weight ? parseFloat(weight.replace(/%$/, "")) / 100 : 0.5;
|
|
34931
|
-
const mixed = [0, 1, 2].map((i) => Math.round(c1[i] * w + c2[i] * (1 - w)));
|
|
34932
|
-
return `#${mixed.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
34933
|
-
}
|
|
34934
|
-
);
|
|
34935
|
-
return scss;
|
|
34936
|
-
}
|
|
34937
|
-
function hexToRgb(color) {
|
|
34938
|
-
let c = color.trim().replace(/^#/, "");
|
|
34939
|
-
if (c.length === 3) {
|
|
34940
|
-
c = c.split("").map((ch) => ch + ch).join("");
|
|
34941
|
-
}
|
|
34942
|
-
if (!/^[0-9a-fA-F]{6}$/.test(c)) return null;
|
|
34943
|
-
return [parseInt(c.slice(0, 2), 16), parseInt(c.slice(2, 4), 16), parseInt(c.slice(4, 6), 16)];
|
|
34944
|
-
}
|
|
34945
|
-
function adjustLightness(color, amount) {
|
|
34946
|
-
const rgb = hexToRgb(color);
|
|
34947
|
-
if (rgb === null) return color;
|
|
34948
|
-
let [r, g, b] = rgb.map((v) => v / 255);
|
|
34949
|
-
const max = Math.max(r, g, b);
|
|
34950
|
-
const min = Math.min(r, g, b);
|
|
34951
|
-
let l = (max + min) / 2;
|
|
34952
|
-
const d = max - min;
|
|
34953
|
-
let h = 0;
|
|
34954
|
-
let s = 0;
|
|
34955
|
-
if (d !== 0) {
|
|
34956
|
-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
34957
|
-
if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
|
|
34958
|
-
else if (max === g) h = (b - r) / d + 2;
|
|
34959
|
-
else h = (r - g) / d + 4;
|
|
34960
|
-
h /= 6;
|
|
34961
|
-
}
|
|
34962
|
-
l = Math.max(0, Math.min(1, l + amount));
|
|
34963
|
-
if (s === 0) {
|
|
34964
|
-
r = g = b = l;
|
|
34965
|
-
} else {
|
|
34966
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
34967
|
-
const p = 2 * l - q;
|
|
34968
|
-
r = hueToRgb(p, q, h + 1 / 3);
|
|
34969
|
-
g = hueToRgb(p, q, h);
|
|
34970
|
-
b = hueToRgb(p, q, h - 1 / 3);
|
|
34971
|
-
}
|
|
34972
|
-
const hex = (v) => Math.trunc(v * 255).toString(16).padStart(2, "0");
|
|
34973
|
-
return `#${hex(r)}${hex(g)}${hex(b)}`;
|
|
34974
|
-
}
|
|
34975
|
-
function hueToRgb(p, q, t) {
|
|
34976
|
-
if (t < 0) t += 1;
|
|
34977
|
-
if (t > 1) t -= 1;
|
|
34978
|
-
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
34979
|
-
if (t < 1 / 2) return q;
|
|
34980
|
-
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
34981
|
-
return p;
|
|
34982
|
-
}
|
|
34983
|
-
function flattenNesting(scss) {
|
|
34984
|
-
const output = [];
|
|
34985
|
-
flattenBlock(scss, [], output);
|
|
34986
|
-
return output.join("\n");
|
|
34987
|
-
}
|
|
34988
|
-
function flattenBlock(content, parentSelectors, output) {
|
|
34989
|
-
let pos = 0;
|
|
34990
|
-
const properties = [];
|
|
34991
|
-
while (pos < content.length) {
|
|
34992
|
-
while (pos < content.length && /[\s]/.test(content[pos])) {
|
|
34993
|
-
pos++;
|
|
34994
|
-
}
|
|
34995
|
-
if (pos >= content.length) break;
|
|
34996
|
-
if (content[pos] === "/" && content[pos + 1] === "*") {
|
|
34997
|
-
const end = content.indexOf("*/", pos + 2);
|
|
34998
|
-
if (end === -1) break;
|
|
34999
|
-
output.push(content.slice(pos, end + 2));
|
|
35000
|
-
pos = end + 2;
|
|
35001
|
-
continue;
|
|
35002
|
-
}
|
|
35003
|
-
if (content.slice(pos, pos + 6) === "@media") {
|
|
35004
|
-
const brace = content.indexOf("{", pos);
|
|
35005
|
-
if (brace === -1) break;
|
|
35006
|
-
const mediaQuery = content.slice(pos, brace).trim();
|
|
35007
|
-
const body = findBlock(content, brace + 1);
|
|
35008
|
-
if (body === null) break;
|
|
35009
|
-
pos = brace + 1 + body.length + 1;
|
|
35010
|
-
const innerOutput = [];
|
|
35011
|
-
flattenBlock(body, parentSelectors, innerOutput);
|
|
35012
|
-
if (innerOutput.length > 0) {
|
|
35013
|
-
output.push(`${mediaQuery} {`);
|
|
35014
|
-
for (const line of innerOutput) {
|
|
35015
|
-
output.push(` ${line}`);
|
|
35016
|
-
}
|
|
35017
|
-
output.push("}");
|
|
35018
|
-
}
|
|
35019
|
-
continue;
|
|
35020
|
-
}
|
|
35021
|
-
const bracePos = content.indexOf("{", pos);
|
|
35022
|
-
const semiPos = content.indexOf(";", pos);
|
|
35023
|
-
if (semiPos !== -1 && (bracePos === -1 || semiPos < bracePos)) {
|
|
35024
|
-
const prop = content.slice(pos, semiPos).trim();
|
|
35025
|
-
if (prop && !prop.startsWith("@")) {
|
|
35026
|
-
properties.push(prop);
|
|
35027
|
-
}
|
|
35028
|
-
pos = semiPos + 1;
|
|
35029
|
-
continue;
|
|
35030
|
-
}
|
|
35031
|
-
if (bracePos !== -1) {
|
|
35032
|
-
const selectorText = content.slice(pos, bracePos).trim();
|
|
35033
|
-
const body = findBlock(content, bracePos + 1);
|
|
35034
|
-
if (body === null) break;
|
|
35035
|
-
pos = bracePos + 1 + body.length + 1;
|
|
35036
|
-
if (!selectorText) continue;
|
|
35037
|
-
const selectors = selectorText.split(",").map((s) => s.trim());
|
|
35038
|
-
const newSelectors = [];
|
|
35039
|
-
for (const sel of selectors) {
|
|
35040
|
-
if (parentSelectors.length > 0) {
|
|
35041
|
-
for (const parent of parentSelectors) {
|
|
35042
|
-
if (sel.includes("&")) {
|
|
35043
|
-
newSelectors.push(sel.replace(/&/g, parent));
|
|
35044
|
-
} else {
|
|
35045
|
-
newSelectors.push(`${parent} ${sel}`);
|
|
35046
|
-
}
|
|
35047
|
-
}
|
|
35048
|
-
} else {
|
|
35049
|
-
newSelectors.push(sel);
|
|
35050
|
-
}
|
|
35051
|
-
}
|
|
35052
|
-
flattenBlock(body, newSelectors, output);
|
|
35053
|
-
continue;
|
|
35054
|
-
}
|
|
35055
|
-
const remaining = content.slice(pos).trim();
|
|
35056
|
-
if (remaining) {
|
|
35057
|
-
properties.push(remaining);
|
|
35058
|
-
}
|
|
35059
|
-
break;
|
|
35060
|
-
}
|
|
35061
|
-
if (properties.length > 0 && parentSelectors.length > 0) {
|
|
35062
|
-
const selectorStr = parentSelectors.join(", ");
|
|
35063
|
-
output.push(`${selectorStr} {`);
|
|
35064
|
-
for (const prop of properties) {
|
|
35065
|
-
output.push(` ${prop};`);
|
|
35066
|
-
}
|
|
35067
|
-
output.push("}");
|
|
35068
|
-
}
|
|
35069
|
-
}
|
|
35070
|
-
function findBlock(content, start2) {
|
|
35071
|
-
let depth = 1;
|
|
35072
|
-
let pos = start2;
|
|
35073
|
-
while (pos < content.length && depth > 0) {
|
|
35074
|
-
if (content[pos] === "{") depth++;
|
|
35075
|
-
else if (content[pos] === "}") depth--;
|
|
35076
|
-
if (depth > 0) pos++;
|
|
35077
|
-
}
|
|
35078
|
-
return depth === 0 ? content.slice(start2, pos) : null;
|
|
35079
|
-
}
|
|
35080
|
-
function cleanup(css) {
|
|
35081
|
-
css = css.replace(/[^{}]+\{\s*\}/g, "");
|
|
35082
|
-
css = css.replace(/\n{3,}/g, "\n\n");
|
|
35083
|
-
css = css.split("\n").map((line) => line.trimEnd()).join("\n");
|
|
35084
|
-
return css.trim() + "\n";
|
|
35085
|
-
}
|
|
35086
|
-
var ScssCompiler, VARIABLE_FLAG;
|
|
35087
|
-
var init_scss = __esm({
|
|
35088
|
-
"src/scss.ts"() {
|
|
35089
|
-
"use strict";
|
|
35090
|
-
ScssCompiler = class {
|
|
35091
|
-
_importPaths;
|
|
35092
|
-
_variables;
|
|
35093
|
-
constructor(config) {
|
|
35094
|
-
this._importPaths = config?.importPaths ? [...config.importPaths] : [];
|
|
35095
|
-
this._variables = config?.variables ? { ...config.variables } : {};
|
|
35096
|
-
}
|
|
35097
|
-
/** Compile an SCSS string to CSS. */
|
|
35098
|
-
compile(source) {
|
|
35099
|
-
return compileString(source, this._importPaths, { ...this._variables });
|
|
35100
|
-
}
|
|
35101
|
-
/** Compile an SCSS file to CSS. */
|
|
35102
|
-
compileFile(filePath) {
|
|
35103
|
-
const absPath = resolve19(filePath);
|
|
35104
|
-
const content = readFileSync23(absPath, "utf-8");
|
|
35105
|
-
const paths = [dirname12(absPath), ...this._importPaths];
|
|
35106
|
-
return compileString(content, paths, { ...this._variables });
|
|
35107
|
-
}
|
|
35108
|
-
/** Add a directory to the import resolution path. */
|
|
35109
|
-
addImportPath(path8) {
|
|
35110
|
-
this._importPaths.push(resolve19(path8));
|
|
35111
|
-
}
|
|
35112
|
-
/** Set or override an SCSS variable. */
|
|
35113
|
-
setVariable(name, value) {
|
|
35114
|
-
const key = name.startsWith("$") ? name.slice(1) : name;
|
|
35115
|
-
this._variables[key] = value;
|
|
35116
|
-
}
|
|
35117
|
-
/** Compile all .scss files in a directory into a single CSS output file. */
|
|
35118
|
-
compileScss(scssDir = "src/scss", output = "src/public/css/default.css", minify = false) {
|
|
35119
|
-
const absDir = resolve19(scssDir);
|
|
35120
|
-
if (!existsSync24(absDir)) return "";
|
|
35121
|
-
const files = readdirSync18(absDir).filter((f) => f.endsWith(".scss") && !f.startsWith("_")).sort().map((f) => join27(absDir, f));
|
|
35122
|
-
if (files.length === 0) return "";
|
|
35123
|
-
const paths = [absDir, ...this._importPaths];
|
|
35124
|
-
const imported = /* @__PURE__ */ new Set();
|
|
35125
|
-
let merged = "";
|
|
35126
|
-
for (const file of files) {
|
|
35127
|
-
const content = readFileSync23(file, "utf-8");
|
|
35128
|
-
imported.add(file);
|
|
35129
|
-
merged += resolveImports(content, paths, imported) + "\n";
|
|
35130
|
-
}
|
|
35131
|
-
let css = compileString(merged, paths, { ...this._variables });
|
|
35132
|
-
if (minify) {
|
|
35133
|
-
css = css.replace(/\/\*.*?\*\//gs, "");
|
|
35134
|
-
css = css.replace(/\s+/g, " ");
|
|
35135
|
-
css = css.replace(/\s*([{}:;,])\s*/g, "$1");
|
|
35136
|
-
css = css.replace(/;}/g, "}");
|
|
35137
|
-
css = css.trim();
|
|
35138
|
-
}
|
|
35139
|
-
const absOutput = resolve19(output);
|
|
35140
|
-
const outDir = dirname12(absOutput);
|
|
35141
|
-
if (!existsSync24(outDir)) mkdirSync18(outDir, { recursive: true });
|
|
35142
|
-
let existing = null;
|
|
35143
|
-
try {
|
|
35144
|
-
existing = existsSync24(absOutput) ? readFileSync23(absOutput, "utf-8") : null;
|
|
35145
|
-
} catch {
|
|
35146
|
-
existing = null;
|
|
35147
|
-
}
|
|
35148
|
-
if (existing !== css) {
|
|
35149
|
-
writeFileSync16(absOutput, css, "utf-8");
|
|
35150
|
-
}
|
|
35151
|
-
return css;
|
|
35152
|
-
}
|
|
35153
|
-
};
|
|
35154
|
-
VARIABLE_FLAG = /\s*!(default|global)\s*$/;
|
|
35155
|
-
}
|
|
35156
|
-
});
|
|
35157
|
-
|
|
35158
34761
|
// src/mqttMessage.ts
|
|
35159
34762
|
var MqttMessage;
|
|
35160
34763
|
var init_mqttMessage = __esm({
|
|
@@ -35228,7 +34831,7 @@ var init_mqttMessage = __esm({
|
|
|
35228
34831
|
import net2 from "node:net";
|
|
35229
34832
|
import tls from "node:tls";
|
|
35230
34833
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
35231
|
-
import { existsSync as
|
|
34834
|
+
import { existsSync as existsSync24, readFileSync as readFileSync23 } from "node:fs";
|
|
35232
34835
|
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;
|
|
35233
34836
|
var init_mqtt = __esm({
|
|
35234
34837
|
"src/mqtt.ts"() {
|
|
@@ -35431,7 +35034,7 @@ var init_mqtt = __esm({
|
|
|
35431
35034
|
*/
|
|
35432
35035
|
async connect() {
|
|
35433
35036
|
this.closeSocket();
|
|
35434
|
-
if (this.secure && this.tlsVerify && this.caFile && !
|
|
35037
|
+
if (this.secure && this.tlsVerify && this.caFile && !existsSync24(this.caFile)) {
|
|
35435
35038
|
throw new MqttError(
|
|
35436
35039
|
`MQTT CA file not found: ${this.caFile} -- TINA4_MQTT_CA_FILE (or caFile) must point at the broker's CA certificate in PEM form`
|
|
35437
35040
|
);
|
|
@@ -35668,7 +35271,7 @@ var init_mqtt = __esm({
|
|
|
35668
35271
|
* a later client.
|
|
35669
35272
|
*/
|
|
35670
35273
|
openSocket() {
|
|
35671
|
-
return new Promise((
|
|
35274
|
+
return new Promise((resolve20, reject) => {
|
|
35672
35275
|
let settled = false;
|
|
35673
35276
|
const settle = (fn) => {
|
|
35674
35277
|
if (settled) return;
|
|
@@ -35693,10 +35296,10 @@ var init_mqtt = __esm({
|
|
|
35693
35296
|
servername: this.host,
|
|
35694
35297
|
rejectUnauthorized: this.tlsVerify
|
|
35695
35298
|
};
|
|
35696
|
-
if (this.tlsVerify && this.caFile) opts.ca =
|
|
35697
|
-
sock = tls.connect(opts, () => settle(() =>
|
|
35299
|
+
if (this.tlsVerify && this.caFile) opts.ca = readFileSync23(this.caFile);
|
|
35300
|
+
sock = tls.connect(opts, () => settle(() => resolve20(sock)));
|
|
35698
35301
|
} else {
|
|
35699
|
-
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() =>
|
|
35302
|
+
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve20(sock)));
|
|
35700
35303
|
}
|
|
35701
35304
|
sock.once("error", (err) => {
|
|
35702
35305
|
settle(() => {
|
|
@@ -35735,13 +35338,13 @@ var init_mqtt = __esm({
|
|
|
35735
35338
|
writePacket(header, body) {
|
|
35736
35339
|
if (this.socket === null) return Promise.reject(new MqttError("not connected to an MQTT broker"));
|
|
35737
35340
|
const packet = Buffer.concat([Buffer.from([header]), _Mqtt.encodeRemainingLength(body.length), body]);
|
|
35738
|
-
return new Promise((
|
|
35341
|
+
return new Promise((resolve20, reject) => {
|
|
35739
35342
|
this.socket.write(packet, (err) => {
|
|
35740
35343
|
if (err) {
|
|
35741
35344
|
reject(new MqttError(`MQTT write failed: ${err.message}`));
|
|
35742
35345
|
} else {
|
|
35743
35346
|
this.lastWriteAt = Date.now();
|
|
35744
|
-
|
|
35347
|
+
resolve20();
|
|
35745
35348
|
}
|
|
35746
35349
|
});
|
|
35747
35350
|
});
|
|
@@ -35774,7 +35377,7 @@ var init_mqtt = __esm({
|
|
|
35774
35377
|
if (this.readBuffer.length >= need) return Promise.resolve(this.take(need));
|
|
35775
35378
|
if (this.socket === null) return Promise.reject(this.socketError ?? new MqttError("not connected to an MQTT broker"));
|
|
35776
35379
|
if (this.socketError !== null) return Promise.reject(this.socketError);
|
|
35777
|
-
return new Promise((
|
|
35380
|
+
return new Promise((resolve20, reject) => {
|
|
35778
35381
|
let timer = null;
|
|
35779
35382
|
if (deadline !== null) {
|
|
35780
35383
|
const remaining = deadline - Date.now();
|
|
@@ -35789,7 +35392,7 @@ var init_mqtt = __esm({
|
|
|
35789
35392
|
}
|
|
35790
35393
|
}, remaining);
|
|
35791
35394
|
}
|
|
35792
|
-
this.waiter = { need, resolve:
|
|
35395
|
+
this.waiter = { need, resolve: resolve20, reject, timer };
|
|
35793
35396
|
this.serviceWaiter();
|
|
35794
35397
|
});
|
|
35795
35398
|
}
|
|
@@ -35913,8 +35516,8 @@ var init_mqtt = __esm({
|
|
|
35913
35516
|
});
|
|
35914
35517
|
|
|
35915
35518
|
// src/service.ts
|
|
35916
|
-
import { readdirSync as
|
|
35917
|
-
import { join as
|
|
35519
|
+
import { readdirSync as readdirSync18, statSync as statSync18, watchFile, unwatchFile } from "node:fs";
|
|
35520
|
+
import { join as join27, extname as extname8 } from "node:path";
|
|
35918
35521
|
import { pathToFileURL } from "node:url";
|
|
35919
35522
|
function matchCronField(field, value) {
|
|
35920
35523
|
if (field === "*") return true;
|
|
@@ -36080,14 +35683,14 @@ var init_service = __esm({
|
|
|
36080
35683
|
const discovered = [];
|
|
36081
35684
|
let entries;
|
|
36082
35685
|
try {
|
|
36083
|
-
entries =
|
|
35686
|
+
entries = readdirSync18(dir);
|
|
36084
35687
|
} catch {
|
|
36085
35688
|
return discovered;
|
|
36086
35689
|
}
|
|
36087
35690
|
for (const entry of entries) {
|
|
36088
35691
|
const ext = extname8(entry);
|
|
36089
35692
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
36090
|
-
const fullPath =
|
|
35693
|
+
const fullPath = join27(dir, entry);
|
|
36091
35694
|
const stat = statSync18(fullPath);
|
|
36092
35695
|
if (!stat.isFile()) continue;
|
|
36093
35696
|
try {
|
|
@@ -36196,14 +35799,14 @@ var init_service = __esm({
|
|
|
36196
35799
|
const dir = serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
36197
35800
|
let entries;
|
|
36198
35801
|
try {
|
|
36199
|
-
entries =
|
|
35802
|
+
entries = readdirSync18(dir);
|
|
36200
35803
|
} catch {
|
|
36201
35804
|
return;
|
|
36202
35805
|
}
|
|
36203
35806
|
for (const entry of entries) {
|
|
36204
35807
|
const ext = extname8(entry);
|
|
36205
35808
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
36206
|
-
const fullPath =
|
|
35809
|
+
const fullPath = join27(dir, entry);
|
|
36207
35810
|
if (watchedFiles.has(fullPath)) continue;
|
|
36208
35811
|
watchedFiles.add(fullPath);
|
|
36209
35812
|
watchFile(fullPath, { interval: 1e3 }, async () => {
|
|
@@ -36247,7 +35850,7 @@ import https from "node:https";
|
|
|
36247
35850
|
import { URL as URL2 } from "node:url";
|
|
36248
35851
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
36249
35852
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
36250
|
-
import { basename as
|
|
35853
|
+
import { basename as basename4 } from "node:path";
|
|
36251
35854
|
import { pipeline } from "node:stream/promises";
|
|
36252
35855
|
function sameOrigin(urlA, urlB) {
|
|
36253
35856
|
try {
|
|
@@ -36537,7 +36140,7 @@ var init_api = __esm({
|
|
|
36537
36140
|
error: err instanceof Error ? err.message : String(err)
|
|
36538
36141
|
};
|
|
36539
36142
|
}
|
|
36540
|
-
uploadName = filename ||
|
|
36143
|
+
uploadName = filename || basename4(filePath);
|
|
36541
36144
|
} else {
|
|
36542
36145
|
return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
|
|
36543
36146
|
}
|
|
@@ -36752,12 +36355,12 @@ var init_api = __esm({
|
|
|
36752
36355
|
* authenticate to.
|
|
36753
36356
|
*/
|
|
36754
36357
|
performRequest(method, url, headers, data, redirectsLeft) {
|
|
36755
|
-
return new Promise((
|
|
36358
|
+
return new Promise((resolve20) => {
|
|
36756
36359
|
let parsed;
|
|
36757
36360
|
try {
|
|
36758
36361
|
parsed = new URL2(url);
|
|
36759
36362
|
} catch (err) {
|
|
36760
|
-
|
|
36363
|
+
resolve20({ kind: "error", error: err instanceof Error ? err.message : String(err) });
|
|
36761
36364
|
return;
|
|
36762
36365
|
}
|
|
36763
36366
|
const isHttps = parsed.protocol === "https:";
|
|
@@ -36782,7 +36385,7 @@ var init_api = __esm({
|
|
|
36782
36385
|
try {
|
|
36783
36386
|
nextUrl = new URL2(location, url).toString();
|
|
36784
36387
|
} catch {
|
|
36785
|
-
|
|
36388
|
+
resolve20({ kind: "response", res });
|
|
36786
36389
|
return;
|
|
36787
36390
|
}
|
|
36788
36391
|
const crossOrigin = !sameOrigin(url, nextUrl);
|
|
@@ -36800,17 +36403,17 @@ var init_api = __esm({
|
|
|
36800
36403
|
deleteHeaderCaseInsensitive(nextHeaders, name);
|
|
36801
36404
|
}
|
|
36802
36405
|
}
|
|
36803
|
-
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(
|
|
36406
|
+
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(resolve20);
|
|
36804
36407
|
return;
|
|
36805
36408
|
}
|
|
36806
|
-
|
|
36409
|
+
resolve20({ kind: "response", res });
|
|
36807
36410
|
});
|
|
36808
36411
|
req2.on("timeout", () => {
|
|
36809
36412
|
req2.destroy();
|
|
36810
|
-
|
|
36413
|
+
resolve20({ kind: "error", error: `Request timed out after ${this.timeout}s` });
|
|
36811
36414
|
});
|
|
36812
36415
|
req2.on("error", (err) => {
|
|
36813
|
-
|
|
36416
|
+
resolve20({ kind: "error", error: err.message });
|
|
36814
36417
|
});
|
|
36815
36418
|
if (data) {
|
|
36816
36419
|
req2.write(data);
|
|
@@ -36820,7 +36423,7 @@ var init_api = __esm({
|
|
|
36820
36423
|
}
|
|
36821
36424
|
/** Buffer a response body, parse JSON if possible, and store cookies. */
|
|
36822
36425
|
readResponse(res) {
|
|
36823
|
-
return new Promise((
|
|
36426
|
+
return new Promise((resolve20) => {
|
|
36824
36427
|
const chunks = [];
|
|
36825
36428
|
res.on("data", (chunk) => {
|
|
36826
36429
|
chunks.push(chunk);
|
|
@@ -36835,7 +36438,7 @@ var init_api = __esm({
|
|
|
36835
36438
|
} catch {
|
|
36836
36439
|
parsed = raw;
|
|
36837
36440
|
}
|
|
36838
|
-
|
|
36441
|
+
resolve20({
|
|
36839
36442
|
http_code: res.statusCode ?? null,
|
|
36840
36443
|
body: parsed,
|
|
36841
36444
|
headers: respHeaders,
|
|
@@ -36843,7 +36446,7 @@ var init_api = __esm({
|
|
|
36843
36446
|
});
|
|
36844
36447
|
});
|
|
36845
36448
|
res.on("error", (err) => {
|
|
36846
|
-
|
|
36449
|
+
resolve20({ http_code: null, body: null, headers: {}, error: err.message });
|
|
36847
36450
|
});
|
|
36848
36451
|
});
|
|
36849
36452
|
}
|
|
@@ -36896,14 +36499,14 @@ var init_api = __esm({
|
|
|
36896
36499
|
// src/messenger.ts
|
|
36897
36500
|
import net3 from "node:net";
|
|
36898
36501
|
import tls2 from "node:tls";
|
|
36899
|
-
import { readFileSync as
|
|
36900
|
-
import { basename as
|
|
36502
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
36503
|
+
import { basename as basename5 } from "node:path";
|
|
36901
36504
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
36902
36505
|
function tlsRejectUnauthorized() {
|
|
36903
36506
|
return !isTruthy(process.env.TINA4_MAIL_TLS_INSECURE);
|
|
36904
36507
|
}
|
|
36905
36508
|
function readResponse(socket) {
|
|
36906
|
-
return new Promise((
|
|
36509
|
+
return new Promise((resolve20, reject) => {
|
|
36907
36510
|
let buffer = "";
|
|
36908
36511
|
const onData = (chunk) => {
|
|
36909
36512
|
buffer += chunk.toString("utf-8");
|
|
@@ -36915,7 +36518,7 @@ function readResponse(socket) {
|
|
|
36915
36518
|
if (line.length >= 4 && line[3] === " ") {
|
|
36916
36519
|
socket.removeListener("data", onData);
|
|
36917
36520
|
socket.removeListener("error", onError);
|
|
36918
|
-
|
|
36521
|
+
resolve20({ code, text: buffer.trim() });
|
|
36919
36522
|
return;
|
|
36920
36523
|
}
|
|
36921
36524
|
}
|
|
@@ -36929,10 +36532,10 @@ function readResponse(socket) {
|
|
|
36929
36532
|
});
|
|
36930
36533
|
}
|
|
36931
36534
|
function sendCommand(socket, command) {
|
|
36932
|
-
return new Promise((
|
|
36535
|
+
return new Promise((resolve20, reject) => {
|
|
36933
36536
|
socket.write(command + "\r\n", "utf-8", (err) => {
|
|
36934
36537
|
if (err) return reject(err);
|
|
36935
|
-
readResponse(socket).then(
|
|
36538
|
+
readResponse(socket).then(resolve20, reject);
|
|
36936
36539
|
});
|
|
36937
36540
|
});
|
|
36938
36541
|
}
|
|
@@ -36988,8 +36591,8 @@ function buildMimeMessage(options) {
|
|
|
36988
36591
|
lines.push(options.body);
|
|
36989
36592
|
}
|
|
36990
36593
|
for (const filePath of options.attachments) {
|
|
36991
|
-
const fileName =
|
|
36992
|
-
const fileData =
|
|
36594
|
+
const fileName = basename5(filePath);
|
|
36595
|
+
const fileData = readFileSync24(filePath);
|
|
36993
36596
|
const base64Data = fileData.toString("base64");
|
|
36994
36597
|
lines.push("");
|
|
36995
36598
|
lines.push(`--${boundary}`);
|
|
@@ -37032,7 +36635,7 @@ function imapQuote(s) {
|
|
|
37032
36635
|
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
37033
36636
|
}
|
|
37034
36637
|
function imapReadLine(socket) {
|
|
37035
|
-
return new Promise((
|
|
36638
|
+
return new Promise((resolve20, reject) => {
|
|
37036
36639
|
let buffer = "";
|
|
37037
36640
|
const onData = (chunk) => {
|
|
37038
36641
|
buffer += chunk.toString("utf-8");
|
|
@@ -37040,7 +36643,7 @@ function imapReadLine(socket) {
|
|
|
37040
36643
|
if (nlIndex !== -1) {
|
|
37041
36644
|
socket.removeListener("data", onData);
|
|
37042
36645
|
socket.removeListener("error", onError);
|
|
37043
|
-
|
|
36646
|
+
resolve20(buffer);
|
|
37044
36647
|
}
|
|
37045
36648
|
};
|
|
37046
36649
|
const onError = (err) => {
|
|
@@ -37052,7 +36655,7 @@ function imapReadLine(socket) {
|
|
|
37052
36655
|
});
|
|
37053
36656
|
}
|
|
37054
36657
|
function imapCommand(socket, command) {
|
|
37055
|
-
return new Promise((
|
|
36658
|
+
return new Promise((resolve20, reject) => {
|
|
37056
36659
|
imapTagCounter++;
|
|
37057
36660
|
const tag = `T${imapTagCounter}`;
|
|
37058
36661
|
const fullCommand = `${tag} ${command}\r
|
|
@@ -37063,7 +36666,7 @@ function imapCommand(socket, command) {
|
|
|
37063
36666
|
if (buffer.includes(`${tag} OK`)) {
|
|
37064
36667
|
socket.removeListener("data", onData);
|
|
37065
36668
|
socket.removeListener("error", onError);
|
|
37066
|
-
|
|
36669
|
+
resolve20(buffer);
|
|
37067
36670
|
return;
|
|
37068
36671
|
}
|
|
37069
36672
|
if (buffer.includes(`${tag} NO`) || buffer.includes(`${tag} BAD`)) {
|
|
@@ -37092,91 +36695,149 @@ function parseSearchResponse(response) {
|
|
|
37092
36695
|
if (!match) return [];
|
|
37093
36696
|
return match[1].trim().split(/\s+/).filter((s) => /^\d+$/.test(s));
|
|
37094
36697
|
}
|
|
37095
|
-
function
|
|
37096
|
-
const
|
|
37097
|
-
|
|
37098
|
-
|
|
37099
|
-
|
|
37100
|
-
let currentKey = "";
|
|
37101
|
-
for (const line of lines) {
|
|
37102
|
-
if (/^\s/.test(line) && currentKey) {
|
|
37103
|
-
headers[currentKey] += " " + line.trim();
|
|
37104
|
-
} else {
|
|
37105
|
-
const colonIdx = line.indexOf(":");
|
|
37106
|
-
if (colonIdx > 0) {
|
|
37107
|
-
currentKey = line.substring(0, colonIdx).trim().toLowerCase();
|
|
37108
|
-
headers[currentKey] = line.substring(colonIdx + 1).trim();
|
|
37109
|
-
}
|
|
37110
|
-
}
|
|
37111
|
-
}
|
|
37112
|
-
}
|
|
37113
|
-
const seen = /\\Seen/i.test(response);
|
|
37114
|
-
return {
|
|
37115
|
-
uid,
|
|
37116
|
-
subject: headers["subject"] ?? "",
|
|
37117
|
-
from: headers["from"] ?? "",
|
|
37118
|
-
to: headers["to"] ?? "",
|
|
37119
|
-
date: headers["date"] ?? "",
|
|
37120
|
-
snippet: "",
|
|
37121
|
-
seen
|
|
37122
|
-
};
|
|
36698
|
+
function extractRawMessage(response) {
|
|
36699
|
+
const m = response.match(/\{(\d+)\}\r\n/);
|
|
36700
|
+
if (!m) return response;
|
|
36701
|
+
const start2 = (m.index ?? 0) + m[0].length;
|
|
36702
|
+
return response.slice(start2, start2 + parseInt(m[1], 10));
|
|
37123
36703
|
}
|
|
37124
|
-
function
|
|
37125
|
-
const bodyMatch = response.match(/\{(\d+)\}\r\n([\s\S]*)/);
|
|
37126
|
-
const rawMessage = bodyMatch ? bodyMatch[2] : response;
|
|
37127
|
-
const headerEnd = rawMessage.indexOf("\r\n\r\n");
|
|
37128
|
-
const headerSection = headerEnd > 0 ? rawMessage.substring(0, headerEnd) : rawMessage;
|
|
37129
|
-
const bodySection = headerEnd > 0 ? rawMessage.substring(headerEnd + 4) : "";
|
|
36704
|
+
function parseMimeHeaders(section) {
|
|
37130
36705
|
const headers = {};
|
|
37131
|
-
const headerLines = headerSection.split(/\r\n/);
|
|
37132
36706
|
let currentKey = "";
|
|
37133
|
-
for (const line of
|
|
36707
|
+
for (const line of section.split(/\r\n/)) {
|
|
37134
36708
|
if (/^\s/.test(line) && currentKey) {
|
|
37135
36709
|
headers[currentKey] += " " + line.trim();
|
|
37136
36710
|
} else {
|
|
37137
|
-
const
|
|
37138
|
-
if (
|
|
37139
|
-
currentKey = line.substring(0,
|
|
37140
|
-
headers[currentKey] = line.substring(
|
|
36711
|
+
const idx = line.indexOf(":");
|
|
36712
|
+
if (idx > 0) {
|
|
36713
|
+
currentKey = line.substring(0, idx).trim().toLowerCase();
|
|
36714
|
+
headers[currentKey] = line.substring(idx + 1).trim();
|
|
36715
|
+
}
|
|
36716
|
+
}
|
|
36717
|
+
}
|
|
36718
|
+
return headers;
|
|
36719
|
+
}
|
|
36720
|
+
function decodeTransfer(body, encoding) {
|
|
36721
|
+
const enc = encoding.toLowerCase().trim();
|
|
36722
|
+
if (enc === "base64") {
|
|
36723
|
+
try {
|
|
36724
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64").toString("utf-8");
|
|
36725
|
+
} catch {
|
|
36726
|
+
return body;
|
|
36727
|
+
}
|
|
36728
|
+
}
|
|
36729
|
+
if (enc === "quoted-printable") {
|
|
36730
|
+
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)));
|
|
36731
|
+
}
|
|
36732
|
+
return body;
|
|
36733
|
+
}
|
|
36734
|
+
function decodeAttachmentBytes(body, encoding) {
|
|
36735
|
+
const enc = encoding.toLowerCase().trim();
|
|
36736
|
+
if (enc === "base64") {
|
|
36737
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64");
|
|
36738
|
+
}
|
|
36739
|
+
const trimmed = body.replace(/\r\n$/, "");
|
|
36740
|
+
if (enc === "quoted-printable") {
|
|
36741
|
+
const collapsed = trimmed.replace(/=\r?\n/g, "");
|
|
36742
|
+
const bytes = [];
|
|
36743
|
+
for (let i = 0; i < collapsed.length; i++) {
|
|
36744
|
+
const hex = collapsed.substring(i + 1, i + 3);
|
|
36745
|
+
if (collapsed[i] === "=" && /^[0-9A-Fa-f]{2}$/.test(hex)) {
|
|
36746
|
+
bytes.push(parseInt(hex, 16));
|
|
36747
|
+
i += 2;
|
|
36748
|
+
} else {
|
|
36749
|
+
bytes.push(collapsed.charCodeAt(i) & 255);
|
|
37141
36750
|
}
|
|
37142
36751
|
}
|
|
36752
|
+
return Buffer.from(bytes);
|
|
37143
36753
|
}
|
|
36754
|
+
return Buffer.from(trimmed, "utf-8");
|
|
36755
|
+
}
|
|
36756
|
+
function attachmentFilename(disposition, contentType) {
|
|
36757
|
+
const d = disposition.match(/filename="?([^";\r\n]+)"?/i);
|
|
36758
|
+
if (d) return d[1].trim();
|
|
36759
|
+
const c = contentType.match(/name="?([^";\r\n]+)"?/i);
|
|
36760
|
+
if (c) return c[1].trim();
|
|
36761
|
+
return "attachment";
|
|
36762
|
+
}
|
|
36763
|
+
function makeSnippet(bodyText, bodyHtml) {
|
|
36764
|
+
return (bodyText || bodyHtml || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
36765
|
+
}
|
|
36766
|
+
function toIsoDate(raw) {
|
|
36767
|
+
if (!raw) return "";
|
|
36768
|
+
const d = new Date(raw);
|
|
36769
|
+
return Number.isNaN(d.getTime()) ? raw : d.toISOString();
|
|
36770
|
+
}
|
|
36771
|
+
function parseMessage(response) {
|
|
36772
|
+
const raw = extractRawMessage(response);
|
|
36773
|
+
const headerEnd = raw.indexOf("\r\n\r\n");
|
|
36774
|
+
const headerSection = headerEnd >= 0 ? raw.substring(0, headerEnd) : raw;
|
|
36775
|
+
const bodySection = headerEnd >= 0 ? raw.substring(headerEnd + 4) : "";
|
|
36776
|
+
const headers = parseMimeHeaders(headerSection);
|
|
37144
36777
|
const contentType = headers["content-type"] ?? "text/plain";
|
|
37145
36778
|
let bodyText = "";
|
|
37146
36779
|
let bodyHtml = "";
|
|
36780
|
+
const attachments = [];
|
|
37147
36781
|
if (contentType.includes("multipart")) {
|
|
37148
36782
|
const boundaryMatch = contentType.match(/boundary="?([^";\s]+)"?/);
|
|
37149
36783
|
if (boundaryMatch) {
|
|
37150
|
-
const boundary = boundaryMatch[1];
|
|
37151
|
-
const
|
|
37152
|
-
|
|
37153
|
-
if (
|
|
37154
|
-
const
|
|
37155
|
-
|
|
37156
|
-
const
|
|
37157
|
-
|
|
37158
|
-
|
|
37159
|
-
|
|
37160
|
-
|
|
36784
|
+
const boundary = "--" + boundaryMatch[1];
|
|
36785
|
+
for (const part of bodySection.split(boundary)) {
|
|
36786
|
+
const trimmed = part.trim();
|
|
36787
|
+
if (trimmed === "" || trimmed === "--") continue;
|
|
36788
|
+
const pEnd = part.indexOf("\r\n\r\n");
|
|
36789
|
+
if (pEnd < 0) continue;
|
|
36790
|
+
const pHeaders = parseMimeHeaders(part.substring(0, pEnd));
|
|
36791
|
+
const pBody = part.substring(pEnd + 4);
|
|
36792
|
+
const cte = pHeaders["content-transfer-encoding"] ?? "";
|
|
36793
|
+
const pType = pHeaders["content-type"] ?? "text/plain";
|
|
36794
|
+
const disposition = pHeaders["content-disposition"] ?? "";
|
|
36795
|
+
if (/attachment/i.test(disposition)) {
|
|
36796
|
+
const content = decodeAttachmentBytes(pBody, cte);
|
|
36797
|
+
attachments.push({
|
|
36798
|
+
filename: attachmentFilename(disposition, pType),
|
|
36799
|
+
contentType: pType.split(";")[0].trim(),
|
|
36800
|
+
size: content.length,
|
|
36801
|
+
content
|
|
36802
|
+
});
|
|
36803
|
+
} else if (pType.includes("text/html")) {
|
|
36804
|
+
bodyHtml = decodeTransfer(pBody, cte).trim();
|
|
36805
|
+
} else if (pType.includes("text/plain")) {
|
|
36806
|
+
bodyText = decodeTransfer(pBody, cte).trim();
|
|
37161
36807
|
}
|
|
37162
36808
|
}
|
|
37163
36809
|
}
|
|
37164
36810
|
} else if (contentType.includes("text/html")) {
|
|
37165
|
-
bodyHtml = bodySection;
|
|
36811
|
+
bodyHtml = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
37166
36812
|
} else {
|
|
37167
|
-
bodyText = bodySection;
|
|
36813
|
+
bodyText = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
37168
36814
|
}
|
|
37169
|
-
|
|
37170
|
-
|
|
36815
|
+
return { headers, bodyText, bodyHtml, attachments };
|
|
36816
|
+
}
|
|
36817
|
+
function parseSummary(uid, response) {
|
|
36818
|
+
const { headers, bodyText, bodyHtml } = parseMessage(response);
|
|
36819
|
+
return {
|
|
36820
|
+
uid,
|
|
36821
|
+
subject: headers["subject"] ?? "",
|
|
36822
|
+
from: headers["from"] ?? "",
|
|
36823
|
+
to: headers["to"] ?? "",
|
|
36824
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
36825
|
+
snippet: makeSnippet(bodyText, bodyHtml),
|
|
36826
|
+
seen: /\\Seen/i.test(response)
|
|
36827
|
+
};
|
|
36828
|
+
}
|
|
36829
|
+
function parseFullMessage(uid, response) {
|
|
36830
|
+
const { headers, bodyText, bodyHtml, attachments } = parseMessage(response);
|
|
37171
36831
|
return {
|
|
37172
36832
|
uid,
|
|
37173
36833
|
subject: headers["subject"] ?? "",
|
|
37174
36834
|
from: headers["from"] ?? "",
|
|
37175
36835
|
to: headers["to"] ?? "",
|
|
37176
36836
|
cc: headers["cc"] ?? "",
|
|
37177
|
-
date: headers["date"] ?? "",
|
|
36837
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
37178
36838
|
bodyText,
|
|
37179
36839
|
bodyHtml,
|
|
36840
|
+
attachments,
|
|
37180
36841
|
headers
|
|
37181
36842
|
};
|
|
37182
36843
|
}
|
|
@@ -37292,89 +36953,89 @@ var init_messenger = __esm({
|
|
|
37292
36953
|
}
|
|
37293
36954
|
const messageId = `${randomUUID7()}@${this.host}`;
|
|
37294
36955
|
if (allRecipients.length === 0) {
|
|
37295
|
-
return { success: false, message: "No recipients specified" };
|
|
36956
|
+
return { success: false, message: "No recipients specified", id: null };
|
|
37296
36957
|
}
|
|
37297
36958
|
if (!this.fromAddress) {
|
|
37298
|
-
return { success: false, message: "No from address configured" };
|
|
36959
|
+
return { success: false, message: "No from address configured", id: null };
|
|
37299
36960
|
}
|
|
37300
36961
|
try {
|
|
37301
36962
|
let socket;
|
|
37302
36963
|
if (this.port === 465) {
|
|
37303
36964
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37304
|
-
await new Promise((
|
|
37305
|
-
socket.once("secureConnect",
|
|
36965
|
+
await new Promise((resolve20, reject) => {
|
|
36966
|
+
socket.once("secureConnect", resolve20);
|
|
37306
36967
|
socket.once("error", reject);
|
|
37307
36968
|
});
|
|
37308
36969
|
} else {
|
|
37309
36970
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
37310
|
-
await new Promise((
|
|
37311
|
-
socket.once("connect",
|
|
36971
|
+
await new Promise((resolve20, reject) => {
|
|
36972
|
+
socket.once("connect", resolve20);
|
|
37312
36973
|
socket.once("error", reject);
|
|
37313
36974
|
});
|
|
37314
36975
|
}
|
|
37315
36976
|
const greeting = await readResponse(socket);
|
|
37316
36977
|
if (greeting.code !== 220) {
|
|
37317
36978
|
socket.destroy();
|
|
37318
|
-
return { success: false, message: `SMTP greeting failed: ${greeting.text}
|
|
36979
|
+
return { success: false, message: `SMTP greeting failed: ${greeting.text}`, id: null };
|
|
37319
36980
|
}
|
|
37320
36981
|
const ehlo = await sendCommand(socket, `EHLO ${this.host}`);
|
|
37321
36982
|
if (ehlo.code !== 250) {
|
|
37322
36983
|
socket.destroy();
|
|
37323
|
-
return { success: false, message: `EHLO failed: ${ehlo.text}
|
|
36984
|
+
return { success: false, message: `EHLO failed: ${ehlo.text}`, id: null };
|
|
37324
36985
|
}
|
|
37325
36986
|
if (this.useTls && this.port !== 465 && ehlo.text.includes("STARTTLS")) {
|
|
37326
36987
|
const starttls = await sendCommand(socket, "STARTTLS");
|
|
37327
36988
|
if (starttls.code !== 220) {
|
|
37328
36989
|
socket.destroy();
|
|
37329
|
-
return { success: false, message: `STARTTLS failed: ${starttls.text}
|
|
36990
|
+
return { success: false, message: `STARTTLS failed: ${starttls.text}`, id: null };
|
|
37330
36991
|
}
|
|
37331
36992
|
const plainSocket = socket;
|
|
37332
36993
|
socket = tls2.connect(
|
|
37333
36994
|
{ socket: plainSocket, host: this.host, rejectUnauthorized: tlsRejectUnauthorized() }
|
|
37334
36995
|
);
|
|
37335
|
-
await new Promise((
|
|
37336
|
-
socket.once("secureConnect",
|
|
36996
|
+
await new Promise((resolve20, reject) => {
|
|
36997
|
+
socket.once("secureConnect", resolve20);
|
|
37337
36998
|
socket.once("error", reject);
|
|
37338
36999
|
});
|
|
37339
37000
|
const ehlo2 = await sendCommand(socket, `EHLO ${this.host}`);
|
|
37340
37001
|
if (ehlo2.code !== 250) {
|
|
37341
37002
|
socket.destroy();
|
|
37342
|
-
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}
|
|
37003
|
+
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}`, id: null };
|
|
37343
37004
|
}
|
|
37344
37005
|
}
|
|
37345
37006
|
if (this.username && this.password) {
|
|
37346
37007
|
const auth = await sendCommand(socket, "AUTH LOGIN");
|
|
37347
37008
|
if (auth.code !== 334) {
|
|
37348
37009
|
socket.destroy();
|
|
37349
|
-
return { success: false, message: `AUTH LOGIN failed: ${auth.text}
|
|
37010
|
+
return { success: false, message: `AUTH LOGIN failed: ${auth.text}`, id: null };
|
|
37350
37011
|
}
|
|
37351
37012
|
const userResp = await sendCommand(socket, Buffer.from(this.username).toString("base64"));
|
|
37352
37013
|
if (userResp.code !== 334) {
|
|
37353
37014
|
socket.destroy();
|
|
37354
|
-
return { success: false, message: `AUTH username failed: ${userResp.text}
|
|
37015
|
+
return { success: false, message: `AUTH username failed: ${userResp.text}`, id: null };
|
|
37355
37016
|
}
|
|
37356
37017
|
const passResp = await sendCommand(socket, Buffer.from(this.password).toString("base64"));
|
|
37357
37018
|
if (passResp.code !== 235) {
|
|
37358
37019
|
socket.destroy();
|
|
37359
|
-
return { success: false, message: `AUTH password failed: ${passResp.text}
|
|
37020
|
+
return { success: false, message: `AUTH password failed: ${passResp.text}`, id: null };
|
|
37360
37021
|
}
|
|
37361
37022
|
}
|
|
37362
37023
|
const mailFrom = await sendCommand(socket, `MAIL FROM:<${this.fromAddress}>`);
|
|
37363
37024
|
if (mailFrom.code !== 250) {
|
|
37364
37025
|
socket.destroy();
|
|
37365
|
-
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}
|
|
37026
|
+
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}`, id: null };
|
|
37366
37027
|
}
|
|
37367
37028
|
for (const recipient of allRecipients) {
|
|
37368
37029
|
const rcpt = await sendCommand(socket, `RCPT TO:<${recipient}>`);
|
|
37369
37030
|
if (rcpt.code !== 250 && rcpt.code !== 251) {
|
|
37370
37031
|
socket.destroy();
|
|
37371
|
-
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}
|
|
37032
|
+
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}`, id: null };
|
|
37372
37033
|
}
|
|
37373
37034
|
}
|
|
37374
37035
|
const dataCmd = await sendCommand(socket, "DATA");
|
|
37375
37036
|
if (dataCmd.code !== 354) {
|
|
37376
37037
|
socket.destroy();
|
|
37377
|
-
return { success: false, message: `DATA failed: ${dataCmd.text}
|
|
37038
|
+
return { success: false, message: `DATA failed: ${dataCmd.text}`, id: null };
|
|
37378
37039
|
}
|
|
37379
37040
|
const mimeMessage = buildMimeMessage({
|
|
37380
37041
|
from: this.fromAddress,
|
|
@@ -37393,16 +37054,31 @@ var init_messenger = __esm({
|
|
|
37393
37054
|
const endData = await sendCommand(socket, mimeMessage + "\r\n.");
|
|
37394
37055
|
if (endData.code !== 250) {
|
|
37395
37056
|
socket.destroy();
|
|
37396
|
-
return { success: false, message: `Message delivery failed: ${endData.text}
|
|
37057
|
+
return { success: false, message: `Message delivery failed: ${endData.text}`, id: null };
|
|
37397
37058
|
}
|
|
37398
37059
|
await sendCommand(socket, "QUIT");
|
|
37399
37060
|
socket.destroy();
|
|
37400
37061
|
return { success: true, message: "Email sent successfully", id: messageId };
|
|
37401
37062
|
} catch (err) {
|
|
37402
37063
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
37403
|
-
return { success: false, message: `SMTP error: ${errMsg}
|
|
37064
|
+
return { success: false, message: `SMTP error: ${errMsg}`, id: null };
|
|
37404
37065
|
}
|
|
37405
37066
|
}
|
|
37067
|
+
/**
|
|
37068
|
+
* Render a Frond template STRING and send it as an HTML email (G7, parity with
|
|
37069
|
+
* Python's send_template). Extra send() options (cc, bcc, replyTo, attachments,
|
|
37070
|
+
* headers) pass through. If the Frond package cannot be loaded the raw template
|
|
37071
|
+
* is sent verbatim (matches Python's ImportError fallback) rather than failing.
|
|
37072
|
+
*/
|
|
37073
|
+
async sendTemplate(to, subject, template, data = {}, cc, bcc, replyTo, attachments, headers) {
|
|
37074
|
+
let body = template;
|
|
37075
|
+
try {
|
|
37076
|
+
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
37077
|
+
body = new Frond2().renderString(template, data);
|
|
37078
|
+
} catch {
|
|
37079
|
+
}
|
|
37080
|
+
return this.send(to, subject, body, true, void 0, cc, bcc, replyTo, attachments, headers);
|
|
37081
|
+
}
|
|
37406
37082
|
/**
|
|
37407
37083
|
* Test the SMTP connection without sending an email.
|
|
37408
37084
|
*/
|
|
@@ -37411,14 +37087,14 @@ var init_messenger = __esm({
|
|
|
37411
37087
|
let socket;
|
|
37412
37088
|
if (this.port === 465) {
|
|
37413
37089
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37414
|
-
await new Promise((
|
|
37415
|
-
socket.once("secureConnect",
|
|
37090
|
+
await new Promise((resolve20, reject) => {
|
|
37091
|
+
socket.once("secureConnect", resolve20);
|
|
37416
37092
|
socket.once("error", reject);
|
|
37417
37093
|
});
|
|
37418
37094
|
} else {
|
|
37419
37095
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
37420
|
-
await new Promise((
|
|
37421
|
-
socket.once("connect",
|
|
37096
|
+
await new Promise((resolve20, reject) => {
|
|
37097
|
+
socket.once("connect", resolve20);
|
|
37422
37098
|
socket.once("error", reject);
|
|
37423
37099
|
});
|
|
37424
37100
|
}
|
|
@@ -37453,14 +37129,14 @@ var init_messenger = __esm({
|
|
|
37453
37129
|
const useTls = this.imapEncryption === "tls" || this.imapEncryption === "ssl" || this.imapEncryption === "" && this.imapPort === 993;
|
|
37454
37130
|
if (useTls) {
|
|
37455
37131
|
socket = tls2.connect({ host: this.imapHost, port: this.imapPort, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37456
|
-
await new Promise((
|
|
37457
|
-
socket.once("secureConnect",
|
|
37132
|
+
await new Promise((resolve20, reject) => {
|
|
37133
|
+
socket.once("secureConnect", resolve20);
|
|
37458
37134
|
socket.once("error", reject);
|
|
37459
37135
|
});
|
|
37460
37136
|
} else {
|
|
37461
37137
|
socket = net3.createConnection({ host: this.imapHost, port: this.imapPort });
|
|
37462
|
-
await new Promise((
|
|
37463
|
-
socket.once("connect",
|
|
37138
|
+
await new Promise((resolve20, reject) => {
|
|
37139
|
+
socket.once("connect", resolve20);
|
|
37464
37140
|
socket.once("error", reject);
|
|
37465
37141
|
});
|
|
37466
37142
|
}
|
|
@@ -37497,7 +37173,7 @@ var init_messenger = __esm({
|
|
|
37497
37173
|
}
|
|
37498
37174
|
try {
|
|
37499
37175
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37500
|
-
const searchResp = await imapCommand(socket, "SEARCH ALL");
|
|
37176
|
+
const searchResp = await imapCommand(socket, "UID SEARCH ALL");
|
|
37501
37177
|
const uids = parseSearchResponse(searchResp);
|
|
37502
37178
|
if (uids.length === 0) return [];
|
|
37503
37179
|
uids.reverse();
|
|
@@ -37505,8 +37181,8 @@ var init_messenger = __esm({
|
|
|
37505
37181
|
if (selected.length === 0) return [];
|
|
37506
37182
|
const messages = [];
|
|
37507
37183
|
for (const uid of selected) {
|
|
37508
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
37509
|
-
messages.push(
|
|
37184
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
37185
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
37510
37186
|
}
|
|
37511
37187
|
return messages;
|
|
37512
37188
|
} catch (err) {
|
|
@@ -37516,7 +37192,7 @@ var init_messenger = __esm({
|
|
|
37516
37192
|
}
|
|
37517
37193
|
}
|
|
37518
37194
|
/**
|
|
37519
|
-
* Read a single message by
|
|
37195
|
+
* Read a single message by its IMAP UID.
|
|
37520
37196
|
*/
|
|
37521
37197
|
async read(uid, folder = "INBOX") {
|
|
37522
37198
|
let socket;
|
|
@@ -37527,11 +37203,11 @@ var init_messenger = __esm({
|
|
|
37527
37203
|
}
|
|
37528
37204
|
try {
|
|
37529
37205
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37530
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY[])`);
|
|
37206
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY[])`);
|
|
37531
37207
|
if (!/\{\d+\}/.test(fetchResp)) {
|
|
37532
37208
|
return null;
|
|
37533
37209
|
}
|
|
37534
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
37210
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
37535
37211
|
return parseFullMessage(uid, fetchResp);
|
|
37536
37212
|
} catch (err) {
|
|
37537
37213
|
throw imapFail("read", err);
|
|
@@ -37558,14 +37234,14 @@ var init_messenger = __esm({
|
|
|
37558
37234
|
}
|
|
37559
37235
|
try {
|
|
37560
37236
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37561
|
-
const searchResp = await imapCommand(socket, `SEARCH ${query}`);
|
|
37237
|
+
const searchResp = await imapCommand(socket, `UID SEARCH ${query}`);
|
|
37562
37238
|
const uids = parseSearchResponse(searchResp);
|
|
37563
37239
|
if (uids.length === 0) return [];
|
|
37564
37240
|
uids.reverse();
|
|
37565
37241
|
const messages = [];
|
|
37566
37242
|
for (const uid of uids.slice(0, limit)) {
|
|
37567
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
37568
|
-
messages.push(
|
|
37243
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
37244
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
37569
37245
|
}
|
|
37570
37246
|
return messages;
|
|
37571
37247
|
} catch (err) {
|
|
@@ -37575,26 +37251,46 @@ var init_messenger = __esm({
|
|
|
37575
37251
|
}
|
|
37576
37252
|
}
|
|
37577
37253
|
/**
|
|
37578
|
-
* Delete a message by UID.
|
|
37254
|
+
* Delete a message by UID (mark \Deleted, then EXPUNGE).
|
|
37255
|
+
*
|
|
37256
|
+
* `delete` is the one cross-framework name (python/php/ruby/node all spell it
|
|
37257
|
+
* `delete`). `deleteMessage` remains as a DEPRECATED alias for one release.
|
|
37579
37258
|
*/
|
|
37580
|
-
async
|
|
37259
|
+
async delete(uid, folder = "INBOX") {
|
|
37581
37260
|
const socket = await this.imapConnect();
|
|
37582
37261
|
try {
|
|
37583
37262
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37584
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Deleted)`);
|
|
37263
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Deleted)`);
|
|
37585
37264
|
await imapCommand(socket, "EXPUNGE");
|
|
37586
37265
|
} finally {
|
|
37587
37266
|
await this.imapDisconnect(socket);
|
|
37588
37267
|
}
|
|
37589
37268
|
}
|
|
37269
|
+
/** @deprecated Use {@link delete} — kept as an alias for one release (G7). */
|
|
37270
|
+
async deleteMessage(uid, folder = "INBOX") {
|
|
37271
|
+
return this.delete(uid, folder);
|
|
37272
|
+
}
|
|
37590
37273
|
/**
|
|
37591
|
-
* Mark a message as read.
|
|
37274
|
+
* Mark a message as read (+FLAGS \Seen).
|
|
37592
37275
|
*/
|
|
37593
37276
|
async markRead(uid, folder = "INBOX") {
|
|
37594
37277
|
const socket = await this.imapConnect();
|
|
37595
37278
|
try {
|
|
37596
37279
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37597
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
37280
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
37281
|
+
} finally {
|
|
37282
|
+
await this.imapDisconnect(socket);
|
|
37283
|
+
}
|
|
37284
|
+
}
|
|
37285
|
+
/**
|
|
37286
|
+
* Mark a message as unread (-FLAGS \Seen) — the inverse of markRead (G7,
|
|
37287
|
+
* parity with Python's mark_unread).
|
|
37288
|
+
*/
|
|
37289
|
+
async markUnread(uid, folder = "INBOX") {
|
|
37290
|
+
const socket = await this.imapConnect();
|
|
37291
|
+
try {
|
|
37292
|
+
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37293
|
+
await imapCommand(socket, `UID STORE ${uid} -FLAGS (\\Seen)`);
|
|
37598
37294
|
} finally {
|
|
37599
37295
|
await this.imapDisconnect(socket);
|
|
37600
37296
|
}
|
|
@@ -37611,7 +37307,7 @@ var init_messenger = __esm({
|
|
|
37611
37307
|
}
|
|
37612
37308
|
try {
|
|
37613
37309
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37614
|
-
const searchResp = await imapCommand(socket, "SEARCH UNSEEN");
|
|
37310
|
+
const searchResp = await imapCommand(socket, "UID SEARCH UNSEEN");
|
|
37615
37311
|
return parseSearchResponse(searchResp).length;
|
|
37616
37312
|
} catch (err) {
|
|
37617
37313
|
throw imapFail("unread", err);
|
|
@@ -38350,17 +38046,17 @@ var init_htmlElement = __esm({
|
|
|
38350
38046
|
});
|
|
38351
38047
|
|
|
38352
38048
|
// src/ai.ts
|
|
38353
|
-
import { existsSync as
|
|
38049
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16, readFileSync as readFileSync25 } from "node:fs";
|
|
38354
38050
|
import { homedir } from "node:os";
|
|
38355
|
-
import { join as
|
|
38051
|
+
import { join as join28, resolve as resolve19, relative as relative10, dirname as dirname12 } from "node:path";
|
|
38356
38052
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
38357
38053
|
import { execSync, execFileSync as execFileSync3 } from "node:child_process";
|
|
38358
38054
|
import { createInterface } from "node:readline";
|
|
38359
38055
|
function readVersion() {
|
|
38360
38056
|
try {
|
|
38361
|
-
const thisDir =
|
|
38362
|
-
const rootPkg =
|
|
38363
|
-
const pkg = JSON.parse(
|
|
38057
|
+
const thisDir = dirname12(fileURLToPath6(import.meta.url));
|
|
38058
|
+
const rootPkg = resolve19(thisDir, "..", "..", "..", "package.json");
|
|
38059
|
+
const pkg = JSON.parse(readFileSync25(rootPkg, "utf-8"));
|
|
38364
38060
|
return pkg.version ?? "0.0.0";
|
|
38365
38061
|
} catch {
|
|
38366
38062
|
return "0.0.0";
|
|
@@ -38409,8 +38105,8 @@ function downloadSkillsSync(jobs) {
|
|
|
38409
38105
|
function installSkills(root = ".", targets) {
|
|
38410
38106
|
const ref = skillsRef();
|
|
38411
38107
|
const dests = targets ?? [
|
|
38412
|
-
|
|
38413
|
-
|
|
38108
|
+
join28(resolve19(root), ".claude", "skills"),
|
|
38109
|
+
join28(homedir(), ".claude", "skills")
|
|
38414
38110
|
];
|
|
38415
38111
|
const jobs = [];
|
|
38416
38112
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -38428,9 +38124,9 @@ function installSkills(root = ".", targets) {
|
|
|
38428
38124
|
const base = `https://raw.githubusercontent.com/tina4stack/${spec.repo}/${ref}/.claude/skills/${skill}`;
|
|
38429
38125
|
skillMdUrl[skill] = `${base}/SKILL.md`;
|
|
38430
38126
|
for (const dest of dests) {
|
|
38431
|
-
add(`${base}/SKILL.md`,
|
|
38127
|
+
add(`${base}/SKILL.md`, join28(dest, skill, "SKILL.md"));
|
|
38432
38128
|
for (const r of spec.references) {
|
|
38433
|
-
add(`${base}/references/${r}`,
|
|
38129
|
+
add(`${base}/references/${r}`, join28(dest, skill, "references", r));
|
|
38434
38130
|
}
|
|
38435
38131
|
}
|
|
38436
38132
|
}
|
|
@@ -38442,10 +38138,10 @@ function installSkills(root = ".", targets) {
|
|
|
38442
38138
|
return installed;
|
|
38443
38139
|
}
|
|
38444
38140
|
function isInstalled(root, tool) {
|
|
38445
|
-
return
|
|
38141
|
+
return existsSync25(join28(resolve19(root), tool.contextFile));
|
|
38446
38142
|
}
|
|
38447
38143
|
function showMenu(root = ".") {
|
|
38448
|
-
const r =
|
|
38144
|
+
const r = resolve19(root);
|
|
38449
38145
|
console.log("\n Tina4 AI Context Installer\n");
|
|
38450
38146
|
for (let i = 0; i < AI_TOOLS.length; i++) {
|
|
38451
38147
|
const tool = AI_TOOLS[i];
|
|
@@ -38463,16 +38159,16 @@ function showMenu(root = ".") {
|
|
|
38463
38159
|
const tina4AiMarker = tina4AiInstalled ? ` ${GREEN2}[installed]${RESET2}` : "";
|
|
38464
38160
|
console.log(` 8. Install tina4-ai tools (requires Python)${tina4AiMarker}`);
|
|
38465
38161
|
console.log();
|
|
38466
|
-
return new Promise((
|
|
38162
|
+
return new Promise((resolve20) => {
|
|
38467
38163
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
38468
38164
|
rl.question(" Select (comma-separated, or 'all'): ", (answer) => {
|
|
38469
38165
|
rl.close();
|
|
38470
|
-
|
|
38166
|
+
resolve20(answer.trim());
|
|
38471
38167
|
});
|
|
38472
38168
|
});
|
|
38473
38169
|
}
|
|
38474
38170
|
function installSelected(root, selection) {
|
|
38475
|
-
const rootPath =
|
|
38171
|
+
const rootPath = resolve19(root);
|
|
38476
38172
|
const created = [];
|
|
38477
38173
|
let indices;
|
|
38478
38174
|
let doInstallTina4Ai = false;
|
|
@@ -38565,33 +38261,33 @@ function looksLikeOldFrameworkInstall(existing) {
|
|
|
38565
38261
|
function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
38566
38262
|
const block = skillBlock(contextFile);
|
|
38567
38263
|
const [start2, end] = markersFor(contextFile);
|
|
38568
|
-
if (!
|
|
38569
|
-
|
|
38264
|
+
if (!existsSync25(contextPath)) {
|
|
38265
|
+
writeFileSync16(contextPath, frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
38570
38266
|
return "Installed";
|
|
38571
38267
|
}
|
|
38572
|
-
const existing =
|
|
38268
|
+
const existing = readFileSync25(contextPath, "utf-8");
|
|
38573
38269
|
if (hasMarkers(existing, start2, end)) {
|
|
38574
|
-
|
|
38270
|
+
writeFileSync16(contextPath, replaceMarkerBlock(existing, block, start2, end), "utf-8");
|
|
38575
38271
|
return "Refreshed skill block in";
|
|
38576
38272
|
}
|
|
38577
38273
|
if (looksLikeOldFrameworkInstall(existing)) {
|
|
38578
38274
|
const head = existing.replace(/^\s+/, "");
|
|
38579
38275
|
const preamble = existing.slice(0, existing.length - head.length);
|
|
38580
38276
|
const newContent = (preamble.trim() ? preamble.replace(/\s+$/, "") + "\n\n" : "") + frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n";
|
|
38581
|
-
|
|
38277
|
+
writeFileSync16(contextPath, newContent, "utf-8");
|
|
38582
38278
|
return "Migrated (replaced old framework dump in)";
|
|
38583
38279
|
}
|
|
38584
|
-
|
|
38280
|
+
writeFileSync16(contextPath, existing.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
38585
38281
|
return "Appended skill block to";
|
|
38586
38282
|
}
|
|
38587
38283
|
function installForTool(root, tool, context) {
|
|
38588
38284
|
const created = [];
|
|
38589
|
-
const contextPath =
|
|
38285
|
+
const contextPath = join28(root, tool.contextFile);
|
|
38590
38286
|
if (tool.configDir) {
|
|
38591
|
-
|
|
38287
|
+
mkdirSync18(join28(root, tool.configDir), { recursive: true });
|
|
38592
38288
|
}
|
|
38593
|
-
const parentDir =
|
|
38594
|
-
|
|
38289
|
+
const parentDir = dirname12(contextPath);
|
|
38290
|
+
mkdirSync18(parentDir, { recursive: true });
|
|
38595
38291
|
const action = writeOrMerge(contextPath, tool.contextFile, context);
|
|
38596
38292
|
const rel = relative10(root, contextPath);
|
|
38597
38293
|
created.push(rel);
|
|
@@ -38626,7 +38322,7 @@ function installTina4Ai() {
|
|
|
38626
38322
|
function installClaudeSkills(root) {
|
|
38627
38323
|
const created = [];
|
|
38628
38324
|
for (const skill of installSkills(root)) {
|
|
38629
|
-
created.push(
|
|
38325
|
+
created.push(join28(".claude", "skills", skill));
|
|
38630
38326
|
console.log(` ${GREEN2}\u2713${RESET2} Installed .claude/skills/${skill} (project + global)`);
|
|
38631
38327
|
}
|
|
38632
38328
|
return created;
|
|
@@ -38967,11 +38663,11 @@ import { tests, assertEqual, runAll } from "tina4-nodejs";
|
|
|
38967
38663
|
}
|
|
38968
38664
|
function generateClaudeCodeContext() {
|
|
38969
38665
|
try {
|
|
38970
|
-
const thisDir =
|
|
38971
|
-
const repoRoot =
|
|
38972
|
-
const claudeMdPath =
|
|
38973
|
-
if (
|
|
38974
|
-
return
|
|
38666
|
+
const thisDir = dirname12(fileURLToPath6(import.meta.url));
|
|
38667
|
+
const repoRoot = resolve19(thisDir, "..", "..", "..");
|
|
38668
|
+
const claudeMdPath = join28(repoRoot, "CLAUDE.md");
|
|
38669
|
+
if (existsSync25(claudeMdPath)) {
|
|
38670
|
+
return readFileSync25(claudeMdPath, "utf-8");
|
|
38975
38671
|
}
|
|
38976
38672
|
} catch {
|
|
38977
38673
|
}
|
|
@@ -39487,16 +39183,8 @@ var init_rabbitmqBackend = __esm({
|
|
|
39487
39183
|
process.stdout.write(String(msgCount));
|
|
39488
39184
|
closeConnection();
|
|
39489
39185
|
}
|
|
39490
|
-
|
|
39491
|
-
|
|
39492
|
-
const qBuf = Buffer.from(queueName, "utf-8");
|
|
39493
|
-
const purgePayload = Buffer.alloc(4 + qBuf.length);
|
|
39494
|
-
purgePayload.writeUInt16BE(0, 0);
|
|
39495
|
-
purgePayload.writeUInt8(qBuf.length, 2);
|
|
39496
|
-
qBuf.copy(purgePayload, 3);
|
|
39497
|
-
purgePayload.writeUInt8(0, 3 + qBuf.length); // no-wait=false
|
|
39498
|
-
sendMethod(1, 50, 30, purgePayload);
|
|
39499
|
-
}
|
|
39186
|
+
// No "purge" operation: clear()/purge() refuse by name (ADR-0022),
|
|
39187
|
+
// so nothing ever sends Queue.Purge and the drain path is gone.
|
|
39500
39188
|
}
|
|
39501
39189
|
else if (classId === 60 && methodId === 71) {
|
|
39502
39190
|
// Basic.Get-Ok \u2014 message body will follow in content frames
|
|
@@ -39507,11 +39195,6 @@ var init_rabbitmqBackend = __esm({
|
|
|
39507
39195
|
process.stdout.write("__EMPTY__");
|
|
39508
39196
|
closeConnection();
|
|
39509
39197
|
}
|
|
39510
|
-
else if (classId === 50 && methodId === 31) {
|
|
39511
|
-
// Queue.Purge-Ok
|
|
39512
|
-
process.stdout.write("__PURGED__");
|
|
39513
|
-
closeConnection();
|
|
39514
|
-
}
|
|
39515
39198
|
else if (classId === 10 && methodId === 50) {
|
|
39516
39199
|
// Connection.Close (server-initiated, e.g. a channel/protocol error)
|
|
39517
39200
|
// \u2192 send Connection.Close-Ok and exit non-zero so the caller sees the
|
|
@@ -39641,8 +39324,15 @@ var init_rabbitmqBackend = __esm({
|
|
|
39641
39324
|
const num = parseInt(result, 10);
|
|
39642
39325
|
return isNaN(num) ? 0 : num;
|
|
39643
39326
|
}
|
|
39644
|
-
clear(
|
|
39645
|
-
|
|
39327
|
+
clear(_queue) {
|
|
39328
|
+
throw new Error(
|
|
39329
|
+
"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."
|
|
39330
|
+
);
|
|
39331
|
+
}
|
|
39332
|
+
purge(_queue, _status) {
|
|
39333
|
+
throw new Error(
|
|
39334
|
+
"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."
|
|
39335
|
+
);
|
|
39646
39336
|
}
|
|
39647
39337
|
};
|
|
39648
39338
|
}
|
|
@@ -40204,6 +39894,14 @@ var init_kafkaBackend = __esm({
|
|
|
40204
39894
|
return 0;
|
|
40205
39895
|
}
|
|
40206
39896
|
clear(_queue) {
|
|
39897
|
+
throw new Error(
|
|
39898
|
+
"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."
|
|
39899
|
+
);
|
|
39900
|
+
}
|
|
39901
|
+
purge(_queue, _status) {
|
|
39902
|
+
throw new Error(
|
|
39903
|
+
"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."
|
|
39904
|
+
);
|
|
40207
39905
|
}
|
|
40208
39906
|
};
|
|
40209
39907
|
}
|
|
@@ -40963,7 +40661,6 @@ __export(index_exports, {
|
|
|
40963
40661
|
RouteRef: () => RouteRef,
|
|
40964
40662
|
Router: () => Router,
|
|
40965
40663
|
SafeString: () => SafeString2,
|
|
40966
|
-
ScssCompiler: () => ScssCompiler,
|
|
40967
40664
|
SecurityHeadersMiddleware: () => SecurityHeadersMiddleware,
|
|
40968
40665
|
ServiceRunner: () => ServiceRunner,
|
|
40969
40666
|
Session: () => Session,
|
|
@@ -41159,7 +40856,6 @@ var init_index = __esm({
|
|
|
41159
40856
|
init_session();
|
|
41160
40857
|
init_i18n();
|
|
41161
40858
|
init_fakeData();
|
|
41162
|
-
init_scss();
|
|
41163
40859
|
init_queue();
|
|
41164
40860
|
init_job();
|
|
41165
40861
|
init_mqtt();
|
|
@@ -41291,7 +40987,6 @@ export {
|
|
|
41291
40987
|
RouteRef,
|
|
41292
40988
|
Router,
|
|
41293
40989
|
SafeString2 as SafeString,
|
|
41294
|
-
ScssCompiler,
|
|
41295
40990
|
SecurityHeadersMiddleware,
|
|
41296
40991
|
ServiceRunner,
|
|
41297
40992
|
Session,
|