tina4-nodejs 3.13.95 → 3.13.96
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 +1 -2
- package/package.json +2 -1
- package/packages/cli/dist/bin.js +681 -994
- package/packages/core/dist/index.js +561 -875
- 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/request.ts +28 -7
- package/packages/core/src/server.ts +135 -7
- package/packages/orm/dist/index.js +612 -926
- 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/server.d.ts +0 -4
- 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) {
|
|
@@ -23315,14 +23219,14 @@ data: ${channel.buffer.shift()}
|
|
|
23315
23219
|
`;
|
|
23316
23220
|
continue;
|
|
23317
23221
|
}
|
|
23318
|
-
const gotMessage = await new Promise((
|
|
23222
|
+
const gotMessage = await new Promise((resolve20) => {
|
|
23319
23223
|
const timer = setTimeout(() => {
|
|
23320
23224
|
channel.wake = null;
|
|
23321
|
-
|
|
23225
|
+
resolve20(false);
|
|
23322
23226
|
}, keepaliveMs);
|
|
23323
23227
|
channel.wake = () => {
|
|
23324
23228
|
clearTimeout(timer);
|
|
23325
|
-
|
|
23229
|
+
resolve20(true);
|
|
23326
23230
|
};
|
|
23327
23231
|
});
|
|
23328
23232
|
if (!gotMessage) yield `: keep-alive
|
|
@@ -25837,7 +25741,7 @@ var init_websocket = __esm({
|
|
|
25837
25741
|
* Start the WebSocket server.
|
|
25838
25742
|
*/
|
|
25839
25743
|
async start() {
|
|
25840
|
-
return new Promise((
|
|
25744
|
+
return new Promise((resolve20, reject) => {
|
|
25841
25745
|
this.server = createServer((req2, res) => {
|
|
25842
25746
|
res.writeHead(426, { "Content-Type": "text/plain" });
|
|
25843
25747
|
res.end("Upgrade Required");
|
|
@@ -25847,7 +25751,7 @@ var init_websocket = __esm({
|
|
|
25847
25751
|
});
|
|
25848
25752
|
this.server.listen(this.port, () => {
|
|
25849
25753
|
this.startIdleReaper();
|
|
25850
|
-
|
|
25754
|
+
resolve20();
|
|
25851
25755
|
});
|
|
25852
25756
|
this.server.on("error", (err) => {
|
|
25853
25757
|
this.emit("error", err);
|
|
@@ -26411,7 +26315,7 @@ var init_websocket = __esm({
|
|
|
26411
26315
|
client.trackerId = this.onAdd(socket.remoteAddress ?? "unknown", "/__dev_reload");
|
|
26412
26316
|
}
|
|
26413
26317
|
this.clients.add(client);
|
|
26414
|
-
const
|
|
26318
|
+
const cleanup = () => {
|
|
26415
26319
|
if (!this.clients.has(client)) return;
|
|
26416
26320
|
this.clients.delete(client);
|
|
26417
26321
|
if (client.trackerId && this.onRemove) this.onRemove(client.trackerId);
|
|
@@ -26434,13 +26338,13 @@ var init_websocket = __esm({
|
|
|
26434
26338
|
socket.end();
|
|
26435
26339
|
} catch {
|
|
26436
26340
|
}
|
|
26437
|
-
|
|
26341
|
+
cleanup();
|
|
26438
26342
|
return;
|
|
26439
26343
|
}
|
|
26440
26344
|
}
|
|
26441
26345
|
});
|
|
26442
|
-
socket.on("close",
|
|
26443
|
-
socket.on("error",
|
|
26346
|
+
socket.on("close", cleanup);
|
|
26347
|
+
socket.on("error", cleanup);
|
|
26444
26348
|
return true;
|
|
26445
26349
|
}
|
|
26446
26350
|
/**
|
|
@@ -27958,7 +27862,7 @@ var init_queue = __esm({
|
|
|
27958
27862
|
const jobs = this.popBatch(resolvedBatchSize);
|
|
27959
27863
|
if (jobs.length === 0) {
|
|
27960
27864
|
if (resolvedPollInterval <= 0) break;
|
|
27961
|
-
await new Promise((
|
|
27865
|
+
await new Promise((resolve20) => setTimeout(resolve20, resolvedPollInterval));
|
|
27962
27866
|
continue;
|
|
27963
27867
|
}
|
|
27964
27868
|
yield jobs;
|
|
@@ -27968,7 +27872,7 @@ var init_queue = __esm({
|
|
|
27968
27872
|
const raw = this.pop();
|
|
27969
27873
|
if (raw === null) {
|
|
27970
27874
|
if (resolvedPollInterval <= 0) break;
|
|
27971
|
-
await new Promise((
|
|
27875
|
+
await new Promise((resolve20) => setTimeout(resolve20, resolvedPollInterval));
|
|
27972
27876
|
continue;
|
|
27973
27877
|
}
|
|
27974
27878
|
yield createJob(raw, this);
|
|
@@ -32488,23 +32392,23 @@ var init_devAdmin = __esm({
|
|
|
32488
32392
|
});
|
|
32489
32393
|
};
|
|
32490
32394
|
handleDevAdminJs = async (_req, res) => {
|
|
32491
|
-
const { readFileSync:
|
|
32492
|
-
const { dirname:
|
|
32395
|
+
const { readFileSync: readFileSync26, existsSync: existsSync26 } = await import("node:fs");
|
|
32396
|
+
const { dirname: dirname13, join: join29, resolve: resolve20 } = await import("node:path");
|
|
32493
32397
|
const { fileURLToPath: fileURLToPath7 } = await import("node:url");
|
|
32494
|
-
const dir =
|
|
32398
|
+
const dir = dirname13(fileURLToPath7(import.meta.url));
|
|
32495
32399
|
const candidates = [
|
|
32496
|
-
|
|
32400
|
+
join29(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
32497
32401
|
// src/../public/js/
|
|
32498
|
-
|
|
32402
|
+
join29(dir, "..", "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
32499
32403
|
// deeper nesting
|
|
32500
|
-
|
|
32501
|
-
|
|
32404
|
+
resolve20(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin.min.js"),
|
|
32405
|
+
resolve20(process.cwd(), "public", "js", "tina4-dev-admin.min.js")
|
|
32502
32406
|
// project public/
|
|
32503
32407
|
];
|
|
32504
32408
|
for (const jsPath of candidates) {
|
|
32505
|
-
if (
|
|
32409
|
+
if (existsSync26(jsPath)) {
|
|
32506
32410
|
try {
|
|
32507
|
-
const content =
|
|
32411
|
+
const content = readFileSync26(jsPath, "utf-8");
|
|
32508
32412
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
32509
32413
|
res.raw.end(content);
|
|
32510
32414
|
return;
|
|
@@ -32965,8 +32869,12 @@ function sanitizeSecurity(reqs, schemes) {
|
|
|
32965
32869
|
function generate(routes, models = []) {
|
|
32966
32870
|
const info = {
|
|
32967
32871
|
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
32968
|
-
version
|
|
32969
|
-
|
|
32872
|
+
// The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
|
|
32873
|
+
// 0.0.1). description defaults to the empty string, not a canned sentence.
|
|
32874
|
+
// Both are the settled cross-framework defaults (parity with the Python
|
|
32875
|
+
// master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
|
|
32876
|
+
version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
|
|
32877
|
+
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? ""
|
|
32970
32878
|
};
|
|
32971
32879
|
const contactEmail = (process.env.TINA4_SWAGGER_CONTACT_EMAIL ?? "").trim();
|
|
32972
32880
|
const contactName = (process.env.TINA4_SWAGGER_CONTACT_TEAM ?? "").trim();
|
|
@@ -32999,9 +32907,11 @@ function generate(routes, models = []) {
|
|
|
32999
32907
|
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
33000
32908
|
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
33001
32909
|
const refSchemas = /* @__PURE__ */ new Set();
|
|
32910
|
+
const tableToSchema = /* @__PURE__ */ new Map();
|
|
33002
32911
|
for (const model of models) {
|
|
33003
|
-
const
|
|
33004
|
-
|
|
32912
|
+
const schemaKey = schemaNameForModel(model);
|
|
32913
|
+
tableToSchema.set(model.tableName, schemaKey);
|
|
32914
|
+
spec.components.schemas[schemaKey] = modelToSchema(model);
|
|
33005
32915
|
}
|
|
33006
32916
|
const usedTags = [];
|
|
33007
32917
|
const seenIds = /* @__PURE__ */ new Set();
|
|
@@ -33028,11 +32938,11 @@ function generate(routes, models = []) {
|
|
|
33028
32938
|
if (route.meta?.deprecated) operation.deprecated = true;
|
|
33029
32939
|
const pathParams = extractPathParams(route.pattern);
|
|
33030
32940
|
if (pathParams.length > 0) {
|
|
33031
|
-
operation.parameters = pathParams.map((name) => ({
|
|
32941
|
+
operation.parameters = pathParams.map(({ name, schema }) => ({
|
|
33032
32942
|
name,
|
|
33033
32943
|
in: "path",
|
|
33034
32944
|
required: true,
|
|
33035
|
-
schema
|
|
32945
|
+
schema
|
|
33036
32946
|
}));
|
|
33037
32947
|
}
|
|
33038
32948
|
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
@@ -33058,19 +32968,20 @@ function generate(routes, models = []) {
|
|
|
33058
32968
|
};
|
|
33059
32969
|
} else if (method === "post" || method === "put") {
|
|
33060
32970
|
const modelName = inferModelFromPath(route.pattern);
|
|
33061
|
-
|
|
33062
|
-
|
|
33063
|
-
|
|
33064
|
-
};
|
|
32971
|
+
const schemaKey = modelName ? tableToSchema.get(modelName) : void 0;
|
|
32972
|
+
if (schemaKey) {
|
|
32973
|
+
const sref = `#/components/schemas/${schemaKey}`;
|
|
32974
|
+
const media = { schema: { $ref: sref } };
|
|
33065
32975
|
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
33066
32976
|
operation.requestBody = {
|
|
33067
32977
|
required: true,
|
|
33068
32978
|
content: { "application/json": media }
|
|
33069
32979
|
};
|
|
33070
|
-
|
|
33071
|
-
|
|
33072
|
-
|
|
33073
|
-
|
|
32980
|
+
if (route.meta?.responses === void 0) {
|
|
32981
|
+
operation.responses = {
|
|
32982
|
+
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
32983
|
+
};
|
|
32984
|
+
}
|
|
33074
32985
|
} else if (route.meta?.example !== void 0) {
|
|
33075
32986
|
operation.requestBody = {
|
|
33076
32987
|
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
@@ -33157,6 +33068,21 @@ function resolveServers() {
|
|
|
33157
33068
|
const dev = (process.env.SWAGGER_DEV_URL ?? "").trim();
|
|
33158
33069
|
return dev.length > 0 ? [{ url: dev }] : [{ url: "/" }];
|
|
33159
33070
|
}
|
|
33071
|
+
function schemaNameForModel(model) {
|
|
33072
|
+
const explicit = model.className?.trim();
|
|
33073
|
+
if (explicit) return explicit;
|
|
33074
|
+
return deriveClassName(model.tableName);
|
|
33075
|
+
}
|
|
33076
|
+
function deriveClassName(tableName) {
|
|
33077
|
+
return singularize(tableName).split(/[_\s-]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("") || tableName;
|
|
33078
|
+
}
|
|
33079
|
+
function singularize(word) {
|
|
33080
|
+
if (/ies$/i.test(word) && word.length > 3) return word.slice(0, -3) + "y";
|
|
33081
|
+
if (/(ses|xes|zes|ches|shes)$/i.test(word)) return word.slice(0, -2);
|
|
33082
|
+
if (/ss$/i.test(word)) return word;
|
|
33083
|
+
if (/s$/i.test(word) && word.length > 1) return word.slice(0, -1);
|
|
33084
|
+
return word;
|
|
33085
|
+
}
|
|
33160
33086
|
function modelToSchema(model) {
|
|
33161
33087
|
const properties = {};
|
|
33162
33088
|
const required = [];
|
|
@@ -33230,15 +33156,35 @@ function inferSchema(value) {
|
|
|
33230
33156
|
if (typeof value === "number") return { type: Number.isInteger(value) ? "integer" : "number" };
|
|
33231
33157
|
return { type: "string" };
|
|
33232
33158
|
}
|
|
33159
|
+
function segmentParam(segment) {
|
|
33160
|
+
if (segment.startsWith("{") && segment.endsWith("}")) {
|
|
33161
|
+
const inner = segment.slice(1, -1);
|
|
33162
|
+
if (inner.startsWith("...")) return { name: inner.slice(3), type: "string" };
|
|
33163
|
+
const colon = inner.indexOf(":");
|
|
33164
|
+
if (colon >= 0) return { name: inner.slice(0, colon), type: inner.slice(colon + 1) };
|
|
33165
|
+
return { name: inner, type: "string" };
|
|
33166
|
+
}
|
|
33167
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
33168
|
+
const inner = segment.slice(1, -1);
|
|
33169
|
+
return { name: inner.startsWith("...") ? inner.slice(3) : inner, type: "string" };
|
|
33170
|
+
}
|
|
33171
|
+
if (segment.startsWith(":") && segment.length > 1) {
|
|
33172
|
+
return { name: segment.slice(1), type: "string" };
|
|
33173
|
+
}
|
|
33174
|
+
return null;
|
|
33175
|
+
}
|
|
33233
33176
|
function patternToOpenAPI(pattern) {
|
|
33234
|
-
return pattern.
|
|
33177
|
+
return pattern.split("/").map((segment) => {
|
|
33178
|
+
const p = segmentParam(segment);
|
|
33179
|
+
return p ? `{${p.name}}` : segment;
|
|
33180
|
+
}).join("/");
|
|
33235
33181
|
}
|
|
33236
33182
|
function extractPathParams(pattern) {
|
|
33237
33183
|
const params = [];
|
|
33238
|
-
const
|
|
33239
|
-
|
|
33240
|
-
|
|
33241
|
-
params.push(
|
|
33184
|
+
for (const segment of pattern.split("/")) {
|
|
33185
|
+
const p = segmentParam(segment);
|
|
33186
|
+
if (!p) continue;
|
|
33187
|
+
params.push({ name: p.name, schema: { ...PARAM_TYPE_SCHEMA[p.type] ?? { type: "string" } } });
|
|
33242
33188
|
}
|
|
33243
33189
|
return params;
|
|
33244
33190
|
}
|
|
@@ -33260,8 +33206,12 @@ function inferModelFromPath(pattern) {
|
|
|
33260
33206
|
if (rest.length === 1 && /^[[{]\.{0,3}\w+[\]}]$/.test(rest[0])) return candidate;
|
|
33261
33207
|
return null;
|
|
33262
33208
|
}
|
|
33209
|
+
function operationIdBase(method, openApiPath) {
|
|
33210
|
+
const clean = openApiPath.replace(/^\/+|\/+$/g, "").replace(/\//g, "_").replace(/\.\.\./g, "").replace(/[{}]/g, "").replace(/\*/g, "wildcard");
|
|
33211
|
+
return clean ? `${method}_${clean}` : method;
|
|
33212
|
+
}
|
|
33263
33213
|
function uniqueOperationId(method, openApiPath, seen) {
|
|
33264
|
-
const base = (method
|
|
33214
|
+
const base = operationIdBase(method, openApiPath);
|
|
33265
33215
|
let oid = base;
|
|
33266
33216
|
let n = 2;
|
|
33267
33217
|
while (seen.has(oid)) {
|
|
@@ -33271,13 +33221,25 @@ function uniqueOperationId(method, openApiPath, seen) {
|
|
|
33271
33221
|
seen.add(oid);
|
|
33272
33222
|
return oid;
|
|
33273
33223
|
}
|
|
33274
|
-
var WRITE_METHODS, registeredSchemes, registeredSchemas;
|
|
33224
|
+
var WRITE_METHODS, registeredSchemes, registeredSchemas, PARAM_TYPE_SCHEMA;
|
|
33275
33225
|
var init_generator = __esm({
|
|
33276
33226
|
"../swagger/src/generator.ts"() {
|
|
33277
33227
|
"use strict";
|
|
33278
33228
|
WRITE_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
|
|
33279
33229
|
registeredSchemes = {};
|
|
33280
33230
|
registeredSchemas = {};
|
|
33231
|
+
PARAM_TYPE_SCHEMA = {
|
|
33232
|
+
int: { type: "integer" },
|
|
33233
|
+
integer: { type: "integer" },
|
|
33234
|
+
float: { type: "number" },
|
|
33235
|
+
number: { type: "number" },
|
|
33236
|
+
uuid: { type: "string", format: "uuid" },
|
|
33237
|
+
slug: { type: "string", pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
|
|
33238
|
+
alpha: { type: "string", pattern: "^[A-Za-z]+$" },
|
|
33239
|
+
alnum: { type: "string", pattern: "^[A-Za-z0-9]+$" },
|
|
33240
|
+
path: { type: "string" },
|
|
33241
|
+
string: { type: "string" }
|
|
33242
|
+
};
|
|
33281
33243
|
}
|
|
33282
33244
|
});
|
|
33283
33245
|
|
|
@@ -33597,10 +33559,29 @@ function openBrowser(url) {
|
|
|
33597
33559
|
}, 2e3);
|
|
33598
33560
|
}
|
|
33599
33561
|
function resolvePortAndHost(config) {
|
|
33600
|
-
const
|
|
33562
|
+
const tina4Port = process.env.TINA4_PORT;
|
|
33563
|
+
const legacyPort = process.env.PORT;
|
|
33564
|
+
let port;
|
|
33565
|
+
if (config?.port !== void 0) {
|
|
33566
|
+
port = config.port;
|
|
33567
|
+
} else if (tina4Port && /^\d+$/.test(tina4Port)) {
|
|
33568
|
+
port = parseInt(tina4Port, 10);
|
|
33569
|
+
} else if (legacyPort && /^\d+$/.test(legacyPort)) {
|
|
33570
|
+
port = parseInt(legacyPort, 10);
|
|
33571
|
+
warnDeprecatedPort(port);
|
|
33572
|
+
} else {
|
|
33573
|
+
port = 7148;
|
|
33574
|
+
}
|
|
33601
33575
|
const host = config?.host ?? process.env.TINA4_HOST ?? process.env.HOST ?? "0.0.0.0";
|
|
33602
33576
|
return { port, host };
|
|
33603
33577
|
}
|
|
33578
|
+
function warnDeprecatedPort(port) {
|
|
33579
|
+
if (portDeprecationWarned) return;
|
|
33580
|
+
portDeprecationWarned = true;
|
|
33581
|
+
Log.warning(
|
|
33582
|
+
`PORT is deprecated and will be removed in 3.14 - use TINA4_PORT instead (binding port ${port} from PORT)`
|
|
33583
|
+
);
|
|
33584
|
+
}
|
|
33604
33585
|
function isBannerSuppressed() {
|
|
33605
33586
|
return isTruthy(process.env.TINA4_SUPPRESS);
|
|
33606
33587
|
}
|
|
@@ -33872,6 +33853,29 @@ function deployGallery(name) {
|
|
|
33872
33853
|
</body>
|
|
33873
33854
|
</html>`;
|
|
33874
33855
|
}
|
|
33856
|
+
function startLoopWatchdog() {
|
|
33857
|
+
const raw = (process.env.TINA4_LOOP_LAG_WARN_MS ?? "").trim();
|
|
33858
|
+
const threshold = /^\d+$/.test(raw) ? parseInt(raw, 10) : 250;
|
|
33859
|
+
if (threshold <= 0) {
|
|
33860
|
+
return { stop: () => {
|
|
33861
|
+
} };
|
|
33862
|
+
}
|
|
33863
|
+
let last = Date.now();
|
|
33864
|
+
let warned = 0;
|
|
33865
|
+
const timer = setInterval(() => {
|
|
33866
|
+
const now = Date.now();
|
|
33867
|
+
const lag = now - last - LOOP_WATCHDOG_TICK_MS;
|
|
33868
|
+
last = now;
|
|
33869
|
+
if (lag < threshold) return;
|
|
33870
|
+
warned++;
|
|
33871
|
+
if (warned > 5 && warned % 20 !== 0) return;
|
|
33872
|
+
Log.warning(
|
|
33873
|
+
`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.`
|
|
33874
|
+
);
|
|
33875
|
+
}, LOOP_WATCHDOG_TICK_MS);
|
|
33876
|
+
timer.unref();
|
|
33877
|
+
return { stop: () => clearInterval(timer) };
|
|
33878
|
+
}
|
|
33875
33879
|
async function start(config) {
|
|
33876
33880
|
const isManaged = process.argv.includes("--managed");
|
|
33877
33881
|
if (!isManaged && process.env.TINA4_OVERRIDE_CLIENT !== "true") {
|
|
@@ -34121,7 +34125,9 @@ async function startServer(config) {
|
|
|
34121
34125
|
const resolved = resolvePortAndHost(config);
|
|
34122
34126
|
const host = resolved.host;
|
|
34123
34127
|
let port = resolved.port;
|
|
34124
|
-
|
|
34128
|
+
if (!cluster.isWorker) {
|
|
34129
|
+
port = findAvailablePort(port);
|
|
34130
|
+
}
|
|
34125
34131
|
const isProduction = (process.env.TINA4_PRODUCTION ?? "").toLowerCase() === "true";
|
|
34126
34132
|
if (cluster.isPrimary && isProduction) {
|
|
34127
34133
|
const numCPUs = os2.cpus().length;
|
|
@@ -34337,7 +34343,20 @@ ${reset2}
|
|
|
34337
34343
|
await sessionAutoStart(rawReq, rawRes, req2);
|
|
34338
34344
|
await middleware.run(req2, res);
|
|
34339
34345
|
if (res.raw.writableEnded) return;
|
|
34340
|
-
|
|
34346
|
+
try {
|
|
34347
|
+
await req2.parseBody();
|
|
34348
|
+
} catch (err) {
|
|
34349
|
+
const status2 = err?.statusCode;
|
|
34350
|
+
if (typeof status2 === "number" && status2 >= 400 && status2 < 500) {
|
|
34351
|
+
if (!rawRes.writableEnded) {
|
|
34352
|
+
rawRes.statusCode = status2;
|
|
34353
|
+
rawRes.setHeader("content-type", "application/json");
|
|
34354
|
+
rawRes.end(JSON.stringify({ error: err.message }));
|
|
34355
|
+
}
|
|
34356
|
+
return;
|
|
34357
|
+
}
|
|
34358
|
+
throw err;
|
|
34359
|
+
}
|
|
34341
34360
|
const pathname = req2.path;
|
|
34342
34361
|
const reqStartTime = DevAdmin.isEnabled() ? Date.now() : 0;
|
|
34343
34362
|
const matchedPattern = { value: "" };
|
|
@@ -34536,8 +34555,10 @@ ${reset2}
|
|
|
34536
34555
|
};
|
|
34537
34556
|
process.on("SIGTERM", onSigterm);
|
|
34538
34557
|
process.on("SIGINT", onSigint);
|
|
34558
|
+
const loopWatchdog = startLoopWatchdog();
|
|
34539
34559
|
resolvePromise({
|
|
34540
34560
|
close: () => {
|
|
34561
|
+
loopWatchdog.stop();
|
|
34541
34562
|
process.off("SIGTERM", onSigterm);
|
|
34542
34563
|
process.off("SIGINT", onSigint);
|
|
34543
34564
|
stopAllBackgroundTasks();
|
|
@@ -34552,7 +34573,7 @@ ${reset2}
|
|
|
34552
34573
|
});
|
|
34553
34574
|
});
|
|
34554
34575
|
}
|
|
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;
|
|
34576
|
+
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
34577
|
var init_server = __esm({
|
|
34557
34578
|
"src/server.ts"() {
|
|
34558
34579
|
"use strict";
|
|
@@ -34605,6 +34626,7 @@ var init_server = __esm({
|
|
|
34605
34626
|
SWAGGER_VERSION: "TINA4_SWAGGER_VERSION",
|
|
34606
34627
|
ORM_PLURAL_TABLE_NAMES: "TINA4_ORM_PLURAL_TABLE_NAMES"
|
|
34607
34628
|
};
|
|
34629
|
+
portDeprecationWarned = false;
|
|
34608
34630
|
TEMPLATE_PAGES_DIR = "pages";
|
|
34609
34631
|
HTTP_REASON_PHRASES = {
|
|
34610
34632
|
100: "Continue",
|
|
@@ -34641,6 +34663,7 @@ var init_server = __esm({
|
|
|
34641
34663
|
templateCache = null;
|
|
34642
34664
|
_dispatchFn = null;
|
|
34643
34665
|
_serverHandle = null;
|
|
34666
|
+
LOOP_WATCHDOG_TICK_MS = 100;
|
|
34644
34667
|
FALLBACK_STAGES = [
|
|
34645
34668
|
serveTemplateFallback,
|
|
34646
34669
|
serveLandingPage,
|
|
@@ -34728,433 +34751,6 @@ var init_env = __esm({
|
|
|
34728
34751
|
}
|
|
34729
34752
|
});
|
|
34730
34753
|
|
|
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
34754
|
// src/mqttMessage.ts
|
|
35159
34755
|
var MqttMessage;
|
|
35160
34756
|
var init_mqttMessage = __esm({
|
|
@@ -35228,7 +34824,7 @@ var init_mqttMessage = __esm({
|
|
|
35228
34824
|
import net2 from "node:net";
|
|
35229
34825
|
import tls from "node:tls";
|
|
35230
34826
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
35231
|
-
import { existsSync as
|
|
34827
|
+
import { existsSync as existsSync24, readFileSync as readFileSync23 } from "node:fs";
|
|
35232
34828
|
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
34829
|
var init_mqtt = __esm({
|
|
35234
34830
|
"src/mqtt.ts"() {
|
|
@@ -35431,7 +35027,7 @@ var init_mqtt = __esm({
|
|
|
35431
35027
|
*/
|
|
35432
35028
|
async connect() {
|
|
35433
35029
|
this.closeSocket();
|
|
35434
|
-
if (this.secure && this.tlsVerify && this.caFile && !
|
|
35030
|
+
if (this.secure && this.tlsVerify && this.caFile && !existsSync24(this.caFile)) {
|
|
35435
35031
|
throw new MqttError(
|
|
35436
35032
|
`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
35033
|
);
|
|
@@ -35668,7 +35264,7 @@ var init_mqtt = __esm({
|
|
|
35668
35264
|
* a later client.
|
|
35669
35265
|
*/
|
|
35670
35266
|
openSocket() {
|
|
35671
|
-
return new Promise((
|
|
35267
|
+
return new Promise((resolve20, reject) => {
|
|
35672
35268
|
let settled = false;
|
|
35673
35269
|
const settle = (fn) => {
|
|
35674
35270
|
if (settled) return;
|
|
@@ -35693,10 +35289,10 @@ var init_mqtt = __esm({
|
|
|
35693
35289
|
servername: this.host,
|
|
35694
35290
|
rejectUnauthorized: this.tlsVerify
|
|
35695
35291
|
};
|
|
35696
|
-
if (this.tlsVerify && this.caFile) opts.ca =
|
|
35697
|
-
sock = tls.connect(opts, () => settle(() =>
|
|
35292
|
+
if (this.tlsVerify && this.caFile) opts.ca = readFileSync23(this.caFile);
|
|
35293
|
+
sock = tls.connect(opts, () => settle(() => resolve20(sock)));
|
|
35698
35294
|
} else {
|
|
35699
|
-
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() =>
|
|
35295
|
+
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve20(sock)));
|
|
35700
35296
|
}
|
|
35701
35297
|
sock.once("error", (err) => {
|
|
35702
35298
|
settle(() => {
|
|
@@ -35735,13 +35331,13 @@ var init_mqtt = __esm({
|
|
|
35735
35331
|
writePacket(header, body) {
|
|
35736
35332
|
if (this.socket === null) return Promise.reject(new MqttError("not connected to an MQTT broker"));
|
|
35737
35333
|
const packet = Buffer.concat([Buffer.from([header]), _Mqtt.encodeRemainingLength(body.length), body]);
|
|
35738
|
-
return new Promise((
|
|
35334
|
+
return new Promise((resolve20, reject) => {
|
|
35739
35335
|
this.socket.write(packet, (err) => {
|
|
35740
35336
|
if (err) {
|
|
35741
35337
|
reject(new MqttError(`MQTT write failed: ${err.message}`));
|
|
35742
35338
|
} else {
|
|
35743
35339
|
this.lastWriteAt = Date.now();
|
|
35744
|
-
|
|
35340
|
+
resolve20();
|
|
35745
35341
|
}
|
|
35746
35342
|
});
|
|
35747
35343
|
});
|
|
@@ -35774,7 +35370,7 @@ var init_mqtt = __esm({
|
|
|
35774
35370
|
if (this.readBuffer.length >= need) return Promise.resolve(this.take(need));
|
|
35775
35371
|
if (this.socket === null) return Promise.reject(this.socketError ?? new MqttError("not connected to an MQTT broker"));
|
|
35776
35372
|
if (this.socketError !== null) return Promise.reject(this.socketError);
|
|
35777
|
-
return new Promise((
|
|
35373
|
+
return new Promise((resolve20, reject) => {
|
|
35778
35374
|
let timer = null;
|
|
35779
35375
|
if (deadline !== null) {
|
|
35780
35376
|
const remaining = deadline - Date.now();
|
|
@@ -35789,7 +35385,7 @@ var init_mqtt = __esm({
|
|
|
35789
35385
|
}
|
|
35790
35386
|
}, remaining);
|
|
35791
35387
|
}
|
|
35792
|
-
this.waiter = { need, resolve:
|
|
35388
|
+
this.waiter = { need, resolve: resolve20, reject, timer };
|
|
35793
35389
|
this.serviceWaiter();
|
|
35794
35390
|
});
|
|
35795
35391
|
}
|
|
@@ -35913,8 +35509,8 @@ var init_mqtt = __esm({
|
|
|
35913
35509
|
});
|
|
35914
35510
|
|
|
35915
35511
|
// src/service.ts
|
|
35916
|
-
import { readdirSync as
|
|
35917
|
-
import { join as
|
|
35512
|
+
import { readdirSync as readdirSync18, statSync as statSync18, watchFile, unwatchFile } from "node:fs";
|
|
35513
|
+
import { join as join27, extname as extname8 } from "node:path";
|
|
35918
35514
|
import { pathToFileURL } from "node:url";
|
|
35919
35515
|
function matchCronField(field, value) {
|
|
35920
35516
|
if (field === "*") return true;
|
|
@@ -36080,14 +35676,14 @@ var init_service = __esm({
|
|
|
36080
35676
|
const discovered = [];
|
|
36081
35677
|
let entries;
|
|
36082
35678
|
try {
|
|
36083
|
-
entries =
|
|
35679
|
+
entries = readdirSync18(dir);
|
|
36084
35680
|
} catch {
|
|
36085
35681
|
return discovered;
|
|
36086
35682
|
}
|
|
36087
35683
|
for (const entry of entries) {
|
|
36088
35684
|
const ext = extname8(entry);
|
|
36089
35685
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
36090
|
-
const fullPath =
|
|
35686
|
+
const fullPath = join27(dir, entry);
|
|
36091
35687
|
const stat = statSync18(fullPath);
|
|
36092
35688
|
if (!stat.isFile()) continue;
|
|
36093
35689
|
try {
|
|
@@ -36196,14 +35792,14 @@ var init_service = __esm({
|
|
|
36196
35792
|
const dir = serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
36197
35793
|
let entries;
|
|
36198
35794
|
try {
|
|
36199
|
-
entries =
|
|
35795
|
+
entries = readdirSync18(dir);
|
|
36200
35796
|
} catch {
|
|
36201
35797
|
return;
|
|
36202
35798
|
}
|
|
36203
35799
|
for (const entry of entries) {
|
|
36204
35800
|
const ext = extname8(entry);
|
|
36205
35801
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
36206
|
-
const fullPath =
|
|
35802
|
+
const fullPath = join27(dir, entry);
|
|
36207
35803
|
if (watchedFiles.has(fullPath)) continue;
|
|
36208
35804
|
watchedFiles.add(fullPath);
|
|
36209
35805
|
watchFile(fullPath, { interval: 1e3 }, async () => {
|
|
@@ -36247,7 +35843,7 @@ import https from "node:https";
|
|
|
36247
35843
|
import { URL as URL2 } from "node:url";
|
|
36248
35844
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
36249
35845
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
36250
|
-
import { basename as
|
|
35846
|
+
import { basename as basename4 } from "node:path";
|
|
36251
35847
|
import { pipeline } from "node:stream/promises";
|
|
36252
35848
|
function sameOrigin(urlA, urlB) {
|
|
36253
35849
|
try {
|
|
@@ -36537,7 +36133,7 @@ var init_api = __esm({
|
|
|
36537
36133
|
error: err instanceof Error ? err.message : String(err)
|
|
36538
36134
|
};
|
|
36539
36135
|
}
|
|
36540
|
-
uploadName = filename ||
|
|
36136
|
+
uploadName = filename || basename4(filePath);
|
|
36541
36137
|
} else {
|
|
36542
36138
|
return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
|
|
36543
36139
|
}
|
|
@@ -36752,12 +36348,12 @@ var init_api = __esm({
|
|
|
36752
36348
|
* authenticate to.
|
|
36753
36349
|
*/
|
|
36754
36350
|
performRequest(method, url, headers, data, redirectsLeft) {
|
|
36755
|
-
return new Promise((
|
|
36351
|
+
return new Promise((resolve20) => {
|
|
36756
36352
|
let parsed;
|
|
36757
36353
|
try {
|
|
36758
36354
|
parsed = new URL2(url);
|
|
36759
36355
|
} catch (err) {
|
|
36760
|
-
|
|
36356
|
+
resolve20({ kind: "error", error: err instanceof Error ? err.message : String(err) });
|
|
36761
36357
|
return;
|
|
36762
36358
|
}
|
|
36763
36359
|
const isHttps = parsed.protocol === "https:";
|
|
@@ -36782,7 +36378,7 @@ var init_api = __esm({
|
|
|
36782
36378
|
try {
|
|
36783
36379
|
nextUrl = new URL2(location, url).toString();
|
|
36784
36380
|
} catch {
|
|
36785
|
-
|
|
36381
|
+
resolve20({ kind: "response", res });
|
|
36786
36382
|
return;
|
|
36787
36383
|
}
|
|
36788
36384
|
const crossOrigin = !sameOrigin(url, nextUrl);
|
|
@@ -36800,17 +36396,17 @@ var init_api = __esm({
|
|
|
36800
36396
|
deleteHeaderCaseInsensitive(nextHeaders, name);
|
|
36801
36397
|
}
|
|
36802
36398
|
}
|
|
36803
|
-
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(
|
|
36399
|
+
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(resolve20);
|
|
36804
36400
|
return;
|
|
36805
36401
|
}
|
|
36806
|
-
|
|
36402
|
+
resolve20({ kind: "response", res });
|
|
36807
36403
|
});
|
|
36808
36404
|
req2.on("timeout", () => {
|
|
36809
36405
|
req2.destroy();
|
|
36810
|
-
|
|
36406
|
+
resolve20({ kind: "error", error: `Request timed out after ${this.timeout}s` });
|
|
36811
36407
|
});
|
|
36812
36408
|
req2.on("error", (err) => {
|
|
36813
|
-
|
|
36409
|
+
resolve20({ kind: "error", error: err.message });
|
|
36814
36410
|
});
|
|
36815
36411
|
if (data) {
|
|
36816
36412
|
req2.write(data);
|
|
@@ -36820,7 +36416,7 @@ var init_api = __esm({
|
|
|
36820
36416
|
}
|
|
36821
36417
|
/** Buffer a response body, parse JSON if possible, and store cookies. */
|
|
36822
36418
|
readResponse(res) {
|
|
36823
|
-
return new Promise((
|
|
36419
|
+
return new Promise((resolve20) => {
|
|
36824
36420
|
const chunks = [];
|
|
36825
36421
|
res.on("data", (chunk) => {
|
|
36826
36422
|
chunks.push(chunk);
|
|
@@ -36835,7 +36431,7 @@ var init_api = __esm({
|
|
|
36835
36431
|
} catch {
|
|
36836
36432
|
parsed = raw;
|
|
36837
36433
|
}
|
|
36838
|
-
|
|
36434
|
+
resolve20({
|
|
36839
36435
|
http_code: res.statusCode ?? null,
|
|
36840
36436
|
body: parsed,
|
|
36841
36437
|
headers: respHeaders,
|
|
@@ -36843,7 +36439,7 @@ var init_api = __esm({
|
|
|
36843
36439
|
});
|
|
36844
36440
|
});
|
|
36845
36441
|
res.on("error", (err) => {
|
|
36846
|
-
|
|
36442
|
+
resolve20({ http_code: null, body: null, headers: {}, error: err.message });
|
|
36847
36443
|
});
|
|
36848
36444
|
});
|
|
36849
36445
|
}
|
|
@@ -36896,14 +36492,14 @@ var init_api = __esm({
|
|
|
36896
36492
|
// src/messenger.ts
|
|
36897
36493
|
import net3 from "node:net";
|
|
36898
36494
|
import tls2 from "node:tls";
|
|
36899
|
-
import { readFileSync as
|
|
36900
|
-
import { basename as
|
|
36495
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
36496
|
+
import { basename as basename5 } from "node:path";
|
|
36901
36497
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
36902
36498
|
function tlsRejectUnauthorized() {
|
|
36903
36499
|
return !isTruthy(process.env.TINA4_MAIL_TLS_INSECURE);
|
|
36904
36500
|
}
|
|
36905
36501
|
function readResponse(socket) {
|
|
36906
|
-
return new Promise((
|
|
36502
|
+
return new Promise((resolve20, reject) => {
|
|
36907
36503
|
let buffer = "";
|
|
36908
36504
|
const onData = (chunk) => {
|
|
36909
36505
|
buffer += chunk.toString("utf-8");
|
|
@@ -36915,7 +36511,7 @@ function readResponse(socket) {
|
|
|
36915
36511
|
if (line.length >= 4 && line[3] === " ") {
|
|
36916
36512
|
socket.removeListener("data", onData);
|
|
36917
36513
|
socket.removeListener("error", onError);
|
|
36918
|
-
|
|
36514
|
+
resolve20({ code, text: buffer.trim() });
|
|
36919
36515
|
return;
|
|
36920
36516
|
}
|
|
36921
36517
|
}
|
|
@@ -36929,10 +36525,10 @@ function readResponse(socket) {
|
|
|
36929
36525
|
});
|
|
36930
36526
|
}
|
|
36931
36527
|
function sendCommand(socket, command) {
|
|
36932
|
-
return new Promise((
|
|
36528
|
+
return new Promise((resolve20, reject) => {
|
|
36933
36529
|
socket.write(command + "\r\n", "utf-8", (err) => {
|
|
36934
36530
|
if (err) return reject(err);
|
|
36935
|
-
readResponse(socket).then(
|
|
36531
|
+
readResponse(socket).then(resolve20, reject);
|
|
36936
36532
|
});
|
|
36937
36533
|
});
|
|
36938
36534
|
}
|
|
@@ -36988,8 +36584,8 @@ function buildMimeMessage(options) {
|
|
|
36988
36584
|
lines.push(options.body);
|
|
36989
36585
|
}
|
|
36990
36586
|
for (const filePath of options.attachments) {
|
|
36991
|
-
const fileName =
|
|
36992
|
-
const fileData =
|
|
36587
|
+
const fileName = basename5(filePath);
|
|
36588
|
+
const fileData = readFileSync24(filePath);
|
|
36993
36589
|
const base64Data = fileData.toString("base64");
|
|
36994
36590
|
lines.push("");
|
|
36995
36591
|
lines.push(`--${boundary}`);
|
|
@@ -37032,7 +36628,7 @@ function imapQuote(s) {
|
|
|
37032
36628
|
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
37033
36629
|
}
|
|
37034
36630
|
function imapReadLine(socket) {
|
|
37035
|
-
return new Promise((
|
|
36631
|
+
return new Promise((resolve20, reject) => {
|
|
37036
36632
|
let buffer = "";
|
|
37037
36633
|
const onData = (chunk) => {
|
|
37038
36634
|
buffer += chunk.toString("utf-8");
|
|
@@ -37040,7 +36636,7 @@ function imapReadLine(socket) {
|
|
|
37040
36636
|
if (nlIndex !== -1) {
|
|
37041
36637
|
socket.removeListener("data", onData);
|
|
37042
36638
|
socket.removeListener("error", onError);
|
|
37043
|
-
|
|
36639
|
+
resolve20(buffer);
|
|
37044
36640
|
}
|
|
37045
36641
|
};
|
|
37046
36642
|
const onError = (err) => {
|
|
@@ -37052,7 +36648,7 @@ function imapReadLine(socket) {
|
|
|
37052
36648
|
});
|
|
37053
36649
|
}
|
|
37054
36650
|
function imapCommand(socket, command) {
|
|
37055
|
-
return new Promise((
|
|
36651
|
+
return new Promise((resolve20, reject) => {
|
|
37056
36652
|
imapTagCounter++;
|
|
37057
36653
|
const tag = `T${imapTagCounter}`;
|
|
37058
36654
|
const fullCommand = `${tag} ${command}\r
|
|
@@ -37063,7 +36659,7 @@ function imapCommand(socket, command) {
|
|
|
37063
36659
|
if (buffer.includes(`${tag} OK`)) {
|
|
37064
36660
|
socket.removeListener("data", onData);
|
|
37065
36661
|
socket.removeListener("error", onError);
|
|
37066
|
-
|
|
36662
|
+
resolve20(buffer);
|
|
37067
36663
|
return;
|
|
37068
36664
|
}
|
|
37069
36665
|
if (buffer.includes(`${tag} NO`) || buffer.includes(`${tag} BAD`)) {
|
|
@@ -37092,91 +36688,149 @@ function parseSearchResponse(response) {
|
|
|
37092
36688
|
if (!match) return [];
|
|
37093
36689
|
return match[1].trim().split(/\s+/).filter((s) => /^\d+$/.test(s));
|
|
37094
36690
|
}
|
|
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
|
-
};
|
|
36691
|
+
function extractRawMessage(response) {
|
|
36692
|
+
const m = response.match(/\{(\d+)\}\r\n/);
|
|
36693
|
+
if (!m) return response;
|
|
36694
|
+
const start2 = (m.index ?? 0) + m[0].length;
|
|
36695
|
+
return response.slice(start2, start2 + parseInt(m[1], 10));
|
|
37123
36696
|
}
|
|
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) : "";
|
|
36697
|
+
function parseMimeHeaders(section) {
|
|
37130
36698
|
const headers = {};
|
|
37131
|
-
const headerLines = headerSection.split(/\r\n/);
|
|
37132
36699
|
let currentKey = "";
|
|
37133
|
-
for (const line of
|
|
36700
|
+
for (const line of section.split(/\r\n/)) {
|
|
37134
36701
|
if (/^\s/.test(line) && currentKey) {
|
|
37135
36702
|
headers[currentKey] += " " + line.trim();
|
|
37136
36703
|
} else {
|
|
37137
|
-
const
|
|
37138
|
-
if (
|
|
37139
|
-
currentKey = line.substring(0,
|
|
37140
|
-
headers[currentKey] = line.substring(
|
|
36704
|
+
const idx = line.indexOf(":");
|
|
36705
|
+
if (idx > 0) {
|
|
36706
|
+
currentKey = line.substring(0, idx).trim().toLowerCase();
|
|
36707
|
+
headers[currentKey] = line.substring(idx + 1).trim();
|
|
36708
|
+
}
|
|
36709
|
+
}
|
|
36710
|
+
}
|
|
36711
|
+
return headers;
|
|
36712
|
+
}
|
|
36713
|
+
function decodeTransfer(body, encoding) {
|
|
36714
|
+
const enc = encoding.toLowerCase().trim();
|
|
36715
|
+
if (enc === "base64") {
|
|
36716
|
+
try {
|
|
36717
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64").toString("utf-8");
|
|
36718
|
+
} catch {
|
|
36719
|
+
return body;
|
|
36720
|
+
}
|
|
36721
|
+
}
|
|
36722
|
+
if (enc === "quoted-printable") {
|
|
36723
|
+
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)));
|
|
36724
|
+
}
|
|
36725
|
+
return body;
|
|
36726
|
+
}
|
|
36727
|
+
function decodeAttachmentBytes(body, encoding) {
|
|
36728
|
+
const enc = encoding.toLowerCase().trim();
|
|
36729
|
+
if (enc === "base64") {
|
|
36730
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64");
|
|
36731
|
+
}
|
|
36732
|
+
const trimmed = body.replace(/\r\n$/, "");
|
|
36733
|
+
if (enc === "quoted-printable") {
|
|
36734
|
+
const collapsed = trimmed.replace(/=\r?\n/g, "");
|
|
36735
|
+
const bytes = [];
|
|
36736
|
+
for (let i = 0; i < collapsed.length; i++) {
|
|
36737
|
+
const hex = collapsed.substring(i + 1, i + 3);
|
|
36738
|
+
if (collapsed[i] === "=" && /^[0-9A-Fa-f]{2}$/.test(hex)) {
|
|
36739
|
+
bytes.push(parseInt(hex, 16));
|
|
36740
|
+
i += 2;
|
|
36741
|
+
} else {
|
|
36742
|
+
bytes.push(collapsed.charCodeAt(i) & 255);
|
|
37141
36743
|
}
|
|
37142
36744
|
}
|
|
36745
|
+
return Buffer.from(bytes);
|
|
37143
36746
|
}
|
|
36747
|
+
return Buffer.from(trimmed, "utf-8");
|
|
36748
|
+
}
|
|
36749
|
+
function attachmentFilename(disposition, contentType) {
|
|
36750
|
+
const d = disposition.match(/filename="?([^";\r\n]+)"?/i);
|
|
36751
|
+
if (d) return d[1].trim();
|
|
36752
|
+
const c = contentType.match(/name="?([^";\r\n]+)"?/i);
|
|
36753
|
+
if (c) return c[1].trim();
|
|
36754
|
+
return "attachment";
|
|
36755
|
+
}
|
|
36756
|
+
function makeSnippet(bodyText, bodyHtml) {
|
|
36757
|
+
return (bodyText || bodyHtml || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
36758
|
+
}
|
|
36759
|
+
function toIsoDate(raw) {
|
|
36760
|
+
if (!raw) return "";
|
|
36761
|
+
const d = new Date(raw);
|
|
36762
|
+
return Number.isNaN(d.getTime()) ? raw : d.toISOString();
|
|
36763
|
+
}
|
|
36764
|
+
function parseMessage(response) {
|
|
36765
|
+
const raw = extractRawMessage(response);
|
|
36766
|
+
const headerEnd = raw.indexOf("\r\n\r\n");
|
|
36767
|
+
const headerSection = headerEnd >= 0 ? raw.substring(0, headerEnd) : raw;
|
|
36768
|
+
const bodySection = headerEnd >= 0 ? raw.substring(headerEnd + 4) : "";
|
|
36769
|
+
const headers = parseMimeHeaders(headerSection);
|
|
37144
36770
|
const contentType = headers["content-type"] ?? "text/plain";
|
|
37145
36771
|
let bodyText = "";
|
|
37146
36772
|
let bodyHtml = "";
|
|
36773
|
+
const attachments = [];
|
|
37147
36774
|
if (contentType.includes("multipart")) {
|
|
37148
36775
|
const boundaryMatch = contentType.match(/boundary="?([^";\s]+)"?/);
|
|
37149
36776
|
if (boundaryMatch) {
|
|
37150
|
-
const boundary = boundaryMatch[1];
|
|
37151
|
-
const
|
|
37152
|
-
|
|
37153
|
-
if (
|
|
37154
|
-
const
|
|
37155
|
-
|
|
37156
|
-
const
|
|
37157
|
-
|
|
37158
|
-
|
|
37159
|
-
|
|
37160
|
-
|
|
36777
|
+
const boundary = "--" + boundaryMatch[1];
|
|
36778
|
+
for (const part of bodySection.split(boundary)) {
|
|
36779
|
+
const trimmed = part.trim();
|
|
36780
|
+
if (trimmed === "" || trimmed === "--") continue;
|
|
36781
|
+
const pEnd = part.indexOf("\r\n\r\n");
|
|
36782
|
+
if (pEnd < 0) continue;
|
|
36783
|
+
const pHeaders = parseMimeHeaders(part.substring(0, pEnd));
|
|
36784
|
+
const pBody = part.substring(pEnd + 4);
|
|
36785
|
+
const cte = pHeaders["content-transfer-encoding"] ?? "";
|
|
36786
|
+
const pType = pHeaders["content-type"] ?? "text/plain";
|
|
36787
|
+
const disposition = pHeaders["content-disposition"] ?? "";
|
|
36788
|
+
if (/attachment/i.test(disposition)) {
|
|
36789
|
+
const content = decodeAttachmentBytes(pBody, cte);
|
|
36790
|
+
attachments.push({
|
|
36791
|
+
filename: attachmentFilename(disposition, pType),
|
|
36792
|
+
contentType: pType.split(";")[0].trim(),
|
|
36793
|
+
size: content.length,
|
|
36794
|
+
content
|
|
36795
|
+
});
|
|
36796
|
+
} else if (pType.includes("text/html")) {
|
|
36797
|
+
bodyHtml = decodeTransfer(pBody, cte).trim();
|
|
36798
|
+
} else if (pType.includes("text/plain")) {
|
|
36799
|
+
bodyText = decodeTransfer(pBody, cte).trim();
|
|
37161
36800
|
}
|
|
37162
36801
|
}
|
|
37163
36802
|
}
|
|
37164
36803
|
} else if (contentType.includes("text/html")) {
|
|
37165
|
-
bodyHtml = bodySection;
|
|
36804
|
+
bodyHtml = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
37166
36805
|
} else {
|
|
37167
|
-
bodyText = bodySection;
|
|
36806
|
+
bodyText = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
37168
36807
|
}
|
|
37169
|
-
|
|
37170
|
-
|
|
36808
|
+
return { headers, bodyText, bodyHtml, attachments };
|
|
36809
|
+
}
|
|
36810
|
+
function parseSummary(uid, response) {
|
|
36811
|
+
const { headers, bodyText, bodyHtml } = parseMessage(response);
|
|
36812
|
+
return {
|
|
36813
|
+
uid,
|
|
36814
|
+
subject: headers["subject"] ?? "",
|
|
36815
|
+
from: headers["from"] ?? "",
|
|
36816
|
+
to: headers["to"] ?? "",
|
|
36817
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
36818
|
+
snippet: makeSnippet(bodyText, bodyHtml),
|
|
36819
|
+
seen: /\\Seen/i.test(response)
|
|
36820
|
+
};
|
|
36821
|
+
}
|
|
36822
|
+
function parseFullMessage(uid, response) {
|
|
36823
|
+
const { headers, bodyText, bodyHtml, attachments } = parseMessage(response);
|
|
37171
36824
|
return {
|
|
37172
36825
|
uid,
|
|
37173
36826
|
subject: headers["subject"] ?? "",
|
|
37174
36827
|
from: headers["from"] ?? "",
|
|
37175
36828
|
to: headers["to"] ?? "",
|
|
37176
36829
|
cc: headers["cc"] ?? "",
|
|
37177
|
-
date: headers["date"] ?? "",
|
|
36830
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
37178
36831
|
bodyText,
|
|
37179
36832
|
bodyHtml,
|
|
36833
|
+
attachments,
|
|
37180
36834
|
headers
|
|
37181
36835
|
};
|
|
37182
36836
|
}
|
|
@@ -37292,89 +36946,89 @@ var init_messenger = __esm({
|
|
|
37292
36946
|
}
|
|
37293
36947
|
const messageId = `${randomUUID7()}@${this.host}`;
|
|
37294
36948
|
if (allRecipients.length === 0) {
|
|
37295
|
-
return { success: false, message: "No recipients specified" };
|
|
36949
|
+
return { success: false, message: "No recipients specified", id: null };
|
|
37296
36950
|
}
|
|
37297
36951
|
if (!this.fromAddress) {
|
|
37298
|
-
return { success: false, message: "No from address configured" };
|
|
36952
|
+
return { success: false, message: "No from address configured", id: null };
|
|
37299
36953
|
}
|
|
37300
36954
|
try {
|
|
37301
36955
|
let socket;
|
|
37302
36956
|
if (this.port === 465) {
|
|
37303
36957
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37304
|
-
await new Promise((
|
|
37305
|
-
socket.once("secureConnect",
|
|
36958
|
+
await new Promise((resolve20, reject) => {
|
|
36959
|
+
socket.once("secureConnect", resolve20);
|
|
37306
36960
|
socket.once("error", reject);
|
|
37307
36961
|
});
|
|
37308
36962
|
} else {
|
|
37309
36963
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
37310
|
-
await new Promise((
|
|
37311
|
-
socket.once("connect",
|
|
36964
|
+
await new Promise((resolve20, reject) => {
|
|
36965
|
+
socket.once("connect", resolve20);
|
|
37312
36966
|
socket.once("error", reject);
|
|
37313
36967
|
});
|
|
37314
36968
|
}
|
|
37315
36969
|
const greeting = await readResponse(socket);
|
|
37316
36970
|
if (greeting.code !== 220) {
|
|
37317
36971
|
socket.destroy();
|
|
37318
|
-
return { success: false, message: `SMTP greeting failed: ${greeting.text}
|
|
36972
|
+
return { success: false, message: `SMTP greeting failed: ${greeting.text}`, id: null };
|
|
37319
36973
|
}
|
|
37320
36974
|
const ehlo = await sendCommand(socket, `EHLO ${this.host}`);
|
|
37321
36975
|
if (ehlo.code !== 250) {
|
|
37322
36976
|
socket.destroy();
|
|
37323
|
-
return { success: false, message: `EHLO failed: ${ehlo.text}
|
|
36977
|
+
return { success: false, message: `EHLO failed: ${ehlo.text}`, id: null };
|
|
37324
36978
|
}
|
|
37325
36979
|
if (this.useTls && this.port !== 465 && ehlo.text.includes("STARTTLS")) {
|
|
37326
36980
|
const starttls = await sendCommand(socket, "STARTTLS");
|
|
37327
36981
|
if (starttls.code !== 220) {
|
|
37328
36982
|
socket.destroy();
|
|
37329
|
-
return { success: false, message: `STARTTLS failed: ${starttls.text}
|
|
36983
|
+
return { success: false, message: `STARTTLS failed: ${starttls.text}`, id: null };
|
|
37330
36984
|
}
|
|
37331
36985
|
const plainSocket = socket;
|
|
37332
36986
|
socket = tls2.connect(
|
|
37333
36987
|
{ socket: plainSocket, host: this.host, rejectUnauthorized: tlsRejectUnauthorized() }
|
|
37334
36988
|
);
|
|
37335
|
-
await new Promise((
|
|
37336
|
-
socket.once("secureConnect",
|
|
36989
|
+
await new Promise((resolve20, reject) => {
|
|
36990
|
+
socket.once("secureConnect", resolve20);
|
|
37337
36991
|
socket.once("error", reject);
|
|
37338
36992
|
});
|
|
37339
36993
|
const ehlo2 = await sendCommand(socket, `EHLO ${this.host}`);
|
|
37340
36994
|
if (ehlo2.code !== 250) {
|
|
37341
36995
|
socket.destroy();
|
|
37342
|
-
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}
|
|
36996
|
+
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}`, id: null };
|
|
37343
36997
|
}
|
|
37344
36998
|
}
|
|
37345
36999
|
if (this.username && this.password) {
|
|
37346
37000
|
const auth = await sendCommand(socket, "AUTH LOGIN");
|
|
37347
37001
|
if (auth.code !== 334) {
|
|
37348
37002
|
socket.destroy();
|
|
37349
|
-
return { success: false, message: `AUTH LOGIN failed: ${auth.text}
|
|
37003
|
+
return { success: false, message: `AUTH LOGIN failed: ${auth.text}`, id: null };
|
|
37350
37004
|
}
|
|
37351
37005
|
const userResp = await sendCommand(socket, Buffer.from(this.username).toString("base64"));
|
|
37352
37006
|
if (userResp.code !== 334) {
|
|
37353
37007
|
socket.destroy();
|
|
37354
|
-
return { success: false, message: `AUTH username failed: ${userResp.text}
|
|
37008
|
+
return { success: false, message: `AUTH username failed: ${userResp.text}`, id: null };
|
|
37355
37009
|
}
|
|
37356
37010
|
const passResp = await sendCommand(socket, Buffer.from(this.password).toString("base64"));
|
|
37357
37011
|
if (passResp.code !== 235) {
|
|
37358
37012
|
socket.destroy();
|
|
37359
|
-
return { success: false, message: `AUTH password failed: ${passResp.text}
|
|
37013
|
+
return { success: false, message: `AUTH password failed: ${passResp.text}`, id: null };
|
|
37360
37014
|
}
|
|
37361
37015
|
}
|
|
37362
37016
|
const mailFrom = await sendCommand(socket, `MAIL FROM:<${this.fromAddress}>`);
|
|
37363
37017
|
if (mailFrom.code !== 250) {
|
|
37364
37018
|
socket.destroy();
|
|
37365
|
-
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}
|
|
37019
|
+
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}`, id: null };
|
|
37366
37020
|
}
|
|
37367
37021
|
for (const recipient of allRecipients) {
|
|
37368
37022
|
const rcpt = await sendCommand(socket, `RCPT TO:<${recipient}>`);
|
|
37369
37023
|
if (rcpt.code !== 250 && rcpt.code !== 251) {
|
|
37370
37024
|
socket.destroy();
|
|
37371
|
-
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}
|
|
37025
|
+
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}`, id: null };
|
|
37372
37026
|
}
|
|
37373
37027
|
}
|
|
37374
37028
|
const dataCmd = await sendCommand(socket, "DATA");
|
|
37375
37029
|
if (dataCmd.code !== 354) {
|
|
37376
37030
|
socket.destroy();
|
|
37377
|
-
return { success: false, message: `DATA failed: ${dataCmd.text}
|
|
37031
|
+
return { success: false, message: `DATA failed: ${dataCmd.text}`, id: null };
|
|
37378
37032
|
}
|
|
37379
37033
|
const mimeMessage = buildMimeMessage({
|
|
37380
37034
|
from: this.fromAddress,
|
|
@@ -37393,15 +37047,30 @@ var init_messenger = __esm({
|
|
|
37393
37047
|
const endData = await sendCommand(socket, mimeMessage + "\r\n.");
|
|
37394
37048
|
if (endData.code !== 250) {
|
|
37395
37049
|
socket.destroy();
|
|
37396
|
-
return { success: false, message: `Message delivery failed: ${endData.text}
|
|
37050
|
+
return { success: false, message: `Message delivery failed: ${endData.text}`, id: null };
|
|
37397
37051
|
}
|
|
37398
37052
|
await sendCommand(socket, "QUIT");
|
|
37399
37053
|
socket.destroy();
|
|
37400
37054
|
return { success: true, message: "Email sent successfully", id: messageId };
|
|
37401
37055
|
} catch (err) {
|
|
37402
37056
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
37403
|
-
return { success: false, message: `SMTP error: ${errMsg}
|
|
37057
|
+
return { success: false, message: `SMTP error: ${errMsg}`, id: null };
|
|
37058
|
+
}
|
|
37059
|
+
}
|
|
37060
|
+
/**
|
|
37061
|
+
* Render a Frond template STRING and send it as an HTML email (G7, parity with
|
|
37062
|
+
* Python's send_template). Extra send() options (cc, bcc, replyTo, attachments,
|
|
37063
|
+
* headers) pass through. If the Frond package cannot be loaded the raw template
|
|
37064
|
+
* is sent verbatim (matches Python's ImportError fallback) rather than failing.
|
|
37065
|
+
*/
|
|
37066
|
+
async sendTemplate(to, subject, template, data = {}, cc, bcc, replyTo, attachments, headers) {
|
|
37067
|
+
let body = template;
|
|
37068
|
+
try {
|
|
37069
|
+
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
37070
|
+
body = new Frond2().renderString(template, data);
|
|
37071
|
+
} catch {
|
|
37404
37072
|
}
|
|
37073
|
+
return this.send(to, subject, body, true, void 0, cc, bcc, replyTo, attachments, headers);
|
|
37405
37074
|
}
|
|
37406
37075
|
/**
|
|
37407
37076
|
* Test the SMTP connection without sending an email.
|
|
@@ -37411,14 +37080,14 @@ var init_messenger = __esm({
|
|
|
37411
37080
|
let socket;
|
|
37412
37081
|
if (this.port === 465) {
|
|
37413
37082
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37414
|
-
await new Promise((
|
|
37415
|
-
socket.once("secureConnect",
|
|
37083
|
+
await new Promise((resolve20, reject) => {
|
|
37084
|
+
socket.once("secureConnect", resolve20);
|
|
37416
37085
|
socket.once("error", reject);
|
|
37417
37086
|
});
|
|
37418
37087
|
} else {
|
|
37419
37088
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
37420
|
-
await new Promise((
|
|
37421
|
-
socket.once("connect",
|
|
37089
|
+
await new Promise((resolve20, reject) => {
|
|
37090
|
+
socket.once("connect", resolve20);
|
|
37422
37091
|
socket.once("error", reject);
|
|
37423
37092
|
});
|
|
37424
37093
|
}
|
|
@@ -37453,14 +37122,14 @@ var init_messenger = __esm({
|
|
|
37453
37122
|
const useTls = this.imapEncryption === "tls" || this.imapEncryption === "ssl" || this.imapEncryption === "" && this.imapPort === 993;
|
|
37454
37123
|
if (useTls) {
|
|
37455
37124
|
socket = tls2.connect({ host: this.imapHost, port: this.imapPort, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37456
|
-
await new Promise((
|
|
37457
|
-
socket.once("secureConnect",
|
|
37125
|
+
await new Promise((resolve20, reject) => {
|
|
37126
|
+
socket.once("secureConnect", resolve20);
|
|
37458
37127
|
socket.once("error", reject);
|
|
37459
37128
|
});
|
|
37460
37129
|
} else {
|
|
37461
37130
|
socket = net3.createConnection({ host: this.imapHost, port: this.imapPort });
|
|
37462
|
-
await new Promise((
|
|
37463
|
-
socket.once("connect",
|
|
37131
|
+
await new Promise((resolve20, reject) => {
|
|
37132
|
+
socket.once("connect", resolve20);
|
|
37464
37133
|
socket.once("error", reject);
|
|
37465
37134
|
});
|
|
37466
37135
|
}
|
|
@@ -37497,7 +37166,7 @@ var init_messenger = __esm({
|
|
|
37497
37166
|
}
|
|
37498
37167
|
try {
|
|
37499
37168
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37500
|
-
const searchResp = await imapCommand(socket, "SEARCH ALL");
|
|
37169
|
+
const searchResp = await imapCommand(socket, "UID SEARCH ALL");
|
|
37501
37170
|
const uids = parseSearchResponse(searchResp);
|
|
37502
37171
|
if (uids.length === 0) return [];
|
|
37503
37172
|
uids.reverse();
|
|
@@ -37505,8 +37174,8 @@ var init_messenger = __esm({
|
|
|
37505
37174
|
if (selected.length === 0) return [];
|
|
37506
37175
|
const messages = [];
|
|
37507
37176
|
for (const uid of selected) {
|
|
37508
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
37509
|
-
messages.push(
|
|
37177
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
37178
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
37510
37179
|
}
|
|
37511
37180
|
return messages;
|
|
37512
37181
|
} catch (err) {
|
|
@@ -37516,7 +37185,7 @@ var init_messenger = __esm({
|
|
|
37516
37185
|
}
|
|
37517
37186
|
}
|
|
37518
37187
|
/**
|
|
37519
|
-
* Read a single message by
|
|
37188
|
+
* Read a single message by its IMAP UID.
|
|
37520
37189
|
*/
|
|
37521
37190
|
async read(uid, folder = "INBOX") {
|
|
37522
37191
|
let socket;
|
|
@@ -37527,11 +37196,11 @@ var init_messenger = __esm({
|
|
|
37527
37196
|
}
|
|
37528
37197
|
try {
|
|
37529
37198
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37530
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY[])`);
|
|
37199
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY[])`);
|
|
37531
37200
|
if (!/\{\d+\}/.test(fetchResp)) {
|
|
37532
37201
|
return null;
|
|
37533
37202
|
}
|
|
37534
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
37203
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
37535
37204
|
return parseFullMessage(uid, fetchResp);
|
|
37536
37205
|
} catch (err) {
|
|
37537
37206
|
throw imapFail("read", err);
|
|
@@ -37558,14 +37227,14 @@ var init_messenger = __esm({
|
|
|
37558
37227
|
}
|
|
37559
37228
|
try {
|
|
37560
37229
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37561
|
-
const searchResp = await imapCommand(socket, `SEARCH ${query}`);
|
|
37230
|
+
const searchResp = await imapCommand(socket, `UID SEARCH ${query}`);
|
|
37562
37231
|
const uids = parseSearchResponse(searchResp);
|
|
37563
37232
|
if (uids.length === 0) return [];
|
|
37564
37233
|
uids.reverse();
|
|
37565
37234
|
const messages = [];
|
|
37566
37235
|
for (const uid of uids.slice(0, limit)) {
|
|
37567
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
37568
|
-
messages.push(
|
|
37236
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
37237
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
37569
37238
|
}
|
|
37570
37239
|
return messages;
|
|
37571
37240
|
} catch (err) {
|
|
@@ -37575,26 +37244,46 @@ var init_messenger = __esm({
|
|
|
37575
37244
|
}
|
|
37576
37245
|
}
|
|
37577
37246
|
/**
|
|
37578
|
-
* Delete a message by UID.
|
|
37247
|
+
* Delete a message by UID (mark \Deleted, then EXPUNGE).
|
|
37248
|
+
*
|
|
37249
|
+
* `delete` is the one cross-framework name (python/php/ruby/node all spell it
|
|
37250
|
+
* `delete`). `deleteMessage` remains as a DEPRECATED alias for one release.
|
|
37579
37251
|
*/
|
|
37580
|
-
async
|
|
37252
|
+
async delete(uid, folder = "INBOX") {
|
|
37581
37253
|
const socket = await this.imapConnect();
|
|
37582
37254
|
try {
|
|
37583
37255
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37584
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Deleted)`);
|
|
37256
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Deleted)`);
|
|
37585
37257
|
await imapCommand(socket, "EXPUNGE");
|
|
37586
37258
|
} finally {
|
|
37587
37259
|
await this.imapDisconnect(socket);
|
|
37588
37260
|
}
|
|
37589
37261
|
}
|
|
37262
|
+
/** @deprecated Use {@link delete} — kept as an alias for one release (G7). */
|
|
37263
|
+
async deleteMessage(uid, folder = "INBOX") {
|
|
37264
|
+
return this.delete(uid, folder);
|
|
37265
|
+
}
|
|
37590
37266
|
/**
|
|
37591
|
-
* Mark a message as read.
|
|
37267
|
+
* Mark a message as read (+FLAGS \Seen).
|
|
37592
37268
|
*/
|
|
37593
37269
|
async markRead(uid, folder = "INBOX") {
|
|
37594
37270
|
const socket = await this.imapConnect();
|
|
37595
37271
|
try {
|
|
37596
37272
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37597
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
37273
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
37274
|
+
} finally {
|
|
37275
|
+
await this.imapDisconnect(socket);
|
|
37276
|
+
}
|
|
37277
|
+
}
|
|
37278
|
+
/**
|
|
37279
|
+
* Mark a message as unread (-FLAGS \Seen) — the inverse of markRead (G7,
|
|
37280
|
+
* parity with Python's mark_unread).
|
|
37281
|
+
*/
|
|
37282
|
+
async markUnread(uid, folder = "INBOX") {
|
|
37283
|
+
const socket = await this.imapConnect();
|
|
37284
|
+
try {
|
|
37285
|
+
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37286
|
+
await imapCommand(socket, `UID STORE ${uid} -FLAGS (\\Seen)`);
|
|
37598
37287
|
} finally {
|
|
37599
37288
|
await this.imapDisconnect(socket);
|
|
37600
37289
|
}
|
|
@@ -37611,7 +37300,7 @@ var init_messenger = __esm({
|
|
|
37611
37300
|
}
|
|
37612
37301
|
try {
|
|
37613
37302
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37614
|
-
const searchResp = await imapCommand(socket, "SEARCH UNSEEN");
|
|
37303
|
+
const searchResp = await imapCommand(socket, "UID SEARCH UNSEEN");
|
|
37615
37304
|
return parseSearchResponse(searchResp).length;
|
|
37616
37305
|
} catch (err) {
|
|
37617
37306
|
throw imapFail("unread", err);
|
|
@@ -38350,17 +38039,17 @@ var init_htmlElement = __esm({
|
|
|
38350
38039
|
});
|
|
38351
38040
|
|
|
38352
38041
|
// src/ai.ts
|
|
38353
|
-
import { existsSync as
|
|
38042
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16, readFileSync as readFileSync25 } from "node:fs";
|
|
38354
38043
|
import { homedir } from "node:os";
|
|
38355
|
-
import { join as
|
|
38044
|
+
import { join as join28, resolve as resolve19, relative as relative10, dirname as dirname12 } from "node:path";
|
|
38356
38045
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
38357
38046
|
import { execSync, execFileSync as execFileSync3 } from "node:child_process";
|
|
38358
38047
|
import { createInterface } from "node:readline";
|
|
38359
38048
|
function readVersion() {
|
|
38360
38049
|
try {
|
|
38361
|
-
const thisDir =
|
|
38362
|
-
const rootPkg =
|
|
38363
|
-
const pkg = JSON.parse(
|
|
38050
|
+
const thisDir = dirname12(fileURLToPath6(import.meta.url));
|
|
38051
|
+
const rootPkg = resolve19(thisDir, "..", "..", "..", "package.json");
|
|
38052
|
+
const pkg = JSON.parse(readFileSync25(rootPkg, "utf-8"));
|
|
38364
38053
|
return pkg.version ?? "0.0.0";
|
|
38365
38054
|
} catch {
|
|
38366
38055
|
return "0.0.0";
|
|
@@ -38409,8 +38098,8 @@ function downloadSkillsSync(jobs) {
|
|
|
38409
38098
|
function installSkills(root = ".", targets) {
|
|
38410
38099
|
const ref = skillsRef();
|
|
38411
38100
|
const dests = targets ?? [
|
|
38412
|
-
|
|
38413
|
-
|
|
38101
|
+
join28(resolve19(root), ".claude", "skills"),
|
|
38102
|
+
join28(homedir(), ".claude", "skills")
|
|
38414
38103
|
];
|
|
38415
38104
|
const jobs = [];
|
|
38416
38105
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -38428,9 +38117,9 @@ function installSkills(root = ".", targets) {
|
|
|
38428
38117
|
const base = `https://raw.githubusercontent.com/tina4stack/${spec.repo}/${ref}/.claude/skills/${skill}`;
|
|
38429
38118
|
skillMdUrl[skill] = `${base}/SKILL.md`;
|
|
38430
38119
|
for (const dest of dests) {
|
|
38431
|
-
add(`${base}/SKILL.md`,
|
|
38120
|
+
add(`${base}/SKILL.md`, join28(dest, skill, "SKILL.md"));
|
|
38432
38121
|
for (const r of spec.references) {
|
|
38433
|
-
add(`${base}/references/${r}`,
|
|
38122
|
+
add(`${base}/references/${r}`, join28(dest, skill, "references", r));
|
|
38434
38123
|
}
|
|
38435
38124
|
}
|
|
38436
38125
|
}
|
|
@@ -38442,10 +38131,10 @@ function installSkills(root = ".", targets) {
|
|
|
38442
38131
|
return installed;
|
|
38443
38132
|
}
|
|
38444
38133
|
function isInstalled(root, tool) {
|
|
38445
|
-
return
|
|
38134
|
+
return existsSync25(join28(resolve19(root), tool.contextFile));
|
|
38446
38135
|
}
|
|
38447
38136
|
function showMenu(root = ".") {
|
|
38448
|
-
const r =
|
|
38137
|
+
const r = resolve19(root);
|
|
38449
38138
|
console.log("\n Tina4 AI Context Installer\n");
|
|
38450
38139
|
for (let i = 0; i < AI_TOOLS.length; i++) {
|
|
38451
38140
|
const tool = AI_TOOLS[i];
|
|
@@ -38463,16 +38152,16 @@ function showMenu(root = ".") {
|
|
|
38463
38152
|
const tina4AiMarker = tina4AiInstalled ? ` ${GREEN2}[installed]${RESET2}` : "";
|
|
38464
38153
|
console.log(` 8. Install tina4-ai tools (requires Python)${tina4AiMarker}`);
|
|
38465
38154
|
console.log();
|
|
38466
|
-
return new Promise((
|
|
38155
|
+
return new Promise((resolve20) => {
|
|
38467
38156
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
38468
38157
|
rl.question(" Select (comma-separated, or 'all'): ", (answer) => {
|
|
38469
38158
|
rl.close();
|
|
38470
|
-
|
|
38159
|
+
resolve20(answer.trim());
|
|
38471
38160
|
});
|
|
38472
38161
|
});
|
|
38473
38162
|
}
|
|
38474
38163
|
function installSelected(root, selection) {
|
|
38475
|
-
const rootPath =
|
|
38164
|
+
const rootPath = resolve19(root);
|
|
38476
38165
|
const created = [];
|
|
38477
38166
|
let indices;
|
|
38478
38167
|
let doInstallTina4Ai = false;
|
|
@@ -38565,33 +38254,33 @@ function looksLikeOldFrameworkInstall(existing) {
|
|
|
38565
38254
|
function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
38566
38255
|
const block = skillBlock(contextFile);
|
|
38567
38256
|
const [start2, end] = markersFor(contextFile);
|
|
38568
|
-
if (!
|
|
38569
|
-
|
|
38257
|
+
if (!existsSync25(contextPath)) {
|
|
38258
|
+
writeFileSync16(contextPath, frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
38570
38259
|
return "Installed";
|
|
38571
38260
|
}
|
|
38572
|
-
const existing =
|
|
38261
|
+
const existing = readFileSync25(contextPath, "utf-8");
|
|
38573
38262
|
if (hasMarkers(existing, start2, end)) {
|
|
38574
|
-
|
|
38263
|
+
writeFileSync16(contextPath, replaceMarkerBlock(existing, block, start2, end), "utf-8");
|
|
38575
38264
|
return "Refreshed skill block in";
|
|
38576
38265
|
}
|
|
38577
38266
|
if (looksLikeOldFrameworkInstall(existing)) {
|
|
38578
38267
|
const head = existing.replace(/^\s+/, "");
|
|
38579
38268
|
const preamble = existing.slice(0, existing.length - head.length);
|
|
38580
38269
|
const newContent = (preamble.trim() ? preamble.replace(/\s+$/, "") + "\n\n" : "") + frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n";
|
|
38581
|
-
|
|
38270
|
+
writeFileSync16(contextPath, newContent, "utf-8");
|
|
38582
38271
|
return "Migrated (replaced old framework dump in)";
|
|
38583
38272
|
}
|
|
38584
|
-
|
|
38273
|
+
writeFileSync16(contextPath, existing.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
38585
38274
|
return "Appended skill block to";
|
|
38586
38275
|
}
|
|
38587
38276
|
function installForTool(root, tool, context) {
|
|
38588
38277
|
const created = [];
|
|
38589
|
-
const contextPath =
|
|
38278
|
+
const contextPath = join28(root, tool.contextFile);
|
|
38590
38279
|
if (tool.configDir) {
|
|
38591
|
-
|
|
38280
|
+
mkdirSync18(join28(root, tool.configDir), { recursive: true });
|
|
38592
38281
|
}
|
|
38593
|
-
const parentDir =
|
|
38594
|
-
|
|
38282
|
+
const parentDir = dirname12(contextPath);
|
|
38283
|
+
mkdirSync18(parentDir, { recursive: true });
|
|
38595
38284
|
const action = writeOrMerge(contextPath, tool.contextFile, context);
|
|
38596
38285
|
const rel = relative10(root, contextPath);
|
|
38597
38286
|
created.push(rel);
|
|
@@ -38626,7 +38315,7 @@ function installTina4Ai() {
|
|
|
38626
38315
|
function installClaudeSkills(root) {
|
|
38627
38316
|
const created = [];
|
|
38628
38317
|
for (const skill of installSkills(root)) {
|
|
38629
|
-
created.push(
|
|
38318
|
+
created.push(join28(".claude", "skills", skill));
|
|
38630
38319
|
console.log(` ${GREEN2}\u2713${RESET2} Installed .claude/skills/${skill} (project + global)`);
|
|
38631
38320
|
}
|
|
38632
38321
|
return created;
|
|
@@ -38967,11 +38656,11 @@ import { tests, assertEqual, runAll } from "tina4-nodejs";
|
|
|
38967
38656
|
}
|
|
38968
38657
|
function generateClaudeCodeContext() {
|
|
38969
38658
|
try {
|
|
38970
|
-
const thisDir =
|
|
38971
|
-
const repoRoot =
|
|
38972
|
-
const claudeMdPath =
|
|
38973
|
-
if (
|
|
38974
|
-
return
|
|
38659
|
+
const thisDir = dirname12(fileURLToPath6(import.meta.url));
|
|
38660
|
+
const repoRoot = resolve19(thisDir, "..", "..", "..");
|
|
38661
|
+
const claudeMdPath = join28(repoRoot, "CLAUDE.md");
|
|
38662
|
+
if (existsSync25(claudeMdPath)) {
|
|
38663
|
+
return readFileSync25(claudeMdPath, "utf-8");
|
|
38975
38664
|
}
|
|
38976
38665
|
} catch {
|
|
38977
38666
|
}
|
|
@@ -40963,7 +40652,6 @@ __export(index_exports, {
|
|
|
40963
40652
|
RouteRef: () => RouteRef,
|
|
40964
40653
|
Router: () => Router,
|
|
40965
40654
|
SafeString: () => SafeString2,
|
|
40966
|
-
ScssCompiler: () => ScssCompiler,
|
|
40967
40655
|
SecurityHeadersMiddleware: () => SecurityHeadersMiddleware,
|
|
40968
40656
|
ServiceRunner: () => ServiceRunner,
|
|
40969
40657
|
Session: () => Session,
|
|
@@ -41159,7 +40847,6 @@ var init_index = __esm({
|
|
|
41159
40847
|
init_session();
|
|
41160
40848
|
init_i18n();
|
|
41161
40849
|
init_fakeData();
|
|
41162
|
-
init_scss();
|
|
41163
40850
|
init_queue();
|
|
41164
40851
|
init_job();
|
|
41165
40852
|
init_mqtt();
|
|
@@ -41291,7 +40978,6 @@ export {
|
|
|
41291
40978
|
RouteRef,
|
|
41292
40979
|
Router,
|
|
41293
40980
|
SafeString2 as SafeString,
|
|
41294
|
-
ScssCompiler,
|
|
41295
40981
|
SecurityHeadersMiddleware,
|
|
41296
40982
|
ServiceRunner,
|
|
41297
40983
|
Session,
|