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
package/packages/cli/dist/bin.js
CHANGED
|
@@ -1829,64 +1829,6 @@ var init_middleware = __esm({
|
|
|
1829
1829
|
}
|
|
1830
1830
|
});
|
|
1831
1831
|
|
|
1832
|
-
// ../orm/src/types.ts
|
|
1833
|
-
var FetchResult;
|
|
1834
|
-
var init_types = __esm({
|
|
1835
|
-
"../orm/src/types.ts"() {
|
|
1836
|
-
"use strict";
|
|
1837
|
-
FetchResult = class {
|
|
1838
|
-
records;
|
|
1839
|
-
count;
|
|
1840
|
-
sql;
|
|
1841
|
-
constructor(records, sql = "") {
|
|
1842
|
-
this.records = records;
|
|
1843
|
-
this.count = records.length;
|
|
1844
|
-
this.sql = sql;
|
|
1845
|
-
}
|
|
1846
|
-
/** Paginate the in-memory result set. */
|
|
1847
|
-
toPaginate(page = 1, perPage = 20) {
|
|
1848
|
-
const total = this.count;
|
|
1849
|
-
const totalPages = Math.max(1, Math.ceil(total / perPage));
|
|
1850
|
-
const offset = (page - 1) * perPage;
|
|
1851
|
-
const data = this.records.slice(offset, offset + perPage);
|
|
1852
|
-
return {
|
|
1853
|
-
data,
|
|
1854
|
-
page,
|
|
1855
|
-
perPage,
|
|
1856
|
-
total,
|
|
1857
|
-
totalPages,
|
|
1858
|
-
hasNext: page < totalPages,
|
|
1859
|
-
hasPrev: page > 1
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
|
-
/** Return the first record or null. */
|
|
1863
|
-
first() {
|
|
1864
|
-
return this.records[0] ?? null;
|
|
1865
|
-
}
|
|
1866
|
-
/** Return the last record or null. */
|
|
1867
|
-
last() {
|
|
1868
|
-
return this.records[this.records.length - 1] ?? null;
|
|
1869
|
-
}
|
|
1870
|
-
/** Check if result is empty. */
|
|
1871
|
-
isEmpty() {
|
|
1872
|
-
return this.records.length === 0;
|
|
1873
|
-
}
|
|
1874
|
-
/** Convert to plain array. */
|
|
1875
|
-
toArray() {
|
|
1876
|
-
return [...this.records];
|
|
1877
|
-
}
|
|
1878
|
-
/** Convert to JSON string. */
|
|
1879
|
-
toJSON() {
|
|
1880
|
-
return JSON.stringify(this.records);
|
|
1881
|
-
}
|
|
1882
|
-
/** Iterate over records. */
|
|
1883
|
-
[Symbol.iterator]() {
|
|
1884
|
-
return this.records[Symbol.iterator]();
|
|
1885
|
-
}
|
|
1886
|
-
};
|
|
1887
|
-
}
|
|
1888
|
-
});
|
|
1889
|
-
|
|
1890
1832
|
// ../orm/src/databaseResult.ts
|
|
1891
1833
|
var DatabaseResult;
|
|
1892
1834
|
var init_databaseResult = __esm({
|
|
@@ -1950,75 +1892,52 @@ var init_databaseResult = __esm({
|
|
|
1950
1892
|
toArray() {
|
|
1951
1893
|
return this.records;
|
|
1952
1894
|
}
|
|
1953
|
-
/**
|
|
1895
|
+
/**
|
|
1896
|
+
* Describe the page this result IS — the canonical pagination envelope.
|
|
1954
1897
|
*
|
|
1955
|
-
*
|
|
1956
|
-
* (
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
1898
|
+
* Takes NO arguments and derives every field from the query that produced this
|
|
1899
|
+
* result (ADR-0043). Passing ANY argument RAISES: a DatabaseResult holds no
|
|
1900
|
+
* connection, so an argument could only re-slice the rows already in memory and
|
|
1901
|
+
* then report total_pages for pages it can never reach. To read page N, FETCH
|
|
1902
|
+
* page N (limit + offset) and call this with no arguments.
|
|
1959
1903
|
*
|
|
1960
|
-
*
|
|
1961
|
-
|
|
1962
|
-
/**
|
|
1963
|
-
* Describe the page this result actually IS. Takes no arguments.
|
|
1904
|
+
* The envelope is EXACTLY seven snake_case keys, identical across all four
|
|
1905
|
+
* frameworks: `records, total, page, per_page, total_pages, limit, offset`.
|
|
1964
1906
|
*
|
|
1965
|
-
*
|
|
1966
|
-
* (
|
|
1967
|
-
*
|
|
1968
|
-
*
|
|
1969
|
-
*
|
|
1970
|
-
*
|
|
1971
|
-
*
|
|
1907
|
+
* per_page = the query's limit
|
|
1908
|
+
* page = floor(offset / limit) + 1
|
|
1909
|
+
* total = the TRUE total for the filter — Database.fetch (and
|
|
1910
|
+
* QueryBuilder.get) run a COUNT probe whenever a limit was
|
|
1911
|
+
* applied — NEVER the number of rows returned
|
|
1912
|
+
* total_pages = ceil(total / per_page)
|
|
1913
|
+
* records = the rows the query returned, VERBATIM (never re-sliced)
|
|
1914
|
+
* limit = the SQL limit actually applied
|
|
1915
|
+
* offset = the SQL offset actually applied
|
|
1972
1916
|
*
|
|
1973
|
-
*
|
|
1974
|
-
*
|
|
1975
|
-
* (
|
|
1976
|
-
*
|
|
1977
|
-
* pages 1-5 of 20 were right and every page from 6 onward came back EMPTY
|
|
1978
|
-
* while totalPages reported 5,000.
|
|
1917
|
+
* The JSON payload is snake_case even though the method name is camelCase — a
|
|
1918
|
+
* JSON key is data, not a language surface (ADR-0043). The old duplicate and
|
|
1919
|
+
* camelCase keys (`data`, `count`, `perPage`, `totalPages`, `has_next`,
|
|
1920
|
+
* `has_prev`) are removed: Node emitted 13 keys, the worst offender of the four.
|
|
1979
1921
|
*
|
|
1980
|
-
*
|
|
1981
|
-
* all four frameworks - Database.fetch runs a COUNT probe whenever it applied
|
|
1982
|
-
* a limit. It used to be ROWS RETURNED here and in Ruby while Python and PHP
|
|
1983
|
-
* probed, so one query answered 20 in two frameworks and 250 in the other
|
|
1984
|
-
* two.
|
|
1922
|
+
* @throws {TypeError} if called with any argument.
|
|
1985
1923
|
*/
|
|
1986
|
-
toPaginate(
|
|
1987
|
-
if (
|
|
1924
|
+
toPaginate() {
|
|
1925
|
+
if (arguments.length > 0) {
|
|
1988
1926
|
throw new TypeError(
|
|
1989
|
-
|
|
1927
|
+
"toPaginate() takes no arguments and derives the page from the query that ran (ADR-0043). A DatabaseResult holds no connection, so an argument could only re-slice the rows already in memory and report total_pages for pages it can never reach. To read a page, FETCH it: db.fetch(sql, params, perPage, (page - 1) * perPage), then call toPaginate() with no arguments."
|
|
1990
1928
|
);
|
|
1991
1929
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
let rows;
|
|
1996
|
-
if (page === void 0 && perPage === void 0) {
|
|
1997
|
-
resolvedPerPage = this.limit > 0 ? this.limit : this.records.length;
|
|
1998
|
-
resolvedPage = resolvedPerPage > 0 ? Math.floor(this.offset / resolvedPerPage) + 1 : 1;
|
|
1999
|
-
offset = this.offset;
|
|
2000
|
-
rows = this.records;
|
|
2001
|
-
} else {
|
|
2002
|
-
resolvedPage = page ?? 1;
|
|
2003
|
-
resolvedPerPage = perPage ?? (this.limit > 0 ? this.limit : 10);
|
|
2004
|
-
offset = (resolvedPage - 1) * resolvedPerPage;
|
|
2005
|
-
rows = this.records.slice(offset, offset + resolvedPerPage);
|
|
2006
|
-
}
|
|
2007
|
-
const totalPages = resolvedPerPage > 0 ? Math.max(1, Math.ceil(this.count / resolvedPerPage)) : 1;
|
|
1930
|
+
const perPage = this.limit > 0 ? this.limit : this.records.length;
|
|
1931
|
+
const page = perPage > 0 ? Math.floor(this.offset / perPage) + 1 : 1;
|
|
1932
|
+
const totalPages = perPage > 0 ? Math.max(1, Math.ceil(this.count / perPage)) : 1;
|
|
2008
1933
|
return {
|
|
2009
|
-
records:
|
|
2010
|
-
data: rows,
|
|
2011
|
-
count: this.count,
|
|
1934
|
+
records: this.records,
|
|
2012
1935
|
total: this.count,
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
page: resolvedPage,
|
|
2016
|
-
per_page: resolvedPerPage,
|
|
2017
|
-
perPage: resolvedPerPage,
|
|
2018
|
-
totalPages,
|
|
1936
|
+
page,
|
|
1937
|
+
per_page: perPage,
|
|
2019
1938
|
total_pages: totalPages,
|
|
2020
|
-
|
|
2021
|
-
|
|
1939
|
+
limit: perPage,
|
|
1940
|
+
offset: this.offset
|
|
2022
1941
|
};
|
|
2023
1942
|
}
|
|
2024
1943
|
/** Iterable — for (const row of result) */
|
|
@@ -3862,7 +3781,7 @@ function withConnectTimeout(attempt, budgetMs, host, port, abandon) {
|
|
|
3862
3781
|
const elapsedMs = () => performance.now() - startedAt;
|
|
3863
3782
|
if (budgetMs === null) return attempt();
|
|
3864
3783
|
const started = attempt();
|
|
3865
|
-
return new Promise((
|
|
3784
|
+
return new Promise((resolve30, reject) => {
|
|
3866
3785
|
let expired = false;
|
|
3867
3786
|
const timer = setTimeout(() => {
|
|
3868
3787
|
expired = true;
|
|
@@ -3872,7 +3791,7 @@ function withConnectTimeout(attempt, budgetMs, host, port, abandon) {
|
|
|
3872
3791
|
(arrived) => {
|
|
3873
3792
|
clearTimeout(timer);
|
|
3874
3793
|
if (expired) abandon?.(arrived);
|
|
3875
|
-
else
|
|
3794
|
+
else resolve30(arrived);
|
|
3876
3795
|
},
|
|
3877
3796
|
(failure) => {
|
|
3878
3797
|
clearTimeout(timer);
|
|
@@ -4429,10 +4348,10 @@ var init_mysql = __esm({
|
|
|
4429
4348
|
...timeoutOption
|
|
4430
4349
|
});
|
|
4431
4350
|
}
|
|
4432
|
-
return new Promise((
|
|
4351
|
+
return new Promise((resolve30, reject) => {
|
|
4433
4352
|
this.connection.connect((err) => {
|
|
4434
4353
|
if (err) reject(err);
|
|
4435
|
-
else
|
|
4354
|
+
else resolve30();
|
|
4436
4355
|
});
|
|
4437
4356
|
});
|
|
4438
4357
|
},
|
|
@@ -4454,10 +4373,10 @@ var init_mysql = __esm({
|
|
|
4454
4373
|
}
|
|
4455
4374
|
}
|
|
4456
4375
|
queryPromise(sql, params) {
|
|
4457
|
-
return new Promise((
|
|
4376
|
+
return new Promise((resolve30, reject) => {
|
|
4458
4377
|
this.connection.query(sql, params ?? [], (err, results) => {
|
|
4459
4378
|
if (err) reject(err);
|
|
4460
|
-
else
|
|
4379
|
+
else resolve30(results);
|
|
4461
4380
|
});
|
|
4462
4381
|
});
|
|
4463
4382
|
}
|
|
@@ -4827,11 +4746,11 @@ var init_mssql = __esm({
|
|
|
4827
4746
|
};
|
|
4828
4747
|
}
|
|
4829
4748
|
await withConnectTimeout(
|
|
4830
|
-
() => new Promise((
|
|
4749
|
+
() => new Promise((resolve30, reject) => {
|
|
4831
4750
|
this.connection = new Connection(tediousConfig);
|
|
4832
4751
|
this.connection.on("connect", (err) => {
|
|
4833
4752
|
if (err) reject(err);
|
|
4834
|
-
else
|
|
4753
|
+
else resolve30();
|
|
4835
4754
|
});
|
|
4836
4755
|
this.connection.connect();
|
|
4837
4756
|
}),
|
|
@@ -4876,11 +4795,11 @@ var init_mssql = __esm({
|
|
|
4876
4795
|
const tediousModule = requireTedious();
|
|
4877
4796
|
const Request = tediousModule.Request;
|
|
4878
4797
|
const TYPES = tediousModule.TYPES;
|
|
4879
|
-
return new Promise((
|
|
4798
|
+
return new Promise((resolve30, reject) => {
|
|
4880
4799
|
const rows = [];
|
|
4881
4800
|
const request = new Request(sql, (err, rowCount) => {
|
|
4882
4801
|
if (err) reject(err);
|
|
4883
|
-
else
|
|
4802
|
+
else resolve30({ rows, rowCount });
|
|
4884
4803
|
});
|
|
4885
4804
|
if (params) {
|
|
4886
4805
|
params.forEach((p, i) => {
|
|
@@ -5083,8 +5002,8 @@ var init_mssql = __esm({
|
|
|
5083
5002
|
throw new Error("Use startTransactionAsync() for MSSQL.");
|
|
5084
5003
|
}
|
|
5085
5004
|
async startTransactionAsync() {
|
|
5086
|
-
await new Promise((
|
|
5087
|
-
this.connection.beginTransaction((err) => err ? reject(err) :
|
|
5005
|
+
await new Promise((resolve30, reject) => {
|
|
5006
|
+
this.connection.beginTransaction((err) => err ? reject(err) : resolve30());
|
|
5088
5007
|
});
|
|
5089
5008
|
this._inTransaction = true;
|
|
5090
5009
|
}
|
|
@@ -5092,8 +5011,8 @@ var init_mssql = __esm({
|
|
|
5092
5011
|
throw new Error("Use commitAsync() for MSSQL.");
|
|
5093
5012
|
}
|
|
5094
5013
|
async commitAsync() {
|
|
5095
|
-
await new Promise((
|
|
5096
|
-
this.connection.commitTransaction((err) => err ? reject(err) :
|
|
5014
|
+
await new Promise((resolve30, reject) => {
|
|
5015
|
+
this.connection.commitTransaction((err) => err ? reject(err) : resolve30());
|
|
5097
5016
|
});
|
|
5098
5017
|
this._inTransaction = false;
|
|
5099
5018
|
}
|
|
@@ -5101,8 +5020,8 @@ var init_mssql = __esm({
|
|
|
5101
5020
|
throw new Error("Use rollbackAsync() for MSSQL.");
|
|
5102
5021
|
}
|
|
5103
5022
|
async rollbackAsync() {
|
|
5104
|
-
await new Promise((
|
|
5105
|
-
this.connection.rollbackTransaction((err) => err ? reject(err) :
|
|
5023
|
+
await new Promise((resolve30, reject) => {
|
|
5024
|
+
this.connection.rollbackTransaction((err) => err ? reject(err) : resolve30());
|
|
5106
5025
|
});
|
|
5107
5026
|
this._inTransaction = false;
|
|
5108
5027
|
}
|
|
@@ -5372,8 +5291,8 @@ var init_firebird = __esm({
|
|
|
5372
5291
|
fbConfig.database = normalizeFirebirdDbIdentifier(fbConfig.database);
|
|
5373
5292
|
}
|
|
5374
5293
|
this.db = await withConnectTimeout(
|
|
5375
|
-
() => new Promise((
|
|
5376
|
-
fb.attach(fbConfig, (err, db) => err ? reject(err) :
|
|
5294
|
+
() => new Promise((resolve30, reject) => {
|
|
5295
|
+
fb.attach(fbConfig, (err, db) => err ? reject(err) : resolve30(db));
|
|
5377
5296
|
}),
|
|
5378
5297
|
connectTimeoutMillis(),
|
|
5379
5298
|
fbConfig.host,
|
|
@@ -5440,20 +5359,20 @@ var init_firebird = __esm({
|
|
|
5440
5359
|
return this.transaction ?? this.db;
|
|
5441
5360
|
}
|
|
5442
5361
|
queryPromise(sql, params) {
|
|
5443
|
-
return new Promise((
|
|
5362
|
+
return new Promise((resolve30, reject) => {
|
|
5444
5363
|
const translated = this.translateSql(sql);
|
|
5445
5364
|
this.statementHandle().query(translated, params ?? [], (err, result) => {
|
|
5446
5365
|
if (err) reject(err);
|
|
5447
|
-
else
|
|
5366
|
+
else resolve30(result ?? []);
|
|
5448
5367
|
});
|
|
5449
5368
|
});
|
|
5450
5369
|
}
|
|
5451
5370
|
executePromise(sql, params) {
|
|
5452
|
-
return new Promise((
|
|
5371
|
+
return new Promise((resolve30, reject) => {
|
|
5453
5372
|
const translated = this.translateSql(sql);
|
|
5454
5373
|
this.statementHandle().execute(translated, params ?? [], (err) => {
|
|
5455
5374
|
if (err) reject(err);
|
|
5456
|
-
else
|
|
5375
|
+
else resolve30();
|
|
5457
5376
|
});
|
|
5458
5377
|
});
|
|
5459
5378
|
}
|
|
@@ -5569,12 +5488,12 @@ var init_firebird = __esm({
|
|
|
5569
5488
|
}
|
|
5570
5489
|
async startTransactionAsync() {
|
|
5571
5490
|
this.ensureConnected();
|
|
5572
|
-
await new Promise((
|
|
5491
|
+
await new Promise((resolve30, reject) => {
|
|
5573
5492
|
this.db.transaction(0, (err, transaction) => {
|
|
5574
5493
|
if (err) reject(err);
|
|
5575
5494
|
else {
|
|
5576
5495
|
this.transaction = transaction;
|
|
5577
|
-
|
|
5496
|
+
resolve30();
|
|
5578
5497
|
}
|
|
5579
5498
|
});
|
|
5580
5499
|
});
|
|
@@ -5584,12 +5503,12 @@ var init_firebird = __esm({
|
|
|
5584
5503
|
}
|
|
5585
5504
|
async commitAsync() {
|
|
5586
5505
|
if (!this.transaction) throw new Error("No active transaction to commit.");
|
|
5587
|
-
await new Promise((
|
|
5506
|
+
await new Promise((resolve30, reject) => {
|
|
5588
5507
|
this.transaction.commit((err) => {
|
|
5589
5508
|
if (err) reject(err);
|
|
5590
5509
|
else {
|
|
5591
5510
|
this.transaction = null;
|
|
5592
|
-
|
|
5511
|
+
resolve30();
|
|
5593
5512
|
}
|
|
5594
5513
|
});
|
|
5595
5514
|
});
|
|
@@ -5599,12 +5518,12 @@ var init_firebird = __esm({
|
|
|
5599
5518
|
}
|
|
5600
5519
|
async rollbackAsync() {
|
|
5601
5520
|
if (!this.transaction) throw new Error("No active transaction to rollback.");
|
|
5602
|
-
await new Promise((
|
|
5521
|
+
await new Promise((resolve30, reject) => {
|
|
5603
5522
|
this.transaction.rollback((err) => {
|
|
5604
5523
|
if (err) reject(err);
|
|
5605
5524
|
else {
|
|
5606
5525
|
this.transaction = null;
|
|
5607
|
-
|
|
5526
|
+
resolve30();
|
|
5608
5527
|
}
|
|
5609
5528
|
});
|
|
5610
5529
|
});
|
|
@@ -6665,6 +6584,7 @@ __export(database_exports, {
|
|
|
6665
6584
|
getNamedAdapter: () => getNamedAdapter,
|
|
6666
6585
|
initDatabase: () => initDatabase,
|
|
6667
6586
|
parseDatabaseUrl: () => parseDatabaseUrl,
|
|
6587
|
+
probeTotal: () => probeTotal,
|
|
6668
6588
|
resetRequestCaches: () => resetRequestCaches,
|
|
6669
6589
|
resolveDbPool: () => resolveDbPool,
|
|
6670
6590
|
setAdapter: () => setAdapter,
|
|
@@ -6718,6 +6638,29 @@ async function adapterCreateTable(adapter, name, columns) {
|
|
|
6718
6638
|
if (adapter.createTableAsync) await adapter.createTableAsync(name, columns);
|
|
6719
6639
|
else adapter.createTable(name, columns);
|
|
6720
6640
|
}
|
|
6641
|
+
async function probeTotal(adapter, sql, params, limit) {
|
|
6642
|
+
if (limit === void 0 || limit <= 0) return void 0;
|
|
6643
|
+
try {
|
|
6644
|
+
const alias = adapter.countSubqueryAlias;
|
|
6645
|
+
const suffix = alias ? ` AS ${alias}` : "";
|
|
6646
|
+
const rows = await adapterFetch(
|
|
6647
|
+
adapter,
|
|
6648
|
+
`SELECT COUNT(*) AS tina4_total FROM (${sql}
|
|
6649
|
+
)${suffix}`,
|
|
6650
|
+
params,
|
|
6651
|
+
void 0,
|
|
6652
|
+
void 0,
|
|
6653
|
+
true
|
|
6654
|
+
);
|
|
6655
|
+
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
6656
|
+
if (!row) return void 0;
|
|
6657
|
+
const value = row["tina4_total"] ?? row["TINA4_TOTAL"] ?? Object.values(row)[0];
|
|
6658
|
+
const n = Number(value);
|
|
6659
|
+
return Number.isFinite(n) ? n : void 0;
|
|
6660
|
+
} catch {
|
|
6661
|
+
return void 0;
|
|
6662
|
+
}
|
|
6663
|
+
}
|
|
6721
6664
|
function extractLastInsertId(result) {
|
|
6722
6665
|
if (result && typeof result === "object") {
|
|
6723
6666
|
const r = result;
|
|
@@ -7176,65 +7119,13 @@ var init_database = __esm({
|
|
|
7176
7119
|
try {
|
|
7177
7120
|
const rows = await adapterFetch(adapter, sql, params, limit, offset, opts?.noCache);
|
|
7178
7121
|
this.lastError = null;
|
|
7179
|
-
const total = await
|
|
7122
|
+
const total = await probeTotal(adapter, sql, params, limit);
|
|
7180
7123
|
return new DatabaseResult(rows, void 0, total, limit, offset, adapter, sql);
|
|
7181
7124
|
} catch (e) {
|
|
7182
7125
|
this.lastError = e?.message ?? String(e);
|
|
7183
7126
|
throw e;
|
|
7184
7127
|
}
|
|
7185
7128
|
}
|
|
7186
|
-
/**
|
|
7187
|
-
* The true row count for `sql`, ignoring the pagination we appended.
|
|
7188
|
-
*
|
|
7189
|
-
* `count` is the TRUE TOTAL for the filter, not the number of rows this page
|
|
7190
|
-
* returned. Node and Ruby used to populate it with `records.length` while
|
|
7191
|
-
* Python and PHP populated it from a probe, so `db.fetch(sql).count` answered
|
|
7192
|
-
* 20 here and 250 there for one query against one table, and every paginated
|
|
7193
|
-
* response built on it under-reported. MEASURED 2026-08-05 on a 250-row table
|
|
7194
|
-
* read with limit=20: Node reported total 20 over 2 pages against Python's
|
|
7195
|
-
* 250 over 13.
|
|
7196
|
-
*
|
|
7197
|
-
* Only probed when a limit was actually applied. With no limit the rows
|
|
7198
|
-
* returned ARE the whole answer for this SQL, so `records.length` is already
|
|
7199
|
-
* the true total and a second round-trip would buy nothing — which is also
|
|
7200
|
-
* what keeps `fetchAll()` at one query.
|
|
7201
|
-
*
|
|
7202
|
-
* BEST EFFORT, and it can never mask a real failure: it runs AFTER the main
|
|
7203
|
-
* query (which has already thrown on bad SQL) and returns undefined on any
|
|
7204
|
-
* error. `undefined` — not 0 — is the miss value, so DatabaseResult falls
|
|
7205
|
-
* back to records.length, a true lower bound. Reporting 0 next to 100 real
|
|
7206
|
-
* records would be the same "states a wrong number authoritatively" defect
|
|
7207
|
-
* this change exists to remove.
|
|
7208
|
-
*
|
|
7209
|
-
* The closing paren goes on its OWN LINE: appended inline, a trailing
|
|
7210
|
-
* `-- comment` in the caller's SQL comments it out and the probe dies with
|
|
7211
|
-
* "incomplete input". Postgres, MySQL and MSSQL additionally require a name
|
|
7212
|
-
* for the derived table; SQLite and Firebird do not, and Firebird rejects
|
|
7213
|
-
* `AS` there — so the alias comes from the adapter, not an assumption.
|
|
7214
|
-
*/
|
|
7215
|
-
async countProbe(adapter, sql, params, limit) {
|
|
7216
|
-
if (limit === void 0 || limit <= 0) return void 0;
|
|
7217
|
-
try {
|
|
7218
|
-
const alias = adapter.countSubqueryAlias;
|
|
7219
|
-
const suffix = alias ? ` AS ${alias}` : "";
|
|
7220
|
-
const rows = await adapterFetch(
|
|
7221
|
-
adapter,
|
|
7222
|
-
`SELECT COUNT(*) AS tina4_total FROM (${sql}
|
|
7223
|
-
)${suffix}`,
|
|
7224
|
-
params,
|
|
7225
|
-
void 0,
|
|
7226
|
-
void 0,
|
|
7227
|
-
true
|
|
7228
|
-
);
|
|
7229
|
-
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
7230
|
-
if (!row) return void 0;
|
|
7231
|
-
const value = row["tina4_total"] ?? row["TINA4_TOTAL"] ?? Object.values(row)[0];
|
|
7232
|
-
const n = Number(value);
|
|
7233
|
-
return Number.isFinite(n) ? n : void 0;
|
|
7234
|
-
} catch {
|
|
7235
|
-
return void 0;
|
|
7236
|
-
}
|
|
7237
|
-
}
|
|
7238
7129
|
/**
|
|
7239
7130
|
* Fetch a single row or null.
|
|
7240
7131
|
*
|
|
@@ -7946,6 +7837,10 @@ async function discoverModels(modelsDir) {
|
|
|
7946
7837
|
}
|
|
7947
7838
|
const definition = {
|
|
7948
7839
|
tableName: ModelClass.tableName,
|
|
7840
|
+
// The class name is the type name a generated OpenAPI client wants
|
|
7841
|
+
// (`Item`, not `items`). Carry it so Swagger keys components.schemas by
|
|
7842
|
+
// it. A model exported as `default` keeps its declared class name here.
|
|
7843
|
+
className: typeof ModelClass.name === "string" && ModelClass.name ? ModelClass.name : void 0,
|
|
7949
7844
|
fields: ModelClass.fields,
|
|
7950
7845
|
fieldMapping: ModelClass.fieldMapping,
|
|
7951
7846
|
softDelete: ModelClass.softDelete ?? false,
|
|
@@ -8560,7 +8455,13 @@ async function status(adapter, options) {
|
|
|
8560
8455
|
return result;
|
|
8561
8456
|
}
|
|
8562
8457
|
async function createMigration(description, options) {
|
|
8563
|
-
|
|
8458
|
+
const kind = (options?.kind ?? "sql").trim().toLowerCase();
|
|
8459
|
+
if (!["sql", "code", "class"].includes(kind)) {
|
|
8460
|
+
throw new Error(
|
|
8461
|
+
`Unknown migration kind "${kind}". Use "sql" (default) or "code" (alias: "class"). An unrecognised kind used to produce a .sql file silently, which is why this now throws.`
|
|
8462
|
+
);
|
|
8463
|
+
}
|
|
8464
|
+
if (kind === "code" || kind === "class") {
|
|
8564
8465
|
return createClassMigration(description, options);
|
|
8565
8466
|
}
|
|
8566
8467
|
const dir = resolve4(options?.migrationsDir ?? "migrations");
|
|
@@ -8687,15 +8588,13 @@ var init_migration = __esm({
|
|
|
8687
8588
|
* Scaffold a new migration file.
|
|
8688
8589
|
*
|
|
8689
8590
|
* kind="sql" — creates {timestamp}_{description}.sql + .down.sql (default)
|
|
8690
|
-
* kind="
|
|
8591
|
+
* kind="code" — creates {timestamp}_{description}.ts with a TypeScript class
|
|
8592
|
+
* template. "class" is accepted as a legacy alias.
|
|
8691
8593
|
*
|
|
8692
8594
|
* Returns the path to the created up file (or class file).
|
|
8693
8595
|
*/
|
|
8694
8596
|
async create(description, kind = "sql") {
|
|
8695
|
-
|
|
8696
|
-
return createClassMigration(description, { migrationsDir: this.dir });
|
|
8697
|
-
}
|
|
8698
|
-
return createMigration(description, { migrationsDir: this.dir });
|
|
8597
|
+
return createMigration(description, { migrationsDir: this.dir, kind });
|
|
8699
8598
|
}
|
|
8700
8599
|
/** Return list of completed (applied) migration filenames. */
|
|
8701
8600
|
async getApplied() {
|
|
@@ -8938,15 +8837,8 @@ function generateCrudRoutes(models, options = {}) {
|
|
|
8938
8837
|
const total = Number(countRow[0]?.total ?? 0);
|
|
8939
8838
|
const limit = qp.limit ?? 100;
|
|
8940
8839
|
const page = qp.page ?? 1;
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
meta: {
|
|
8944
|
-
total,
|
|
8945
|
-
page,
|
|
8946
|
-
limit,
|
|
8947
|
-
totalPages: Math.ceil(total / limit)
|
|
8948
|
-
}
|
|
8949
|
-
});
|
|
8840
|
+
const offset = (page - 1) * limit;
|
|
8841
|
+
res.json(new DatabaseResult(rows, void 0, total, limit, offset).toPaginate());
|
|
8950
8842
|
}
|
|
8951
8843
|
});
|
|
8952
8844
|
routes.push({
|
|
@@ -9100,6 +8992,7 @@ var init_autoCrud = __esm({
|
|
|
9100
8992
|
"../orm/src/autoCrud.ts"() {
|
|
9101
8993
|
"use strict";
|
|
9102
8994
|
init_database();
|
|
8995
|
+
init_databaseResult();
|
|
9103
8996
|
init_query();
|
|
9104
8997
|
init_validation();
|
|
9105
8998
|
AutoCrud = class _AutoCrud {
|
|
@@ -9354,17 +9247,19 @@ var init_queryBuilder = __esm({
|
|
|
9354
9247
|
this.ensureDb();
|
|
9355
9248
|
const sql = this.toSql();
|
|
9356
9249
|
const allParams = [...this.params, ...this.havingParams];
|
|
9250
|
+
const queryParams = allParams.length > 0 ? allParams : void 0;
|
|
9357
9251
|
const rows = await adapterFetch(
|
|
9358
9252
|
this.db,
|
|
9359
9253
|
sql,
|
|
9360
|
-
|
|
9254
|
+
queryParams,
|
|
9361
9255
|
this.limitVal,
|
|
9362
9256
|
this.offsetVal
|
|
9363
9257
|
);
|
|
9258
|
+
const total = await probeTotal(this.db, sql, queryParams, this.limitVal);
|
|
9364
9259
|
return new DatabaseResult(
|
|
9365
9260
|
rows,
|
|
9366
9261
|
void 0,
|
|
9367
|
-
|
|
9262
|
+
total,
|
|
9368
9263
|
this.limitVal,
|
|
9369
9264
|
this.offsetVal,
|
|
9370
9265
|
this.db,
|
|
@@ -12941,7 +12836,6 @@ __export(src_exports, {
|
|
|
12941
12836
|
DatabaseUrl: () => DatabaseUrl,
|
|
12942
12837
|
DocStoreDriverMissing: () => DocStoreDriverMissing,
|
|
12943
12838
|
FakeData: () => FakeData2,
|
|
12944
|
-
FetchResult: () => FetchResult,
|
|
12945
12839
|
FirebirdAdapter: () => FirebirdAdapter,
|
|
12946
12840
|
InvalidId: () => InvalidId,
|
|
12947
12841
|
LocalStorage: () => LocalStorage,
|
|
@@ -13041,7 +12935,6 @@ __export(src_exports, {
|
|
|
13041
12935
|
var init_src = __esm({
|
|
13042
12936
|
"../orm/src/index.ts"() {
|
|
13043
12937
|
"use strict";
|
|
13044
|
-
init_types();
|
|
13045
12938
|
init_databaseResult();
|
|
13046
12939
|
init_database();
|
|
13047
12940
|
init_database();
|
|
@@ -13451,7 +13344,7 @@ ${s}\r
|
|
|
13451
13344
|
connect() {
|
|
13452
13345
|
if (this.connected) return Promise.resolve();
|
|
13453
13346
|
if (this.connecting) return this.connecting;
|
|
13454
|
-
this.connecting = new Promise((
|
|
13347
|
+
this.connecting = new Promise((resolve30, reject) => {
|
|
13455
13348
|
const sock = net.createConnection({ host: this.host, port: this.port });
|
|
13456
13349
|
sock.setNoDelay(true);
|
|
13457
13350
|
const onError = (err) => {
|
|
@@ -13488,7 +13381,7 @@ ${s}\r
|
|
|
13488
13381
|
sock.on("error", (e) => {
|
|
13489
13382
|
this.brokenError = e;
|
|
13490
13383
|
});
|
|
13491
|
-
|
|
13384
|
+
resolve30();
|
|
13492
13385
|
} catch (e) {
|
|
13493
13386
|
onError(e);
|
|
13494
13387
|
}
|
|
@@ -13551,12 +13444,12 @@ ${s}\r
|
|
|
13551
13444
|
}
|
|
13552
13445
|
/** Send one command and await its reply (assumes socket is up). */
|
|
13553
13446
|
raw(args) {
|
|
13554
|
-
return new Promise((
|
|
13447
|
+
return new Promise((resolve30, reject) => {
|
|
13555
13448
|
if (!this.sock || this.sock.destroyed) {
|
|
13556
13449
|
reject(this.brokenError ?? new Error("redis socket not connected"));
|
|
13557
13450
|
return;
|
|
13558
13451
|
}
|
|
13559
|
-
this.waiters.push({ resolve:
|
|
13452
|
+
this.waiters.push({ resolve: resolve30, reject });
|
|
13560
13453
|
this.sock.write(_RespClient.encode(args));
|
|
13561
13454
|
});
|
|
13562
13455
|
}
|
|
@@ -13898,7 +13791,7 @@ ${s}\r
|
|
|
13898
13791
|
connect() {
|
|
13899
13792
|
if (this.connected) return Promise.resolve();
|
|
13900
13793
|
if (this.connecting) return this.connecting;
|
|
13901
|
-
this.connecting = new Promise((
|
|
13794
|
+
this.connecting = new Promise((resolve30, reject) => {
|
|
13902
13795
|
const sock = net.createConnection({ host: this.host, port: this.port });
|
|
13903
13796
|
sock.setNoDelay(true);
|
|
13904
13797
|
sock.once("error", (err) => {
|
|
@@ -13919,7 +13812,7 @@ ${s}\r
|
|
|
13919
13812
|
p.resolve(this.buffer.toString("utf-8"));
|
|
13920
13813
|
}
|
|
13921
13814
|
});
|
|
13922
|
-
|
|
13815
|
+
resolve30();
|
|
13923
13816
|
});
|
|
13924
13817
|
});
|
|
13925
13818
|
return this.connecting;
|
|
@@ -13952,13 +13845,13 @@ ${s}\r
|
|
|
13952
13845
|
async send(payload, terminator) {
|
|
13953
13846
|
await this.connect();
|
|
13954
13847
|
if (!this.sock || this.sock.destroyed) return "";
|
|
13955
|
-
return new Promise((
|
|
13848
|
+
return new Promise((resolve30) => {
|
|
13956
13849
|
this.buffer = Buffer.alloc(0);
|
|
13957
|
-
this.pending = { terminator, resolve:
|
|
13850
|
+
this.pending = { terminator, resolve: resolve30 };
|
|
13958
13851
|
const timer = setTimeout(() => {
|
|
13959
|
-
if (this.pending && this.pending.resolve ===
|
|
13852
|
+
if (this.pending && this.pending.resolve === resolve30) {
|
|
13960
13853
|
this.pending = null;
|
|
13961
|
-
|
|
13854
|
+
resolve30(this.buffer.toString("utf-8"));
|
|
13962
13855
|
}
|
|
13963
13856
|
}, 4e3);
|
|
13964
13857
|
if (timer.unref) timer.unref();
|
|
@@ -15484,16 +15377,27 @@ async function parseBody(req2) {
|
|
|
15484
15377
|
}
|
|
15485
15378
|
const contentType = req2.headers["content-type"] ?? "";
|
|
15486
15379
|
const chunks = [];
|
|
15487
|
-
await new Promise((
|
|
15488
|
-
|
|
15489
|
-
|
|
15380
|
+
await new Promise((resolve30, reject) => {
|
|
15381
|
+
let received = 0;
|
|
15382
|
+
let refused = false;
|
|
15383
|
+
req2.on("data", (chunk) => {
|
|
15384
|
+
if (refused) return;
|
|
15385
|
+
received += chunk.length;
|
|
15386
|
+
if (received > TINA4_MAX_UPLOAD_SIZE) {
|
|
15387
|
+
refused = true;
|
|
15388
|
+
chunks.length = 0;
|
|
15389
|
+
reject(new PayloadTooLargeError(received, TINA4_MAX_UPLOAD_SIZE));
|
|
15390
|
+
return;
|
|
15391
|
+
}
|
|
15392
|
+
chunks.push(chunk);
|
|
15393
|
+
});
|
|
15394
|
+
req2.on("end", () => {
|
|
15395
|
+
if (!refused) resolve30();
|
|
15396
|
+
});
|
|
15490
15397
|
req2.on("error", reject);
|
|
15491
15398
|
});
|
|
15492
15399
|
const raw = Buffer.concat(chunks);
|
|
15493
15400
|
if (raw.length === 0) return;
|
|
15494
|
-
if (raw.length > TINA4_MAX_UPLOAD_SIZE) {
|
|
15495
|
-
throw new PayloadTooLargeError(raw.length, TINA4_MAX_UPLOAD_SIZE);
|
|
15496
|
-
}
|
|
15497
15401
|
if (contentType.includes("multipart/form-data")) {
|
|
15498
15402
|
const boundary = extractBoundary(contentType);
|
|
15499
15403
|
if (boundary) {
|
|
@@ -23327,14 +23231,14 @@ data: ${channel.buffer.shift()}
|
|
|
23327
23231
|
`;
|
|
23328
23232
|
continue;
|
|
23329
23233
|
}
|
|
23330
|
-
const gotMessage = await new Promise((
|
|
23234
|
+
const gotMessage = await new Promise((resolve30) => {
|
|
23331
23235
|
const timer = setTimeout(() => {
|
|
23332
23236
|
channel.wake = null;
|
|
23333
|
-
|
|
23237
|
+
resolve30(false);
|
|
23334
23238
|
}, keepaliveMs);
|
|
23335
23239
|
channel.wake = () => {
|
|
23336
23240
|
clearTimeout(timer);
|
|
23337
|
-
|
|
23241
|
+
resolve30(true);
|
|
23338
23242
|
};
|
|
23339
23243
|
});
|
|
23340
23244
|
if (!gotMessage) yield `: keep-alive
|
|
@@ -25849,7 +25753,7 @@ var init_websocket = __esm({
|
|
|
25849
25753
|
* Start the WebSocket server.
|
|
25850
25754
|
*/
|
|
25851
25755
|
async start() {
|
|
25852
|
-
return new Promise((
|
|
25756
|
+
return new Promise((resolve30, reject) => {
|
|
25853
25757
|
this.server = createServer((req2, res) => {
|
|
25854
25758
|
res.writeHead(426, { "Content-Type": "text/plain" });
|
|
25855
25759
|
res.end("Upgrade Required");
|
|
@@ -25859,7 +25763,7 @@ var init_websocket = __esm({
|
|
|
25859
25763
|
});
|
|
25860
25764
|
this.server.listen(this.port, () => {
|
|
25861
25765
|
this.startIdleReaper();
|
|
25862
|
-
|
|
25766
|
+
resolve30();
|
|
25863
25767
|
});
|
|
25864
25768
|
this.server.on("error", (err) => {
|
|
25865
25769
|
this.emit("error", err);
|
|
@@ -26423,7 +26327,7 @@ var init_websocket = __esm({
|
|
|
26423
26327
|
client.trackerId = this.onAdd(socket.remoteAddress ?? "unknown", "/__dev_reload");
|
|
26424
26328
|
}
|
|
26425
26329
|
this.clients.add(client);
|
|
26426
|
-
const
|
|
26330
|
+
const cleanup = () => {
|
|
26427
26331
|
if (!this.clients.has(client)) return;
|
|
26428
26332
|
this.clients.delete(client);
|
|
26429
26333
|
if (client.trackerId && this.onRemove) this.onRemove(client.trackerId);
|
|
@@ -26446,13 +26350,13 @@ var init_websocket = __esm({
|
|
|
26446
26350
|
socket.end();
|
|
26447
26351
|
} catch {
|
|
26448
26352
|
}
|
|
26449
|
-
|
|
26353
|
+
cleanup();
|
|
26450
26354
|
return;
|
|
26451
26355
|
}
|
|
26452
26356
|
}
|
|
26453
26357
|
});
|
|
26454
|
-
socket.on("close",
|
|
26455
|
-
socket.on("error",
|
|
26358
|
+
socket.on("close", cleanup);
|
|
26359
|
+
socket.on("error", cleanup);
|
|
26456
26360
|
return true;
|
|
26457
26361
|
}
|
|
26458
26362
|
/**
|
|
@@ -27970,7 +27874,7 @@ var init_queue = __esm({
|
|
|
27970
27874
|
const jobs = this.popBatch(resolvedBatchSize);
|
|
27971
27875
|
if (jobs.length === 0) {
|
|
27972
27876
|
if (resolvedPollInterval <= 0) break;
|
|
27973
|
-
await new Promise((
|
|
27877
|
+
await new Promise((resolve30) => setTimeout(resolve30, resolvedPollInterval));
|
|
27974
27878
|
continue;
|
|
27975
27879
|
}
|
|
27976
27880
|
yield jobs;
|
|
@@ -27980,7 +27884,7 @@ var init_queue = __esm({
|
|
|
27980
27884
|
const raw = this.pop();
|
|
27981
27885
|
if (raw === null) {
|
|
27982
27886
|
if (resolvedPollInterval <= 0) break;
|
|
27983
|
-
await new Promise((
|
|
27887
|
+
await new Promise((resolve30) => setTimeout(resolve30, resolvedPollInterval));
|
|
27984
27888
|
continue;
|
|
27985
27889
|
}
|
|
27986
27890
|
yield createJob(raw, this);
|
|
@@ -32500,23 +32404,23 @@ var init_devAdmin = __esm({
|
|
|
32500
32404
|
});
|
|
32501
32405
|
};
|
|
32502
32406
|
handleDevAdminJs = async (_req, res) => {
|
|
32503
|
-
const { readFileSync:
|
|
32504
|
-
const { dirname:
|
|
32407
|
+
const { readFileSync: readFileSync28, existsSync: existsSync36 } = await import("node:fs");
|
|
32408
|
+
const { dirname: dirname15, join: join38, resolve: resolve30 } = await import("node:path");
|
|
32505
32409
|
const { fileURLToPath: fileURLToPath9 } = await import("node:url");
|
|
32506
|
-
const dir =
|
|
32410
|
+
const dir = dirname15(fileURLToPath9(import.meta.url));
|
|
32507
32411
|
const candidates = [
|
|
32508
|
-
|
|
32412
|
+
join38(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
32509
32413
|
// src/../public/js/
|
|
32510
|
-
|
|
32414
|
+
join38(dir, "..", "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
32511
32415
|
// deeper nesting
|
|
32512
|
-
|
|
32513
|
-
|
|
32416
|
+
resolve30(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin.min.js"),
|
|
32417
|
+
resolve30(process.cwd(), "public", "js", "tina4-dev-admin.min.js")
|
|
32514
32418
|
// project public/
|
|
32515
32419
|
];
|
|
32516
32420
|
for (const jsPath of candidates) {
|
|
32517
|
-
if (
|
|
32421
|
+
if (existsSync36(jsPath)) {
|
|
32518
32422
|
try {
|
|
32519
|
-
const content =
|
|
32423
|
+
const content = readFileSync28(jsPath, "utf-8");
|
|
32520
32424
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
32521
32425
|
res.raw.end(content);
|
|
32522
32426
|
return;
|
|
@@ -32977,8 +32881,12 @@ function sanitizeSecurity(reqs, schemes) {
|
|
|
32977
32881
|
function generate(routes, models = []) {
|
|
32978
32882
|
const info = {
|
|
32979
32883
|
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
32980
|
-
version
|
|
32981
|
-
|
|
32884
|
+
// The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
|
|
32885
|
+
// 0.0.1). description defaults to the empty string, not a canned sentence.
|
|
32886
|
+
// Both are the settled cross-framework defaults (parity with the Python
|
|
32887
|
+
// master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
|
|
32888
|
+
version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
|
|
32889
|
+
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? ""
|
|
32982
32890
|
};
|
|
32983
32891
|
const contactEmail = (process.env.TINA4_SWAGGER_CONTACT_EMAIL ?? "").trim();
|
|
32984
32892
|
const contactName = (process.env.TINA4_SWAGGER_CONTACT_TEAM ?? "").trim();
|
|
@@ -33011,9 +32919,11 @@ function generate(routes, models = []) {
|
|
|
33011
32919
|
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
33012
32920
|
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
33013
32921
|
const refSchemas = /* @__PURE__ */ new Set();
|
|
32922
|
+
const tableToSchema = /* @__PURE__ */ new Map();
|
|
33014
32923
|
for (const model of models) {
|
|
33015
|
-
const
|
|
33016
|
-
|
|
32924
|
+
const schemaKey = schemaNameForModel(model);
|
|
32925
|
+
tableToSchema.set(model.tableName, schemaKey);
|
|
32926
|
+
spec.components.schemas[schemaKey] = modelToSchema(model);
|
|
33017
32927
|
}
|
|
33018
32928
|
const usedTags = [];
|
|
33019
32929
|
const seenIds = /* @__PURE__ */ new Set();
|
|
@@ -33040,11 +32950,11 @@ function generate(routes, models = []) {
|
|
|
33040
32950
|
if (route.meta?.deprecated) operation.deprecated = true;
|
|
33041
32951
|
const pathParams = extractPathParams(route.pattern);
|
|
33042
32952
|
if (pathParams.length > 0) {
|
|
33043
|
-
operation.parameters = pathParams.map((name) => ({
|
|
32953
|
+
operation.parameters = pathParams.map(({ name, schema }) => ({
|
|
33044
32954
|
name,
|
|
33045
32955
|
in: "path",
|
|
33046
32956
|
required: true,
|
|
33047
|
-
schema
|
|
32957
|
+
schema
|
|
33048
32958
|
}));
|
|
33049
32959
|
}
|
|
33050
32960
|
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
@@ -33070,19 +32980,20 @@ function generate(routes, models = []) {
|
|
|
33070
32980
|
};
|
|
33071
32981
|
} else if (method === "post" || method === "put") {
|
|
33072
32982
|
const modelName = inferModelFromPath(route.pattern);
|
|
33073
|
-
|
|
33074
|
-
|
|
33075
|
-
|
|
33076
|
-
};
|
|
32983
|
+
const schemaKey = modelName ? tableToSchema.get(modelName) : void 0;
|
|
32984
|
+
if (schemaKey) {
|
|
32985
|
+
const sref = `#/components/schemas/${schemaKey}`;
|
|
32986
|
+
const media = { schema: { $ref: sref } };
|
|
33077
32987
|
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
33078
32988
|
operation.requestBody = {
|
|
33079
32989
|
required: true,
|
|
33080
32990
|
content: { "application/json": media }
|
|
33081
32991
|
};
|
|
33082
|
-
|
|
33083
|
-
|
|
33084
|
-
|
|
33085
|
-
|
|
32992
|
+
if (route.meta?.responses === void 0) {
|
|
32993
|
+
operation.responses = {
|
|
32994
|
+
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
32995
|
+
};
|
|
32996
|
+
}
|
|
33086
32997
|
} else if (route.meta?.example !== void 0) {
|
|
33087
32998
|
operation.requestBody = {
|
|
33088
32999
|
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
@@ -33169,6 +33080,21 @@ function resolveServers() {
|
|
|
33169
33080
|
const dev = (process.env.SWAGGER_DEV_URL ?? "").trim();
|
|
33170
33081
|
return dev.length > 0 ? [{ url: dev }] : [{ url: "/" }];
|
|
33171
33082
|
}
|
|
33083
|
+
function schemaNameForModel(model) {
|
|
33084
|
+
const explicit = model.className?.trim();
|
|
33085
|
+
if (explicit) return explicit;
|
|
33086
|
+
return deriveClassName(model.tableName);
|
|
33087
|
+
}
|
|
33088
|
+
function deriveClassName(tableName) {
|
|
33089
|
+
return singularize(tableName).split(/[_\s-]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("") || tableName;
|
|
33090
|
+
}
|
|
33091
|
+
function singularize(word) {
|
|
33092
|
+
if (/ies$/i.test(word) && word.length > 3) return word.slice(0, -3) + "y";
|
|
33093
|
+
if (/(ses|xes|zes|ches|shes)$/i.test(word)) return word.slice(0, -2);
|
|
33094
|
+
if (/ss$/i.test(word)) return word;
|
|
33095
|
+
if (/s$/i.test(word) && word.length > 1) return word.slice(0, -1);
|
|
33096
|
+
return word;
|
|
33097
|
+
}
|
|
33172
33098
|
function modelToSchema(model) {
|
|
33173
33099
|
const properties = {};
|
|
33174
33100
|
const required = [];
|
|
@@ -33242,15 +33168,35 @@ function inferSchema(value) {
|
|
|
33242
33168
|
if (typeof value === "number") return { type: Number.isInteger(value) ? "integer" : "number" };
|
|
33243
33169
|
return { type: "string" };
|
|
33244
33170
|
}
|
|
33171
|
+
function segmentParam(segment) {
|
|
33172
|
+
if (segment.startsWith("{") && segment.endsWith("}")) {
|
|
33173
|
+
const inner = segment.slice(1, -1);
|
|
33174
|
+
if (inner.startsWith("...")) return { name: inner.slice(3), type: "string" };
|
|
33175
|
+
const colon = inner.indexOf(":");
|
|
33176
|
+
if (colon >= 0) return { name: inner.slice(0, colon), type: inner.slice(colon + 1) };
|
|
33177
|
+
return { name: inner, type: "string" };
|
|
33178
|
+
}
|
|
33179
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
33180
|
+
const inner = segment.slice(1, -1);
|
|
33181
|
+
return { name: inner.startsWith("...") ? inner.slice(3) : inner, type: "string" };
|
|
33182
|
+
}
|
|
33183
|
+
if (segment.startsWith(":") && segment.length > 1) {
|
|
33184
|
+
return { name: segment.slice(1), type: "string" };
|
|
33185
|
+
}
|
|
33186
|
+
return null;
|
|
33187
|
+
}
|
|
33245
33188
|
function patternToOpenAPI(pattern) {
|
|
33246
|
-
return pattern.
|
|
33189
|
+
return pattern.split("/").map((segment) => {
|
|
33190
|
+
const p = segmentParam(segment);
|
|
33191
|
+
return p ? `{${p.name}}` : segment;
|
|
33192
|
+
}).join("/");
|
|
33247
33193
|
}
|
|
33248
33194
|
function extractPathParams(pattern) {
|
|
33249
33195
|
const params = [];
|
|
33250
|
-
const
|
|
33251
|
-
|
|
33252
|
-
|
|
33253
|
-
params.push(
|
|
33196
|
+
for (const segment of pattern.split("/")) {
|
|
33197
|
+
const p = segmentParam(segment);
|
|
33198
|
+
if (!p) continue;
|
|
33199
|
+
params.push({ name: p.name, schema: { ...PARAM_TYPE_SCHEMA[p.type] ?? { type: "string" } } });
|
|
33254
33200
|
}
|
|
33255
33201
|
return params;
|
|
33256
33202
|
}
|
|
@@ -33272,8 +33218,12 @@ function inferModelFromPath(pattern) {
|
|
|
33272
33218
|
if (rest.length === 1 && /^[[{]\.{0,3}\w+[\]}]$/.test(rest[0])) return candidate;
|
|
33273
33219
|
return null;
|
|
33274
33220
|
}
|
|
33221
|
+
function operationIdBase(method, openApiPath) {
|
|
33222
|
+
const clean = openApiPath.replace(/^\/+|\/+$/g, "").replace(/\//g, "_").replace(/\.\.\./g, "").replace(/[{}]/g, "").replace(/\*/g, "wildcard");
|
|
33223
|
+
return clean ? `${method}_${clean}` : method;
|
|
33224
|
+
}
|
|
33275
33225
|
function uniqueOperationId(method, openApiPath, seen) {
|
|
33276
|
-
const base = (method
|
|
33226
|
+
const base = operationIdBase(method, openApiPath);
|
|
33277
33227
|
let oid = base;
|
|
33278
33228
|
let n = 2;
|
|
33279
33229
|
while (seen.has(oid)) {
|
|
@@ -33283,13 +33233,25 @@ function uniqueOperationId(method, openApiPath, seen) {
|
|
|
33283
33233
|
seen.add(oid);
|
|
33284
33234
|
return oid;
|
|
33285
33235
|
}
|
|
33286
|
-
var WRITE_METHODS, registeredSchemes, registeredSchemas;
|
|
33236
|
+
var WRITE_METHODS, registeredSchemes, registeredSchemas, PARAM_TYPE_SCHEMA;
|
|
33287
33237
|
var init_generator = __esm({
|
|
33288
33238
|
"../swagger/src/generator.ts"() {
|
|
33289
33239
|
"use strict";
|
|
33290
33240
|
WRITE_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
|
|
33291
33241
|
registeredSchemes = {};
|
|
33292
33242
|
registeredSchemas = {};
|
|
33243
|
+
PARAM_TYPE_SCHEMA = {
|
|
33244
|
+
int: { type: "integer" },
|
|
33245
|
+
integer: { type: "integer" },
|
|
33246
|
+
float: { type: "number" },
|
|
33247
|
+
number: { type: "number" },
|
|
33248
|
+
uuid: { type: "string", format: "uuid" },
|
|
33249
|
+
slug: { type: "string", pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
|
|
33250
|
+
alpha: { type: "string", pattern: "^[A-Za-z]+$" },
|
|
33251
|
+
alnum: { type: "string", pattern: "^[A-Za-z0-9]+$" },
|
|
33252
|
+
path: { type: "string" },
|
|
33253
|
+
string: { type: "string" }
|
|
33254
|
+
};
|
|
33293
33255
|
}
|
|
33294
33256
|
});
|
|
33295
33257
|
|
|
@@ -33609,10 +33571,29 @@ function openBrowser(url) {
|
|
|
33609
33571
|
}, 2e3);
|
|
33610
33572
|
}
|
|
33611
33573
|
function resolvePortAndHost(config) {
|
|
33612
|
-
const
|
|
33574
|
+
const tina4Port = process.env.TINA4_PORT;
|
|
33575
|
+
const legacyPort = process.env.PORT;
|
|
33576
|
+
let port;
|
|
33577
|
+
if (config?.port !== void 0) {
|
|
33578
|
+
port = config.port;
|
|
33579
|
+
} else if (tina4Port && /^\d+$/.test(tina4Port)) {
|
|
33580
|
+
port = parseInt(tina4Port, 10);
|
|
33581
|
+
} else if (legacyPort && /^\d+$/.test(legacyPort)) {
|
|
33582
|
+
port = parseInt(legacyPort, 10);
|
|
33583
|
+
warnDeprecatedPort(port);
|
|
33584
|
+
} else {
|
|
33585
|
+
port = 7148;
|
|
33586
|
+
}
|
|
33613
33587
|
const host = config?.host ?? process.env.TINA4_HOST ?? process.env.HOST ?? "0.0.0.0";
|
|
33614
33588
|
return { port, host };
|
|
33615
33589
|
}
|
|
33590
|
+
function warnDeprecatedPort(port) {
|
|
33591
|
+
if (portDeprecationWarned) return;
|
|
33592
|
+
portDeprecationWarned = true;
|
|
33593
|
+
Log.warning(
|
|
33594
|
+
`PORT is deprecated and will be removed in 3.14 - use TINA4_PORT instead (binding port ${port} from PORT)`
|
|
33595
|
+
);
|
|
33596
|
+
}
|
|
33616
33597
|
function isBannerSuppressed() {
|
|
33617
33598
|
return isTruthy(process.env.TINA4_SUPPRESS);
|
|
33618
33599
|
}
|
|
@@ -33884,6 +33865,29 @@ function deployGallery(name) {
|
|
|
33884
33865
|
</body>
|
|
33885
33866
|
</html>`;
|
|
33886
33867
|
}
|
|
33868
|
+
function startLoopWatchdog() {
|
|
33869
|
+
const raw = (process.env.TINA4_LOOP_LAG_WARN_MS ?? "").trim();
|
|
33870
|
+
const threshold = /^\d+$/.test(raw) ? parseInt(raw, 10) : 250;
|
|
33871
|
+
if (threshold <= 0) {
|
|
33872
|
+
return { stop: () => {
|
|
33873
|
+
} };
|
|
33874
|
+
}
|
|
33875
|
+
let last = Date.now();
|
|
33876
|
+
let warned = 0;
|
|
33877
|
+
const timer = setInterval(() => {
|
|
33878
|
+
const now = Date.now();
|
|
33879
|
+
const lag = now - last - LOOP_WATCHDOG_TICK_MS;
|
|
33880
|
+
last = now;
|
|
33881
|
+
if (lag < threshold) return;
|
|
33882
|
+
warned++;
|
|
33883
|
+
if (warned > 5 && warned % 20 !== 0) return;
|
|
33884
|
+
Log.warning(
|
|
33885
|
+
`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.`
|
|
33886
|
+
);
|
|
33887
|
+
}, LOOP_WATCHDOG_TICK_MS);
|
|
33888
|
+
timer.unref();
|
|
33889
|
+
return { stop: () => clearInterval(timer) };
|
|
33890
|
+
}
|
|
33887
33891
|
async function start(config) {
|
|
33888
33892
|
const isManaged = process.argv.includes("--managed");
|
|
33889
33893
|
if (!isManaged && process.env.TINA4_OVERRIDE_CLIENT !== "true") {
|
|
@@ -34133,7 +34137,9 @@ async function startServer(config) {
|
|
|
34133
34137
|
const resolved = resolvePortAndHost(config);
|
|
34134
34138
|
const host = resolved.host;
|
|
34135
34139
|
let port = resolved.port;
|
|
34136
|
-
|
|
34140
|
+
if (!cluster.isWorker) {
|
|
34141
|
+
port = findAvailablePort(port);
|
|
34142
|
+
}
|
|
34137
34143
|
const isProduction = (process.env.TINA4_PRODUCTION ?? "").toLowerCase() === "true";
|
|
34138
34144
|
if (cluster.isPrimary && isProduction) {
|
|
34139
34145
|
const numCPUs = os2.cpus().length;
|
|
@@ -34349,7 +34355,20 @@ ${reset2}
|
|
|
34349
34355
|
await sessionAutoStart(rawReq, rawRes, req2);
|
|
34350
34356
|
await middleware.run(req2, res);
|
|
34351
34357
|
if (res.raw.writableEnded) return;
|
|
34352
|
-
|
|
34358
|
+
try {
|
|
34359
|
+
await req2.parseBody();
|
|
34360
|
+
} catch (err) {
|
|
34361
|
+
const status2 = err?.statusCode;
|
|
34362
|
+
if (typeof status2 === "number" && status2 >= 400 && status2 < 500) {
|
|
34363
|
+
if (!rawRes.writableEnded) {
|
|
34364
|
+
rawRes.statusCode = status2;
|
|
34365
|
+
rawRes.setHeader("content-type", "application/json");
|
|
34366
|
+
rawRes.end(JSON.stringify({ error: err.message }));
|
|
34367
|
+
}
|
|
34368
|
+
return;
|
|
34369
|
+
}
|
|
34370
|
+
throw err;
|
|
34371
|
+
}
|
|
34353
34372
|
const pathname = req2.path;
|
|
34354
34373
|
const reqStartTime = DevAdmin.isEnabled() ? Date.now() : 0;
|
|
34355
34374
|
const matchedPattern = { value: "" };
|
|
@@ -34548,8 +34567,10 @@ ${reset2}
|
|
|
34548
34567
|
};
|
|
34549
34568
|
process.on("SIGTERM", onSigterm);
|
|
34550
34569
|
process.on("SIGINT", onSigint);
|
|
34570
|
+
const loopWatchdog = startLoopWatchdog();
|
|
34551
34571
|
resolvePromise({
|
|
34552
34572
|
close: () => {
|
|
34573
|
+
loopWatchdog.stop();
|
|
34553
34574
|
process.off("SIGTERM", onSigterm);
|
|
34554
34575
|
process.off("SIGINT", onSigint);
|
|
34555
34576
|
stopAllBackgroundTasks();
|
|
@@ -34564,7 +34585,7 @@ ${reset2}
|
|
|
34564
34585
|
});
|
|
34565
34586
|
});
|
|
34566
34587
|
}
|
|
34567
|
-
var __filename, __dirname, BUILTIN_ERROR_TEMPLATES_DIR, BUILTIN_PUBLIC_DIR, swaggerAssetsEnabled, DEFAULT_SHUTDOWN_TIMEOUT_SECONDS, TINA4_VERSION2, frondCache, _LEGACY_ENV_VARS, TEMPLATE_PAGES_DIR, HTTP_REASON_PHRASES, templateCache, _dispatchFn, _serverHandle, FALLBACK_STAGES;
|
|
34588
|
+
var __filename, __dirname, BUILTIN_ERROR_TEMPLATES_DIR, BUILTIN_PUBLIC_DIR, swaggerAssetsEnabled, DEFAULT_SHUTDOWN_TIMEOUT_SECONDS, TINA4_VERSION2, frondCache, _LEGACY_ENV_VARS, portDeprecationWarned, TEMPLATE_PAGES_DIR, HTTP_REASON_PHRASES, templateCache, _dispatchFn, _serverHandle, LOOP_WATCHDOG_TICK_MS, FALLBACK_STAGES;
|
|
34568
34589
|
var init_server = __esm({
|
|
34569
34590
|
"../core/src/server.ts"() {
|
|
34570
34591
|
"use strict";
|
|
@@ -34617,6 +34638,7 @@ var init_server = __esm({
|
|
|
34617
34638
|
SWAGGER_VERSION: "TINA4_SWAGGER_VERSION",
|
|
34618
34639
|
ORM_PLURAL_TABLE_NAMES: "TINA4_ORM_PLURAL_TABLE_NAMES"
|
|
34619
34640
|
};
|
|
34641
|
+
portDeprecationWarned = false;
|
|
34620
34642
|
TEMPLATE_PAGES_DIR = "pages";
|
|
34621
34643
|
HTTP_REASON_PHRASES = {
|
|
34622
34644
|
100: "Continue",
|
|
@@ -34653,6 +34675,7 @@ var init_server = __esm({
|
|
|
34653
34675
|
templateCache = null;
|
|
34654
34676
|
_dispatchFn = null;
|
|
34655
34677
|
_serverHandle = null;
|
|
34678
|
+
LOOP_WATCHDOG_TICK_MS = 100;
|
|
34656
34679
|
FALLBACK_STAGES = [
|
|
34657
34680
|
serveTemplateFallback,
|
|
34658
34681
|
serveLandingPage,
|
|
@@ -34740,433 +34763,6 @@ var init_env = __esm({
|
|
|
34740
34763
|
}
|
|
34741
34764
|
});
|
|
34742
34765
|
|
|
34743
|
-
// ../core/src/scss.ts
|
|
34744
|
-
import { readFileSync as readFileSync23, writeFileSync as writeFileSync17, existsSync as existsSync25, mkdirSync as mkdirSync19, readdirSync as readdirSync18 } from "node:fs";
|
|
34745
|
-
import { join as join28, resolve as resolve20, dirname as dirname13 } from "node:path";
|
|
34746
|
-
function compileString(scss, importPaths, variables) {
|
|
34747
|
-
const imported = /* @__PURE__ */ new Set();
|
|
34748
|
-
scss = resolveImports(scss, importPaths, imported);
|
|
34749
|
-
scss = scss.replace(/(?<![:"'])\/\/[^\n]*/g, "");
|
|
34750
|
-
scss = extractVariables(scss, variables);
|
|
34751
|
-
const mixins = {};
|
|
34752
|
-
scss = extractMixins(scss, mixins);
|
|
34753
|
-
scss = resolveIncludes(scss, mixins);
|
|
34754
|
-
scss = resolveInterpolation(scss, variables);
|
|
34755
|
-
scss = substituteVariables(scss, variables);
|
|
34756
|
-
scss = evalMath(scss);
|
|
34757
|
-
scss = resolveColorFunctions(scss);
|
|
34758
|
-
const css = flattenNesting(scss);
|
|
34759
|
-
return cleanup(css);
|
|
34760
|
-
}
|
|
34761
|
-
function resolveImports(content, paths, imported) {
|
|
34762
|
-
return content.replace(/@import\s+["']?([^"';\n]+)["']?\s*;/g, (_match, name) => {
|
|
34763
|
-
name = name.trim();
|
|
34764
|
-
const candidates = [];
|
|
34765
|
-
for (const base of paths) {
|
|
34766
|
-
candidates.push(
|
|
34767
|
-
join28(base, `${name}.scss`),
|
|
34768
|
-
join28(base, `_${name}.scss`),
|
|
34769
|
-
join28(base, name)
|
|
34770
|
-
);
|
|
34771
|
-
}
|
|
34772
|
-
for (const candidate of candidates) {
|
|
34773
|
-
if (existsSync25(candidate) && !imported.has(candidate)) {
|
|
34774
|
-
imported.add(candidate);
|
|
34775
|
-
const fileContent = readFileSync23(candidate, "utf-8");
|
|
34776
|
-
return resolveImports(fileContent, [dirname13(candidate), ...paths], imported);
|
|
34777
|
-
}
|
|
34778
|
-
}
|
|
34779
|
-
return `/* IMPORT NOT FOUND: ${name} */`;
|
|
34780
|
-
});
|
|
34781
|
-
}
|
|
34782
|
-
function stripVariableFlags(value) {
|
|
34783
|
-
let declaresDefault = false;
|
|
34784
|
-
for (; ; ) {
|
|
34785
|
-
const match = VARIABLE_FLAG.exec(value);
|
|
34786
|
-
if (match === null) return [value.trim(), declaresDefault];
|
|
34787
|
-
if (match[1] === "default") declaresDefault = true;
|
|
34788
|
-
value = value.slice(0, match.index);
|
|
34789
|
-
}
|
|
34790
|
-
}
|
|
34791
|
-
function extractVariables(scss, variables) {
|
|
34792
|
-
return scss.replace(/\$([a-zA-Z_][\w-]*)\s*:\s*([^;]+);/g, (_m, name, value) => {
|
|
34793
|
-
const [stripped, declaresDefault] = stripVariableFlags(value.trim());
|
|
34794
|
-
if (declaresDefault && (variables[name] ?? "null") !== "null") {
|
|
34795
|
-
return "";
|
|
34796
|
-
}
|
|
34797
|
-
let resolved = stripped;
|
|
34798
|
-
for (const [vName, vVal] of Object.entries(variables)) {
|
|
34799
|
-
resolved = resolved.replaceAll(`$${vName}`, vVal);
|
|
34800
|
-
}
|
|
34801
|
-
variables[name] = resolved;
|
|
34802
|
-
return "";
|
|
34803
|
-
});
|
|
34804
|
-
}
|
|
34805
|
-
function substituteVariables(scss, variables) {
|
|
34806
|
-
const sorted = Object.keys(variables).sort((a, b) => b.length - a.length);
|
|
34807
|
-
for (const name of sorted) {
|
|
34808
|
-
scss = scss.replaceAll(`$${name}`, variables[name]);
|
|
34809
|
-
}
|
|
34810
|
-
return scss;
|
|
34811
|
-
}
|
|
34812
|
-
function resolveInterpolation(scss, variables) {
|
|
34813
|
-
const sorted = Object.keys(variables).sort((a, b) => b.length - a.length);
|
|
34814
|
-
return scss.replace(/#\{([^{}]*)\}/g, (_m, inner) => {
|
|
34815
|
-
let resolved = inner.trim();
|
|
34816
|
-
for (const name of sorted) {
|
|
34817
|
-
resolved = resolved.replaceAll(`$${name}`, variables[name]);
|
|
34818
|
-
}
|
|
34819
|
-
return resolved;
|
|
34820
|
-
});
|
|
34821
|
-
}
|
|
34822
|
-
function extractMixins(scss, mixins) {
|
|
34823
|
-
const pattern = /@mixin\s+([\w-]+)\s*(?:\(([^)]*)\))?\s*\{/g;
|
|
34824
|
-
let match;
|
|
34825
|
-
const locations = [];
|
|
34826
|
-
while ((match = pattern.exec(scss)) !== null) {
|
|
34827
|
-
const name = match[1];
|
|
34828
|
-
const paramsStr = match[2] ?? "";
|
|
34829
|
-
const params = paramsStr.split(",").map((p) => p.trim().replace(/^\$/, "")).filter(Boolean);
|
|
34830
|
-
const bodyStart = match.index + match[0].length;
|
|
34831
|
-
const body = findBlock(scss, bodyStart);
|
|
34832
|
-
if (body !== null) {
|
|
34833
|
-
mixins[name] = { params, body };
|
|
34834
|
-
locations.push({
|
|
34835
|
-
start: match.index,
|
|
34836
|
-
end: bodyStart + body.length + 1,
|
|
34837
|
-
name
|
|
34838
|
-
});
|
|
34839
|
-
}
|
|
34840
|
-
}
|
|
34841
|
-
let result = scss;
|
|
34842
|
-
for (const loc of locations.reverse()) {
|
|
34843
|
-
result = result.slice(0, loc.start) + result.slice(loc.end);
|
|
34844
|
-
}
|
|
34845
|
-
return result;
|
|
34846
|
-
}
|
|
34847
|
-
function resolveIncludes(scss, mixins) {
|
|
34848
|
-
return scss.replace(
|
|
34849
|
-
/@include\s+([\w-]+)\s*(?:\(([^)]*)\))?\s*;/g,
|
|
34850
|
-
(_m, name, argsStr) => {
|
|
34851
|
-
if (!(name in mixins)) {
|
|
34852
|
-
return `/* MIXIN NOT FOUND: ${name} */`;
|
|
34853
|
-
}
|
|
34854
|
-
const mixin = mixins[name];
|
|
34855
|
-
const args = argsStr ? argsStr.split(",").map((a) => a.trim()).filter(Boolean) : [];
|
|
34856
|
-
let body = mixin.body;
|
|
34857
|
-
for (let i = 0; i < mixin.params.length; i++) {
|
|
34858
|
-
const paramName = mixin.params[i].split(":")[0].trim();
|
|
34859
|
-
const defaultVal = mixin.params[i].includes(":") ? mixin.params[i].split(":").slice(1).join(":").trim() : "";
|
|
34860
|
-
const value = i < args.length ? args[i] : defaultVal;
|
|
34861
|
-
body = body.replaceAll(`$${paramName}`, value);
|
|
34862
|
-
}
|
|
34863
|
-
return body;
|
|
34864
|
-
}
|
|
34865
|
-
);
|
|
34866
|
-
}
|
|
34867
|
-
function evalMath(scss) {
|
|
34868
|
-
const placeholders = [];
|
|
34869
|
-
const masked = scss.replace(/calc\([^()]*\)/g, (m) => {
|
|
34870
|
-
placeholders.push(m);
|
|
34871
|
-
return `\0CALC${placeholders.length - 1}\0`;
|
|
34872
|
-
});
|
|
34873
|
-
const folded = masked.replace(
|
|
34874
|
-
/([\d.]+)([a-z%]*)\s*([+\-*/])\s*([\d.]+)([a-z%]*)/g,
|
|
34875
|
-
(full, n1, u1, op, n2, u2) => {
|
|
34876
|
-
const num1 = parseFloat(n1);
|
|
34877
|
-
const num2 = parseFloat(n2);
|
|
34878
|
-
if (Number.isNaN(num1) || Number.isNaN(num2)) return full;
|
|
34879
|
-
const unit1 = u1 || "";
|
|
34880
|
-
const unit2 = u2 || "";
|
|
34881
|
-
let unit;
|
|
34882
|
-
if (unit1 === unit2) {
|
|
34883
|
-
unit = unit1;
|
|
34884
|
-
} else if ((op === "*" || op === "/") && unit1 === "") {
|
|
34885
|
-
unit = unit2;
|
|
34886
|
-
} else if ((op === "*" || op === "/") && unit2 === "") {
|
|
34887
|
-
unit = unit1;
|
|
34888
|
-
} else {
|
|
34889
|
-
return full;
|
|
34890
|
-
}
|
|
34891
|
-
let result;
|
|
34892
|
-
switch (op) {
|
|
34893
|
-
case "+":
|
|
34894
|
-
result = num1 + num2;
|
|
34895
|
-
break;
|
|
34896
|
-
case "-":
|
|
34897
|
-
result = num1 - num2;
|
|
34898
|
-
break;
|
|
34899
|
-
case "*":
|
|
34900
|
-
result = num1 * num2;
|
|
34901
|
-
break;
|
|
34902
|
-
case "/":
|
|
34903
|
-
if (num2 === 0) return full;
|
|
34904
|
-
result = num1 / num2;
|
|
34905
|
-
break;
|
|
34906
|
-
default:
|
|
34907
|
-
return full;
|
|
34908
|
-
}
|
|
34909
|
-
if (result === Math.floor(result)) {
|
|
34910
|
-
return `${Math.floor(result)}${unit}`;
|
|
34911
|
-
}
|
|
34912
|
-
return `${result.toFixed(2)}${unit}`;
|
|
34913
|
-
}
|
|
34914
|
-
);
|
|
34915
|
-
return folded.replace(/\x00CALC(\d+)\x00/g, (_m, idx) => {
|
|
34916
|
-
return placeholders[parseInt(idx, 10)];
|
|
34917
|
-
});
|
|
34918
|
-
}
|
|
34919
|
-
function resolveColorFunctions(scss) {
|
|
34920
|
-
scss = scss.replace(
|
|
34921
|
-
/lighten\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/g,
|
|
34922
|
-
(_m, color, amt) => adjustLightness(color.trim(), parseFloat(amt.trim().replace(/%$/, "")) / 100)
|
|
34923
|
-
);
|
|
34924
|
-
scss = scss.replace(
|
|
34925
|
-
/darken\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/g,
|
|
34926
|
-
(_m, color, amt) => adjustLightness(color.trim(), -(parseFloat(amt.trim().replace(/%$/, "")) / 100))
|
|
34927
|
-
);
|
|
34928
|
-
scss = scss.replace(/rgba\(\s*(#[0-9a-fA-F]{3,8})\s*,\s*([\d.]+)\s*\)/g, (whole, hex, alpha) => {
|
|
34929
|
-
const rgb = hexToRgb(hex);
|
|
34930
|
-
return rgb === null ? whole : `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${alpha.trim()})`;
|
|
34931
|
-
});
|
|
34932
|
-
scss = scss.replace(/rgb\(\s*(#[0-9a-fA-F]{3,8})\s*\)/g, (whole, hex) => {
|
|
34933
|
-
const rgb = hexToRgb(hex);
|
|
34934
|
-
return rgb === null ? whole : `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
|
|
34935
|
-
});
|
|
34936
|
-
scss = scss.replace(
|
|
34937
|
-
/mix\(\s*(#[0-9a-fA-F]{3,8})\s*,\s*(#[0-9a-fA-F]{3,8})\s*(?:,\s*([\d.]+%?)\s*)?\)/g,
|
|
34938
|
-
(whole, h1, h2, weight) => {
|
|
34939
|
-
const c1 = hexToRgb(h1);
|
|
34940
|
-
const c2 = hexToRgb(h2);
|
|
34941
|
-
if (c1 === null || c2 === null) return whole;
|
|
34942
|
-
const w = weight ? parseFloat(weight.replace(/%$/, "")) / 100 : 0.5;
|
|
34943
|
-
const mixed = [0, 1, 2].map((i) => Math.round(c1[i] * w + c2[i] * (1 - w)));
|
|
34944
|
-
return `#${mixed.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
34945
|
-
}
|
|
34946
|
-
);
|
|
34947
|
-
return scss;
|
|
34948
|
-
}
|
|
34949
|
-
function hexToRgb(color) {
|
|
34950
|
-
let c = color.trim().replace(/^#/, "");
|
|
34951
|
-
if (c.length === 3) {
|
|
34952
|
-
c = c.split("").map((ch) => ch + ch).join("");
|
|
34953
|
-
}
|
|
34954
|
-
if (!/^[0-9a-fA-F]{6}$/.test(c)) return null;
|
|
34955
|
-
return [parseInt(c.slice(0, 2), 16), parseInt(c.slice(2, 4), 16), parseInt(c.slice(4, 6), 16)];
|
|
34956
|
-
}
|
|
34957
|
-
function adjustLightness(color, amount) {
|
|
34958
|
-
const rgb = hexToRgb(color);
|
|
34959
|
-
if (rgb === null) return color;
|
|
34960
|
-
let [r, g, b] = rgb.map((v) => v / 255);
|
|
34961
|
-
const max = Math.max(r, g, b);
|
|
34962
|
-
const min = Math.min(r, g, b);
|
|
34963
|
-
let l = (max + min) / 2;
|
|
34964
|
-
const d = max - min;
|
|
34965
|
-
let h = 0;
|
|
34966
|
-
let s = 0;
|
|
34967
|
-
if (d !== 0) {
|
|
34968
|
-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
34969
|
-
if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
|
|
34970
|
-
else if (max === g) h = (b - r) / d + 2;
|
|
34971
|
-
else h = (r - g) / d + 4;
|
|
34972
|
-
h /= 6;
|
|
34973
|
-
}
|
|
34974
|
-
l = Math.max(0, Math.min(1, l + amount));
|
|
34975
|
-
if (s === 0) {
|
|
34976
|
-
r = g = b = l;
|
|
34977
|
-
} else {
|
|
34978
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
34979
|
-
const p = 2 * l - q;
|
|
34980
|
-
r = hueToRgb(p, q, h + 1 / 3);
|
|
34981
|
-
g = hueToRgb(p, q, h);
|
|
34982
|
-
b = hueToRgb(p, q, h - 1 / 3);
|
|
34983
|
-
}
|
|
34984
|
-
const hex = (v) => Math.trunc(v * 255).toString(16).padStart(2, "0");
|
|
34985
|
-
return `#${hex(r)}${hex(g)}${hex(b)}`;
|
|
34986
|
-
}
|
|
34987
|
-
function hueToRgb(p, q, t) {
|
|
34988
|
-
if (t < 0) t += 1;
|
|
34989
|
-
if (t > 1) t -= 1;
|
|
34990
|
-
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
34991
|
-
if (t < 1 / 2) return q;
|
|
34992
|
-
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
34993
|
-
return p;
|
|
34994
|
-
}
|
|
34995
|
-
function flattenNesting(scss) {
|
|
34996
|
-
const output = [];
|
|
34997
|
-
flattenBlock(scss, [], output);
|
|
34998
|
-
return output.join("\n");
|
|
34999
|
-
}
|
|
35000
|
-
function flattenBlock(content, parentSelectors, output) {
|
|
35001
|
-
let pos = 0;
|
|
35002
|
-
const properties = [];
|
|
35003
|
-
while (pos < content.length) {
|
|
35004
|
-
while (pos < content.length && /[\s]/.test(content[pos])) {
|
|
35005
|
-
pos++;
|
|
35006
|
-
}
|
|
35007
|
-
if (pos >= content.length) break;
|
|
35008
|
-
if (content[pos] === "/" && content[pos + 1] === "*") {
|
|
35009
|
-
const end = content.indexOf("*/", pos + 2);
|
|
35010
|
-
if (end === -1) break;
|
|
35011
|
-
output.push(content.slice(pos, end + 2));
|
|
35012
|
-
pos = end + 2;
|
|
35013
|
-
continue;
|
|
35014
|
-
}
|
|
35015
|
-
if (content.slice(pos, pos + 6) === "@media") {
|
|
35016
|
-
const brace = content.indexOf("{", pos);
|
|
35017
|
-
if (brace === -1) break;
|
|
35018
|
-
const mediaQuery = content.slice(pos, brace).trim();
|
|
35019
|
-
const body = findBlock(content, brace + 1);
|
|
35020
|
-
if (body === null) break;
|
|
35021
|
-
pos = brace + 1 + body.length + 1;
|
|
35022
|
-
const innerOutput = [];
|
|
35023
|
-
flattenBlock(body, parentSelectors, innerOutput);
|
|
35024
|
-
if (innerOutput.length > 0) {
|
|
35025
|
-
output.push(`${mediaQuery} {`);
|
|
35026
|
-
for (const line of innerOutput) {
|
|
35027
|
-
output.push(` ${line}`);
|
|
35028
|
-
}
|
|
35029
|
-
output.push("}");
|
|
35030
|
-
}
|
|
35031
|
-
continue;
|
|
35032
|
-
}
|
|
35033
|
-
const bracePos = content.indexOf("{", pos);
|
|
35034
|
-
const semiPos = content.indexOf(";", pos);
|
|
35035
|
-
if (semiPos !== -1 && (bracePos === -1 || semiPos < bracePos)) {
|
|
35036
|
-
const prop = content.slice(pos, semiPos).trim();
|
|
35037
|
-
if (prop && !prop.startsWith("@")) {
|
|
35038
|
-
properties.push(prop);
|
|
35039
|
-
}
|
|
35040
|
-
pos = semiPos + 1;
|
|
35041
|
-
continue;
|
|
35042
|
-
}
|
|
35043
|
-
if (bracePos !== -1) {
|
|
35044
|
-
const selectorText = content.slice(pos, bracePos).trim();
|
|
35045
|
-
const body = findBlock(content, bracePos + 1);
|
|
35046
|
-
if (body === null) break;
|
|
35047
|
-
pos = bracePos + 1 + body.length + 1;
|
|
35048
|
-
if (!selectorText) continue;
|
|
35049
|
-
const selectors = selectorText.split(",").map((s) => s.trim());
|
|
35050
|
-
const newSelectors = [];
|
|
35051
|
-
for (const sel of selectors) {
|
|
35052
|
-
if (parentSelectors.length > 0) {
|
|
35053
|
-
for (const parent of parentSelectors) {
|
|
35054
|
-
if (sel.includes("&")) {
|
|
35055
|
-
newSelectors.push(sel.replace(/&/g, parent));
|
|
35056
|
-
} else {
|
|
35057
|
-
newSelectors.push(`${parent} ${sel}`);
|
|
35058
|
-
}
|
|
35059
|
-
}
|
|
35060
|
-
} else {
|
|
35061
|
-
newSelectors.push(sel);
|
|
35062
|
-
}
|
|
35063
|
-
}
|
|
35064
|
-
flattenBlock(body, newSelectors, output);
|
|
35065
|
-
continue;
|
|
35066
|
-
}
|
|
35067
|
-
const remaining = content.slice(pos).trim();
|
|
35068
|
-
if (remaining) {
|
|
35069
|
-
properties.push(remaining);
|
|
35070
|
-
}
|
|
35071
|
-
break;
|
|
35072
|
-
}
|
|
35073
|
-
if (properties.length > 0 && parentSelectors.length > 0) {
|
|
35074
|
-
const selectorStr = parentSelectors.join(", ");
|
|
35075
|
-
output.push(`${selectorStr} {`);
|
|
35076
|
-
for (const prop of properties) {
|
|
35077
|
-
output.push(` ${prop};`);
|
|
35078
|
-
}
|
|
35079
|
-
output.push("}");
|
|
35080
|
-
}
|
|
35081
|
-
}
|
|
35082
|
-
function findBlock(content, start2) {
|
|
35083
|
-
let depth = 1;
|
|
35084
|
-
let pos = start2;
|
|
35085
|
-
while (pos < content.length && depth > 0) {
|
|
35086
|
-
if (content[pos] === "{") depth++;
|
|
35087
|
-
else if (content[pos] === "}") depth--;
|
|
35088
|
-
if (depth > 0) pos++;
|
|
35089
|
-
}
|
|
35090
|
-
return depth === 0 ? content.slice(start2, pos) : null;
|
|
35091
|
-
}
|
|
35092
|
-
function cleanup(css) {
|
|
35093
|
-
css = css.replace(/[^{}]+\{\s*\}/g, "");
|
|
35094
|
-
css = css.replace(/\n{3,}/g, "\n\n");
|
|
35095
|
-
css = css.split("\n").map((line) => line.trimEnd()).join("\n");
|
|
35096
|
-
return css.trim() + "\n";
|
|
35097
|
-
}
|
|
35098
|
-
var ScssCompiler, VARIABLE_FLAG;
|
|
35099
|
-
var init_scss = __esm({
|
|
35100
|
-
"../core/src/scss.ts"() {
|
|
35101
|
-
"use strict";
|
|
35102
|
-
ScssCompiler = class {
|
|
35103
|
-
_importPaths;
|
|
35104
|
-
_variables;
|
|
35105
|
-
constructor(config) {
|
|
35106
|
-
this._importPaths = config?.importPaths ? [...config.importPaths] : [];
|
|
35107
|
-
this._variables = config?.variables ? { ...config.variables } : {};
|
|
35108
|
-
}
|
|
35109
|
-
/** Compile an SCSS string to CSS. */
|
|
35110
|
-
compile(source) {
|
|
35111
|
-
return compileString(source, this._importPaths, { ...this._variables });
|
|
35112
|
-
}
|
|
35113
|
-
/** Compile an SCSS file to CSS. */
|
|
35114
|
-
compileFile(filePath) {
|
|
35115
|
-
const absPath = resolve20(filePath);
|
|
35116
|
-
const content = readFileSync23(absPath, "utf-8");
|
|
35117
|
-
const paths = [dirname13(absPath), ...this._importPaths];
|
|
35118
|
-
return compileString(content, paths, { ...this._variables });
|
|
35119
|
-
}
|
|
35120
|
-
/** Add a directory to the import resolution path. */
|
|
35121
|
-
addImportPath(path8) {
|
|
35122
|
-
this._importPaths.push(resolve20(path8));
|
|
35123
|
-
}
|
|
35124
|
-
/** Set or override an SCSS variable. */
|
|
35125
|
-
setVariable(name, value) {
|
|
35126
|
-
const key = name.startsWith("$") ? name.slice(1) : name;
|
|
35127
|
-
this._variables[key] = value;
|
|
35128
|
-
}
|
|
35129
|
-
/** Compile all .scss files in a directory into a single CSS output file. */
|
|
35130
|
-
compileScss(scssDir = "src/scss", output = "src/public/css/default.css", minify = false) {
|
|
35131
|
-
const absDir = resolve20(scssDir);
|
|
35132
|
-
if (!existsSync25(absDir)) return "";
|
|
35133
|
-
const files = readdirSync18(absDir).filter((f) => f.endsWith(".scss") && !f.startsWith("_")).sort().map((f) => join28(absDir, f));
|
|
35134
|
-
if (files.length === 0) return "";
|
|
35135
|
-
const paths = [absDir, ...this._importPaths];
|
|
35136
|
-
const imported = /* @__PURE__ */ new Set();
|
|
35137
|
-
let merged = "";
|
|
35138
|
-
for (const file of files) {
|
|
35139
|
-
const content = readFileSync23(file, "utf-8");
|
|
35140
|
-
imported.add(file);
|
|
35141
|
-
merged += resolveImports(content, paths, imported) + "\n";
|
|
35142
|
-
}
|
|
35143
|
-
let css = compileString(merged, paths, { ...this._variables });
|
|
35144
|
-
if (minify) {
|
|
35145
|
-
css = css.replace(/\/\*.*?\*\//gs, "");
|
|
35146
|
-
css = css.replace(/\s+/g, " ");
|
|
35147
|
-
css = css.replace(/\s*([{}:;,])\s*/g, "$1");
|
|
35148
|
-
css = css.replace(/;}/g, "}");
|
|
35149
|
-
css = css.trim();
|
|
35150
|
-
}
|
|
35151
|
-
const absOutput = resolve20(output);
|
|
35152
|
-
const outDir = dirname13(absOutput);
|
|
35153
|
-
if (!existsSync25(outDir)) mkdirSync19(outDir, { recursive: true });
|
|
35154
|
-
let existing = null;
|
|
35155
|
-
try {
|
|
35156
|
-
existing = existsSync25(absOutput) ? readFileSync23(absOutput, "utf-8") : null;
|
|
35157
|
-
} catch {
|
|
35158
|
-
existing = null;
|
|
35159
|
-
}
|
|
35160
|
-
if (existing !== css) {
|
|
35161
|
-
writeFileSync17(absOutput, css, "utf-8");
|
|
35162
|
-
}
|
|
35163
|
-
return css;
|
|
35164
|
-
}
|
|
35165
|
-
};
|
|
35166
|
-
VARIABLE_FLAG = /\s*!(default|global)\s*$/;
|
|
35167
|
-
}
|
|
35168
|
-
});
|
|
35169
|
-
|
|
35170
34766
|
// ../core/src/mqttMessage.ts
|
|
35171
34767
|
var MqttMessage;
|
|
35172
34768
|
var init_mqttMessage = __esm({
|
|
@@ -35240,7 +34836,7 @@ var init_mqttMessage = __esm({
|
|
|
35240
34836
|
import net2 from "node:net";
|
|
35241
34837
|
import tls from "node:tls";
|
|
35242
34838
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
35243
|
-
import { existsSync as
|
|
34839
|
+
import { existsSync as existsSync25, readFileSync as readFileSync23 } from "node:fs";
|
|
35244
34840
|
var MqttError, MqttTimeoutError, CONNECT, CONNACK, PUBLISH, PUBACK, SUBSCRIBE, SUBACK, PINGREQ, PINGRESP, DISCONNECT, PROTOCOL_LEVEL, DEFAULT_PORT, DEFAULT_TLS_PORT, DEFAULT_URL, DEFAULT_KEEPALIVE, SUBSCRIPTION_REFUSED, MAX_REMAINING_LENGTH, QOS2_REFUSED_MESSAGE, CONNACK_RETURN_CODES, Mqtt;
|
|
35245
34841
|
var init_mqtt = __esm({
|
|
35246
34842
|
"../core/src/mqtt.ts"() {
|
|
@@ -35443,7 +35039,7 @@ var init_mqtt = __esm({
|
|
|
35443
35039
|
*/
|
|
35444
35040
|
async connect() {
|
|
35445
35041
|
this.closeSocket();
|
|
35446
|
-
if (this.secure && this.tlsVerify && this.caFile && !
|
|
35042
|
+
if (this.secure && this.tlsVerify && this.caFile && !existsSync25(this.caFile)) {
|
|
35447
35043
|
throw new MqttError(
|
|
35448
35044
|
`MQTT CA file not found: ${this.caFile} -- TINA4_MQTT_CA_FILE (or caFile) must point at the broker's CA certificate in PEM form`
|
|
35449
35045
|
);
|
|
@@ -35680,7 +35276,7 @@ var init_mqtt = __esm({
|
|
|
35680
35276
|
* a later client.
|
|
35681
35277
|
*/
|
|
35682
35278
|
openSocket() {
|
|
35683
|
-
return new Promise((
|
|
35279
|
+
return new Promise((resolve30, reject) => {
|
|
35684
35280
|
let settled = false;
|
|
35685
35281
|
const settle = (fn) => {
|
|
35686
35282
|
if (settled) return;
|
|
@@ -35705,10 +35301,10 @@ var init_mqtt = __esm({
|
|
|
35705
35301
|
servername: this.host,
|
|
35706
35302
|
rejectUnauthorized: this.tlsVerify
|
|
35707
35303
|
};
|
|
35708
|
-
if (this.tlsVerify && this.caFile) opts.ca =
|
|
35709
|
-
sock = tls.connect(opts, () => settle(() =>
|
|
35304
|
+
if (this.tlsVerify && this.caFile) opts.ca = readFileSync23(this.caFile);
|
|
35305
|
+
sock = tls.connect(opts, () => settle(() => resolve30(sock)));
|
|
35710
35306
|
} else {
|
|
35711
|
-
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() =>
|
|
35307
|
+
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve30(sock)));
|
|
35712
35308
|
}
|
|
35713
35309
|
sock.once("error", (err) => {
|
|
35714
35310
|
settle(() => {
|
|
@@ -35747,13 +35343,13 @@ var init_mqtt = __esm({
|
|
|
35747
35343
|
writePacket(header, body) {
|
|
35748
35344
|
if (this.socket === null) return Promise.reject(new MqttError("not connected to an MQTT broker"));
|
|
35749
35345
|
const packet = Buffer.concat([Buffer.from([header]), _Mqtt.encodeRemainingLength(body.length), body]);
|
|
35750
|
-
return new Promise((
|
|
35346
|
+
return new Promise((resolve30, reject) => {
|
|
35751
35347
|
this.socket.write(packet, (err) => {
|
|
35752
35348
|
if (err) {
|
|
35753
35349
|
reject(new MqttError(`MQTT write failed: ${err.message}`));
|
|
35754
35350
|
} else {
|
|
35755
35351
|
this.lastWriteAt = Date.now();
|
|
35756
|
-
|
|
35352
|
+
resolve30();
|
|
35757
35353
|
}
|
|
35758
35354
|
});
|
|
35759
35355
|
});
|
|
@@ -35786,7 +35382,7 @@ var init_mqtt = __esm({
|
|
|
35786
35382
|
if (this.readBuffer.length >= need) return Promise.resolve(this.take(need));
|
|
35787
35383
|
if (this.socket === null) return Promise.reject(this.socketError ?? new MqttError("not connected to an MQTT broker"));
|
|
35788
35384
|
if (this.socketError !== null) return Promise.reject(this.socketError);
|
|
35789
|
-
return new Promise((
|
|
35385
|
+
return new Promise((resolve30, reject) => {
|
|
35790
35386
|
let timer = null;
|
|
35791
35387
|
if (deadline !== null) {
|
|
35792
35388
|
const remaining = deadline - Date.now();
|
|
@@ -35801,7 +35397,7 @@ var init_mqtt = __esm({
|
|
|
35801
35397
|
}
|
|
35802
35398
|
}, remaining);
|
|
35803
35399
|
}
|
|
35804
|
-
this.waiter = { need, resolve:
|
|
35400
|
+
this.waiter = { need, resolve: resolve30, reject, timer };
|
|
35805
35401
|
this.serviceWaiter();
|
|
35806
35402
|
});
|
|
35807
35403
|
}
|
|
@@ -35925,8 +35521,8 @@ var init_mqtt = __esm({
|
|
|
35925
35521
|
});
|
|
35926
35522
|
|
|
35927
35523
|
// ../core/src/service.ts
|
|
35928
|
-
import { readdirSync as
|
|
35929
|
-
import { join as
|
|
35524
|
+
import { readdirSync as readdirSync18, statSync as statSync18, watchFile, unwatchFile } from "node:fs";
|
|
35525
|
+
import { join as join28, extname as extname8 } from "node:path";
|
|
35930
35526
|
import { pathToFileURL } from "node:url";
|
|
35931
35527
|
function matchCronField(field, value) {
|
|
35932
35528
|
if (field === "*") return true;
|
|
@@ -36092,14 +35688,14 @@ var init_service = __esm({
|
|
|
36092
35688
|
const discovered = [];
|
|
36093
35689
|
let entries;
|
|
36094
35690
|
try {
|
|
36095
|
-
entries =
|
|
35691
|
+
entries = readdirSync18(dir);
|
|
36096
35692
|
} catch {
|
|
36097
35693
|
return discovered;
|
|
36098
35694
|
}
|
|
36099
35695
|
for (const entry of entries) {
|
|
36100
35696
|
const ext = extname8(entry);
|
|
36101
35697
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
36102
|
-
const fullPath =
|
|
35698
|
+
const fullPath = join28(dir, entry);
|
|
36103
35699
|
const stat = statSync18(fullPath);
|
|
36104
35700
|
if (!stat.isFile()) continue;
|
|
36105
35701
|
try {
|
|
@@ -36208,14 +35804,14 @@ var init_service = __esm({
|
|
|
36208
35804
|
const dir = serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
36209
35805
|
let entries;
|
|
36210
35806
|
try {
|
|
36211
|
-
entries =
|
|
35807
|
+
entries = readdirSync18(dir);
|
|
36212
35808
|
} catch {
|
|
36213
35809
|
return;
|
|
36214
35810
|
}
|
|
36215
35811
|
for (const entry of entries) {
|
|
36216
35812
|
const ext = extname8(entry);
|
|
36217
35813
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
36218
|
-
const fullPath =
|
|
35814
|
+
const fullPath = join28(dir, entry);
|
|
36219
35815
|
if (watchedFiles.has(fullPath)) continue;
|
|
36220
35816
|
watchedFiles.add(fullPath);
|
|
36221
35817
|
watchFile(fullPath, { interval: 1e3 }, async () => {
|
|
@@ -36259,7 +35855,7 @@ import https from "node:https";
|
|
|
36259
35855
|
import { URL as URL2 } from "node:url";
|
|
36260
35856
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
36261
35857
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
36262
|
-
import { basename as
|
|
35858
|
+
import { basename as basename5 } from "node:path";
|
|
36263
35859
|
import { pipeline } from "node:stream/promises";
|
|
36264
35860
|
function sameOrigin(urlA, urlB) {
|
|
36265
35861
|
try {
|
|
@@ -36549,7 +36145,7 @@ var init_api = __esm({
|
|
|
36549
36145
|
error: err instanceof Error ? err.message : String(err)
|
|
36550
36146
|
};
|
|
36551
36147
|
}
|
|
36552
|
-
uploadName = filename ||
|
|
36148
|
+
uploadName = filename || basename5(filePath);
|
|
36553
36149
|
} else {
|
|
36554
36150
|
return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
|
|
36555
36151
|
}
|
|
@@ -36764,12 +36360,12 @@ var init_api = __esm({
|
|
|
36764
36360
|
* authenticate to.
|
|
36765
36361
|
*/
|
|
36766
36362
|
performRequest(method, url, headers, data, redirectsLeft) {
|
|
36767
|
-
return new Promise((
|
|
36363
|
+
return new Promise((resolve30) => {
|
|
36768
36364
|
let parsed;
|
|
36769
36365
|
try {
|
|
36770
36366
|
parsed = new URL2(url);
|
|
36771
36367
|
} catch (err) {
|
|
36772
|
-
|
|
36368
|
+
resolve30({ kind: "error", error: err instanceof Error ? err.message : String(err) });
|
|
36773
36369
|
return;
|
|
36774
36370
|
}
|
|
36775
36371
|
const isHttps = parsed.protocol === "https:";
|
|
@@ -36794,7 +36390,7 @@ var init_api = __esm({
|
|
|
36794
36390
|
try {
|
|
36795
36391
|
nextUrl = new URL2(location, url).toString();
|
|
36796
36392
|
} catch {
|
|
36797
|
-
|
|
36393
|
+
resolve30({ kind: "response", res });
|
|
36798
36394
|
return;
|
|
36799
36395
|
}
|
|
36800
36396
|
const crossOrigin = !sameOrigin(url, nextUrl);
|
|
@@ -36812,17 +36408,17 @@ var init_api = __esm({
|
|
|
36812
36408
|
deleteHeaderCaseInsensitive(nextHeaders, name);
|
|
36813
36409
|
}
|
|
36814
36410
|
}
|
|
36815
|
-
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(
|
|
36411
|
+
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(resolve30);
|
|
36816
36412
|
return;
|
|
36817
36413
|
}
|
|
36818
|
-
|
|
36414
|
+
resolve30({ kind: "response", res });
|
|
36819
36415
|
});
|
|
36820
36416
|
req2.on("timeout", () => {
|
|
36821
36417
|
req2.destroy();
|
|
36822
|
-
|
|
36418
|
+
resolve30({ kind: "error", error: `Request timed out after ${this.timeout}s` });
|
|
36823
36419
|
});
|
|
36824
36420
|
req2.on("error", (err) => {
|
|
36825
|
-
|
|
36421
|
+
resolve30({ kind: "error", error: err.message });
|
|
36826
36422
|
});
|
|
36827
36423
|
if (data) {
|
|
36828
36424
|
req2.write(data);
|
|
@@ -36832,7 +36428,7 @@ var init_api = __esm({
|
|
|
36832
36428
|
}
|
|
36833
36429
|
/** Buffer a response body, parse JSON if possible, and store cookies. */
|
|
36834
36430
|
readResponse(res) {
|
|
36835
|
-
return new Promise((
|
|
36431
|
+
return new Promise((resolve30) => {
|
|
36836
36432
|
const chunks = [];
|
|
36837
36433
|
res.on("data", (chunk) => {
|
|
36838
36434
|
chunks.push(chunk);
|
|
@@ -36847,7 +36443,7 @@ var init_api = __esm({
|
|
|
36847
36443
|
} catch {
|
|
36848
36444
|
parsed = raw;
|
|
36849
36445
|
}
|
|
36850
|
-
|
|
36446
|
+
resolve30({
|
|
36851
36447
|
http_code: res.statusCode ?? null,
|
|
36852
36448
|
body: parsed,
|
|
36853
36449
|
headers: respHeaders,
|
|
@@ -36855,7 +36451,7 @@ var init_api = __esm({
|
|
|
36855
36451
|
});
|
|
36856
36452
|
});
|
|
36857
36453
|
res.on("error", (err) => {
|
|
36858
|
-
|
|
36454
|
+
resolve30({ http_code: null, body: null, headers: {}, error: err.message });
|
|
36859
36455
|
});
|
|
36860
36456
|
});
|
|
36861
36457
|
}
|
|
@@ -36908,14 +36504,14 @@ var init_api = __esm({
|
|
|
36908
36504
|
// ../core/src/messenger.ts
|
|
36909
36505
|
import net3 from "node:net";
|
|
36910
36506
|
import tls2 from "node:tls";
|
|
36911
|
-
import { readFileSync as
|
|
36912
|
-
import { basename as
|
|
36507
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
36508
|
+
import { basename as basename6 } from "node:path";
|
|
36913
36509
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
36914
36510
|
function tlsRejectUnauthorized() {
|
|
36915
36511
|
return !isTruthy(process.env.TINA4_MAIL_TLS_INSECURE);
|
|
36916
36512
|
}
|
|
36917
36513
|
function readResponse(socket) {
|
|
36918
|
-
return new Promise((
|
|
36514
|
+
return new Promise((resolve30, reject) => {
|
|
36919
36515
|
let buffer = "";
|
|
36920
36516
|
const onData = (chunk) => {
|
|
36921
36517
|
buffer += chunk.toString("utf-8");
|
|
@@ -36927,7 +36523,7 @@ function readResponse(socket) {
|
|
|
36927
36523
|
if (line.length >= 4 && line[3] === " ") {
|
|
36928
36524
|
socket.removeListener("data", onData);
|
|
36929
36525
|
socket.removeListener("error", onError);
|
|
36930
|
-
|
|
36526
|
+
resolve30({ code, text: buffer.trim() });
|
|
36931
36527
|
return;
|
|
36932
36528
|
}
|
|
36933
36529
|
}
|
|
@@ -36941,10 +36537,10 @@ function readResponse(socket) {
|
|
|
36941
36537
|
});
|
|
36942
36538
|
}
|
|
36943
36539
|
function sendCommand(socket, command) {
|
|
36944
|
-
return new Promise((
|
|
36540
|
+
return new Promise((resolve30, reject) => {
|
|
36945
36541
|
socket.write(command + "\r\n", "utf-8", (err) => {
|
|
36946
36542
|
if (err) return reject(err);
|
|
36947
|
-
readResponse(socket).then(
|
|
36543
|
+
readResponse(socket).then(resolve30, reject);
|
|
36948
36544
|
});
|
|
36949
36545
|
});
|
|
36950
36546
|
}
|
|
@@ -37000,8 +36596,8 @@ function buildMimeMessage(options) {
|
|
|
37000
36596
|
lines.push(options.body);
|
|
37001
36597
|
}
|
|
37002
36598
|
for (const filePath of options.attachments) {
|
|
37003
|
-
const fileName =
|
|
37004
|
-
const fileData =
|
|
36599
|
+
const fileName = basename6(filePath);
|
|
36600
|
+
const fileData = readFileSync24(filePath);
|
|
37005
36601
|
const base64Data = fileData.toString("base64");
|
|
37006
36602
|
lines.push("");
|
|
37007
36603
|
lines.push(`--${boundary}`);
|
|
@@ -37044,7 +36640,7 @@ function imapQuote(s) {
|
|
|
37044
36640
|
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
37045
36641
|
}
|
|
37046
36642
|
function imapReadLine(socket) {
|
|
37047
|
-
return new Promise((
|
|
36643
|
+
return new Promise((resolve30, reject) => {
|
|
37048
36644
|
let buffer = "";
|
|
37049
36645
|
const onData = (chunk) => {
|
|
37050
36646
|
buffer += chunk.toString("utf-8");
|
|
@@ -37052,7 +36648,7 @@ function imapReadLine(socket) {
|
|
|
37052
36648
|
if (nlIndex !== -1) {
|
|
37053
36649
|
socket.removeListener("data", onData);
|
|
37054
36650
|
socket.removeListener("error", onError);
|
|
37055
|
-
|
|
36651
|
+
resolve30(buffer);
|
|
37056
36652
|
}
|
|
37057
36653
|
};
|
|
37058
36654
|
const onError = (err) => {
|
|
@@ -37064,7 +36660,7 @@ function imapReadLine(socket) {
|
|
|
37064
36660
|
});
|
|
37065
36661
|
}
|
|
37066
36662
|
function imapCommand(socket, command) {
|
|
37067
|
-
return new Promise((
|
|
36663
|
+
return new Promise((resolve30, reject) => {
|
|
37068
36664
|
imapTagCounter++;
|
|
37069
36665
|
const tag = `T${imapTagCounter}`;
|
|
37070
36666
|
const fullCommand = `${tag} ${command}\r
|
|
@@ -37075,7 +36671,7 @@ function imapCommand(socket, command) {
|
|
|
37075
36671
|
if (buffer.includes(`${tag} OK`)) {
|
|
37076
36672
|
socket.removeListener("data", onData);
|
|
37077
36673
|
socket.removeListener("error", onError);
|
|
37078
|
-
|
|
36674
|
+
resolve30(buffer);
|
|
37079
36675
|
return;
|
|
37080
36676
|
}
|
|
37081
36677
|
if (buffer.includes(`${tag} NO`) || buffer.includes(`${tag} BAD`)) {
|
|
@@ -37104,91 +36700,149 @@ function parseSearchResponse(response) {
|
|
|
37104
36700
|
if (!match) return [];
|
|
37105
36701
|
return match[1].trim().split(/\s+/).filter((s) => /^\d+$/.test(s));
|
|
37106
36702
|
}
|
|
37107
|
-
function
|
|
37108
|
-
const
|
|
37109
|
-
|
|
37110
|
-
|
|
37111
|
-
|
|
37112
|
-
let currentKey = "";
|
|
37113
|
-
for (const line of lines) {
|
|
37114
|
-
if (/^\s/.test(line) && currentKey) {
|
|
37115
|
-
headers[currentKey] += " " + line.trim();
|
|
37116
|
-
} else {
|
|
37117
|
-
const colonIdx = line.indexOf(":");
|
|
37118
|
-
if (colonIdx > 0) {
|
|
37119
|
-
currentKey = line.substring(0, colonIdx).trim().toLowerCase();
|
|
37120
|
-
headers[currentKey] = line.substring(colonIdx + 1).trim();
|
|
37121
|
-
}
|
|
37122
|
-
}
|
|
37123
|
-
}
|
|
37124
|
-
}
|
|
37125
|
-
const seen = /\\Seen/i.test(response);
|
|
37126
|
-
return {
|
|
37127
|
-
uid,
|
|
37128
|
-
subject: headers["subject"] ?? "",
|
|
37129
|
-
from: headers["from"] ?? "",
|
|
37130
|
-
to: headers["to"] ?? "",
|
|
37131
|
-
date: headers["date"] ?? "",
|
|
37132
|
-
snippet: "",
|
|
37133
|
-
seen
|
|
37134
|
-
};
|
|
36703
|
+
function extractRawMessage(response) {
|
|
36704
|
+
const m = response.match(/\{(\d+)\}\r\n/);
|
|
36705
|
+
if (!m) return response;
|
|
36706
|
+
const start2 = (m.index ?? 0) + m[0].length;
|
|
36707
|
+
return response.slice(start2, start2 + parseInt(m[1], 10));
|
|
37135
36708
|
}
|
|
37136
|
-
function
|
|
37137
|
-
const bodyMatch = response.match(/\{(\d+)\}\r\n([\s\S]*)/);
|
|
37138
|
-
const rawMessage = bodyMatch ? bodyMatch[2] : response;
|
|
37139
|
-
const headerEnd = rawMessage.indexOf("\r\n\r\n");
|
|
37140
|
-
const headerSection = headerEnd > 0 ? rawMessage.substring(0, headerEnd) : rawMessage;
|
|
37141
|
-
const bodySection = headerEnd > 0 ? rawMessage.substring(headerEnd + 4) : "";
|
|
36709
|
+
function parseMimeHeaders(section) {
|
|
37142
36710
|
const headers = {};
|
|
37143
|
-
const headerLines = headerSection.split(/\r\n/);
|
|
37144
36711
|
let currentKey = "";
|
|
37145
|
-
for (const line of
|
|
36712
|
+
for (const line of section.split(/\r\n/)) {
|
|
37146
36713
|
if (/^\s/.test(line) && currentKey) {
|
|
37147
36714
|
headers[currentKey] += " " + line.trim();
|
|
37148
36715
|
} else {
|
|
37149
|
-
const
|
|
37150
|
-
if (
|
|
37151
|
-
currentKey = line.substring(0,
|
|
37152
|
-
headers[currentKey] = line.substring(
|
|
36716
|
+
const idx = line.indexOf(":");
|
|
36717
|
+
if (idx > 0) {
|
|
36718
|
+
currentKey = line.substring(0, idx).trim().toLowerCase();
|
|
36719
|
+
headers[currentKey] = line.substring(idx + 1).trim();
|
|
36720
|
+
}
|
|
36721
|
+
}
|
|
36722
|
+
}
|
|
36723
|
+
return headers;
|
|
36724
|
+
}
|
|
36725
|
+
function decodeTransfer(body, encoding) {
|
|
36726
|
+
const enc = encoding.toLowerCase().trim();
|
|
36727
|
+
if (enc === "base64") {
|
|
36728
|
+
try {
|
|
36729
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64").toString("utf-8");
|
|
36730
|
+
} catch {
|
|
36731
|
+
return body;
|
|
36732
|
+
}
|
|
36733
|
+
}
|
|
36734
|
+
if (enc === "quoted-printable") {
|
|
36735
|
+
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)));
|
|
36736
|
+
}
|
|
36737
|
+
return body;
|
|
36738
|
+
}
|
|
36739
|
+
function decodeAttachmentBytes(body, encoding) {
|
|
36740
|
+
const enc = encoding.toLowerCase().trim();
|
|
36741
|
+
if (enc === "base64") {
|
|
36742
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64");
|
|
36743
|
+
}
|
|
36744
|
+
const trimmed = body.replace(/\r\n$/, "");
|
|
36745
|
+
if (enc === "quoted-printable") {
|
|
36746
|
+
const collapsed = trimmed.replace(/=\r?\n/g, "");
|
|
36747
|
+
const bytes = [];
|
|
36748
|
+
for (let i = 0; i < collapsed.length; i++) {
|
|
36749
|
+
const hex = collapsed.substring(i + 1, i + 3);
|
|
36750
|
+
if (collapsed[i] === "=" && /^[0-9A-Fa-f]{2}$/.test(hex)) {
|
|
36751
|
+
bytes.push(parseInt(hex, 16));
|
|
36752
|
+
i += 2;
|
|
36753
|
+
} else {
|
|
36754
|
+
bytes.push(collapsed.charCodeAt(i) & 255);
|
|
37153
36755
|
}
|
|
37154
36756
|
}
|
|
36757
|
+
return Buffer.from(bytes);
|
|
37155
36758
|
}
|
|
36759
|
+
return Buffer.from(trimmed, "utf-8");
|
|
36760
|
+
}
|
|
36761
|
+
function attachmentFilename(disposition, contentType) {
|
|
36762
|
+
const d = disposition.match(/filename="?([^";\r\n]+)"?/i);
|
|
36763
|
+
if (d) return d[1].trim();
|
|
36764
|
+
const c = contentType.match(/name="?([^";\r\n]+)"?/i);
|
|
36765
|
+
if (c) return c[1].trim();
|
|
36766
|
+
return "attachment";
|
|
36767
|
+
}
|
|
36768
|
+
function makeSnippet(bodyText, bodyHtml) {
|
|
36769
|
+
return (bodyText || bodyHtml || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
36770
|
+
}
|
|
36771
|
+
function toIsoDate(raw) {
|
|
36772
|
+
if (!raw) return "";
|
|
36773
|
+
const d = new Date(raw);
|
|
36774
|
+
return Number.isNaN(d.getTime()) ? raw : d.toISOString();
|
|
36775
|
+
}
|
|
36776
|
+
function parseMessage(response) {
|
|
36777
|
+
const raw = extractRawMessage(response);
|
|
36778
|
+
const headerEnd = raw.indexOf("\r\n\r\n");
|
|
36779
|
+
const headerSection = headerEnd >= 0 ? raw.substring(0, headerEnd) : raw;
|
|
36780
|
+
const bodySection = headerEnd >= 0 ? raw.substring(headerEnd + 4) : "";
|
|
36781
|
+
const headers = parseMimeHeaders(headerSection);
|
|
37156
36782
|
const contentType = headers["content-type"] ?? "text/plain";
|
|
37157
36783
|
let bodyText = "";
|
|
37158
36784
|
let bodyHtml = "";
|
|
36785
|
+
const attachments = [];
|
|
37159
36786
|
if (contentType.includes("multipart")) {
|
|
37160
36787
|
const boundaryMatch = contentType.match(/boundary="?([^";\s]+)"?/);
|
|
37161
36788
|
if (boundaryMatch) {
|
|
37162
|
-
const boundary = boundaryMatch[1];
|
|
37163
|
-
const
|
|
37164
|
-
|
|
37165
|
-
if (
|
|
37166
|
-
const
|
|
37167
|
-
|
|
37168
|
-
const
|
|
37169
|
-
|
|
37170
|
-
|
|
37171
|
-
|
|
37172
|
-
|
|
36789
|
+
const boundary = "--" + boundaryMatch[1];
|
|
36790
|
+
for (const part of bodySection.split(boundary)) {
|
|
36791
|
+
const trimmed = part.trim();
|
|
36792
|
+
if (trimmed === "" || trimmed === "--") continue;
|
|
36793
|
+
const pEnd = part.indexOf("\r\n\r\n");
|
|
36794
|
+
if (pEnd < 0) continue;
|
|
36795
|
+
const pHeaders = parseMimeHeaders(part.substring(0, pEnd));
|
|
36796
|
+
const pBody = part.substring(pEnd + 4);
|
|
36797
|
+
const cte = pHeaders["content-transfer-encoding"] ?? "";
|
|
36798
|
+
const pType = pHeaders["content-type"] ?? "text/plain";
|
|
36799
|
+
const disposition = pHeaders["content-disposition"] ?? "";
|
|
36800
|
+
if (/attachment/i.test(disposition)) {
|
|
36801
|
+
const content = decodeAttachmentBytes(pBody, cte);
|
|
36802
|
+
attachments.push({
|
|
36803
|
+
filename: attachmentFilename(disposition, pType),
|
|
36804
|
+
contentType: pType.split(";")[0].trim(),
|
|
36805
|
+
size: content.length,
|
|
36806
|
+
content
|
|
36807
|
+
});
|
|
36808
|
+
} else if (pType.includes("text/html")) {
|
|
36809
|
+
bodyHtml = decodeTransfer(pBody, cte).trim();
|
|
36810
|
+
} else if (pType.includes("text/plain")) {
|
|
36811
|
+
bodyText = decodeTransfer(pBody, cte).trim();
|
|
37173
36812
|
}
|
|
37174
36813
|
}
|
|
37175
36814
|
}
|
|
37176
36815
|
} else if (contentType.includes("text/html")) {
|
|
37177
|
-
bodyHtml = bodySection;
|
|
36816
|
+
bodyHtml = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
37178
36817
|
} else {
|
|
37179
|
-
bodyText = bodySection;
|
|
36818
|
+
bodyText = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
37180
36819
|
}
|
|
37181
|
-
|
|
37182
|
-
|
|
36820
|
+
return { headers, bodyText, bodyHtml, attachments };
|
|
36821
|
+
}
|
|
36822
|
+
function parseSummary(uid, response) {
|
|
36823
|
+
const { headers, bodyText, bodyHtml } = parseMessage(response);
|
|
36824
|
+
return {
|
|
36825
|
+
uid,
|
|
36826
|
+
subject: headers["subject"] ?? "",
|
|
36827
|
+
from: headers["from"] ?? "",
|
|
36828
|
+
to: headers["to"] ?? "",
|
|
36829
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
36830
|
+
snippet: makeSnippet(bodyText, bodyHtml),
|
|
36831
|
+
seen: /\\Seen/i.test(response)
|
|
36832
|
+
};
|
|
36833
|
+
}
|
|
36834
|
+
function parseFullMessage(uid, response) {
|
|
36835
|
+
const { headers, bodyText, bodyHtml, attachments } = parseMessage(response);
|
|
37183
36836
|
return {
|
|
37184
36837
|
uid,
|
|
37185
36838
|
subject: headers["subject"] ?? "",
|
|
37186
36839
|
from: headers["from"] ?? "",
|
|
37187
36840
|
to: headers["to"] ?? "",
|
|
37188
36841
|
cc: headers["cc"] ?? "",
|
|
37189
|
-
date: headers["date"] ?? "",
|
|
36842
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
37190
36843
|
bodyText,
|
|
37191
36844
|
bodyHtml,
|
|
36845
|
+
attachments,
|
|
37192
36846
|
headers
|
|
37193
36847
|
};
|
|
37194
36848
|
}
|
|
@@ -37304,89 +36958,89 @@ var init_messenger = __esm({
|
|
|
37304
36958
|
}
|
|
37305
36959
|
const messageId = `${randomUUID7()}@${this.host}`;
|
|
37306
36960
|
if (allRecipients.length === 0) {
|
|
37307
|
-
return { success: false, message: "No recipients specified" };
|
|
36961
|
+
return { success: false, message: "No recipients specified", id: null };
|
|
37308
36962
|
}
|
|
37309
36963
|
if (!this.fromAddress) {
|
|
37310
|
-
return { success: false, message: "No from address configured" };
|
|
36964
|
+
return { success: false, message: "No from address configured", id: null };
|
|
37311
36965
|
}
|
|
37312
36966
|
try {
|
|
37313
36967
|
let socket;
|
|
37314
36968
|
if (this.port === 465) {
|
|
37315
36969
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37316
|
-
await new Promise((
|
|
37317
|
-
socket.once("secureConnect",
|
|
36970
|
+
await new Promise((resolve30, reject) => {
|
|
36971
|
+
socket.once("secureConnect", resolve30);
|
|
37318
36972
|
socket.once("error", reject);
|
|
37319
36973
|
});
|
|
37320
36974
|
} else {
|
|
37321
36975
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
37322
|
-
await new Promise((
|
|
37323
|
-
socket.once("connect",
|
|
36976
|
+
await new Promise((resolve30, reject) => {
|
|
36977
|
+
socket.once("connect", resolve30);
|
|
37324
36978
|
socket.once("error", reject);
|
|
37325
36979
|
});
|
|
37326
36980
|
}
|
|
37327
36981
|
const greeting = await readResponse(socket);
|
|
37328
36982
|
if (greeting.code !== 220) {
|
|
37329
36983
|
socket.destroy();
|
|
37330
|
-
return { success: false, message: `SMTP greeting failed: ${greeting.text}
|
|
36984
|
+
return { success: false, message: `SMTP greeting failed: ${greeting.text}`, id: null };
|
|
37331
36985
|
}
|
|
37332
36986
|
const ehlo = await sendCommand(socket, `EHLO ${this.host}`);
|
|
37333
36987
|
if (ehlo.code !== 250) {
|
|
37334
36988
|
socket.destroy();
|
|
37335
|
-
return { success: false, message: `EHLO failed: ${ehlo.text}
|
|
36989
|
+
return { success: false, message: `EHLO failed: ${ehlo.text}`, id: null };
|
|
37336
36990
|
}
|
|
37337
36991
|
if (this.useTls && this.port !== 465 && ehlo.text.includes("STARTTLS")) {
|
|
37338
36992
|
const starttls = await sendCommand(socket, "STARTTLS");
|
|
37339
36993
|
if (starttls.code !== 220) {
|
|
37340
36994
|
socket.destroy();
|
|
37341
|
-
return { success: false, message: `STARTTLS failed: ${starttls.text}
|
|
36995
|
+
return { success: false, message: `STARTTLS failed: ${starttls.text}`, id: null };
|
|
37342
36996
|
}
|
|
37343
36997
|
const plainSocket = socket;
|
|
37344
36998
|
socket = tls2.connect(
|
|
37345
36999
|
{ socket: plainSocket, host: this.host, rejectUnauthorized: tlsRejectUnauthorized() }
|
|
37346
37000
|
);
|
|
37347
|
-
await new Promise((
|
|
37348
|
-
socket.once("secureConnect",
|
|
37001
|
+
await new Promise((resolve30, reject) => {
|
|
37002
|
+
socket.once("secureConnect", resolve30);
|
|
37349
37003
|
socket.once("error", reject);
|
|
37350
37004
|
});
|
|
37351
37005
|
const ehlo2 = await sendCommand(socket, `EHLO ${this.host}`);
|
|
37352
37006
|
if (ehlo2.code !== 250) {
|
|
37353
37007
|
socket.destroy();
|
|
37354
|
-
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}
|
|
37008
|
+
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}`, id: null };
|
|
37355
37009
|
}
|
|
37356
37010
|
}
|
|
37357
37011
|
if (this.username && this.password) {
|
|
37358
37012
|
const auth = await sendCommand(socket, "AUTH LOGIN");
|
|
37359
37013
|
if (auth.code !== 334) {
|
|
37360
37014
|
socket.destroy();
|
|
37361
|
-
return { success: false, message: `AUTH LOGIN failed: ${auth.text}
|
|
37015
|
+
return { success: false, message: `AUTH LOGIN failed: ${auth.text}`, id: null };
|
|
37362
37016
|
}
|
|
37363
37017
|
const userResp = await sendCommand(socket, Buffer.from(this.username).toString("base64"));
|
|
37364
37018
|
if (userResp.code !== 334) {
|
|
37365
37019
|
socket.destroy();
|
|
37366
|
-
return { success: false, message: `AUTH username failed: ${userResp.text}
|
|
37020
|
+
return { success: false, message: `AUTH username failed: ${userResp.text}`, id: null };
|
|
37367
37021
|
}
|
|
37368
37022
|
const passResp = await sendCommand(socket, Buffer.from(this.password).toString("base64"));
|
|
37369
37023
|
if (passResp.code !== 235) {
|
|
37370
37024
|
socket.destroy();
|
|
37371
|
-
return { success: false, message: `AUTH password failed: ${passResp.text}
|
|
37025
|
+
return { success: false, message: `AUTH password failed: ${passResp.text}`, id: null };
|
|
37372
37026
|
}
|
|
37373
37027
|
}
|
|
37374
37028
|
const mailFrom = await sendCommand(socket, `MAIL FROM:<${this.fromAddress}>`);
|
|
37375
37029
|
if (mailFrom.code !== 250) {
|
|
37376
37030
|
socket.destroy();
|
|
37377
|
-
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}
|
|
37031
|
+
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}`, id: null };
|
|
37378
37032
|
}
|
|
37379
37033
|
for (const recipient of allRecipients) {
|
|
37380
37034
|
const rcpt = await sendCommand(socket, `RCPT TO:<${recipient}>`);
|
|
37381
37035
|
if (rcpt.code !== 250 && rcpt.code !== 251) {
|
|
37382
37036
|
socket.destroy();
|
|
37383
|
-
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}
|
|
37037
|
+
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}`, id: null };
|
|
37384
37038
|
}
|
|
37385
37039
|
}
|
|
37386
37040
|
const dataCmd = await sendCommand(socket, "DATA");
|
|
37387
37041
|
if (dataCmd.code !== 354) {
|
|
37388
37042
|
socket.destroy();
|
|
37389
|
-
return { success: false, message: `DATA failed: ${dataCmd.text}
|
|
37043
|
+
return { success: false, message: `DATA failed: ${dataCmd.text}`, id: null };
|
|
37390
37044
|
}
|
|
37391
37045
|
const mimeMessage = buildMimeMessage({
|
|
37392
37046
|
from: this.fromAddress,
|
|
@@ -37405,16 +37059,31 @@ var init_messenger = __esm({
|
|
|
37405
37059
|
const endData = await sendCommand(socket, mimeMessage + "\r\n.");
|
|
37406
37060
|
if (endData.code !== 250) {
|
|
37407
37061
|
socket.destroy();
|
|
37408
|
-
return { success: false, message: `Message delivery failed: ${endData.text}
|
|
37062
|
+
return { success: false, message: `Message delivery failed: ${endData.text}`, id: null };
|
|
37409
37063
|
}
|
|
37410
37064
|
await sendCommand(socket, "QUIT");
|
|
37411
37065
|
socket.destroy();
|
|
37412
37066
|
return { success: true, message: "Email sent successfully", id: messageId };
|
|
37413
37067
|
} catch (err) {
|
|
37414
37068
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
37415
|
-
return { success: false, message: `SMTP error: ${errMsg}
|
|
37069
|
+
return { success: false, message: `SMTP error: ${errMsg}`, id: null };
|
|
37416
37070
|
}
|
|
37417
37071
|
}
|
|
37072
|
+
/**
|
|
37073
|
+
* Render a Frond template STRING and send it as an HTML email (G7, parity with
|
|
37074
|
+
* Python's send_template). Extra send() options (cc, bcc, replyTo, attachments,
|
|
37075
|
+
* headers) pass through. If the Frond package cannot be loaded the raw template
|
|
37076
|
+
* is sent verbatim (matches Python's ImportError fallback) rather than failing.
|
|
37077
|
+
*/
|
|
37078
|
+
async sendTemplate(to, subject, template, data = {}, cc, bcc, replyTo, attachments, headers) {
|
|
37079
|
+
let body = template;
|
|
37080
|
+
try {
|
|
37081
|
+
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
37082
|
+
body = new Frond2().renderString(template, data);
|
|
37083
|
+
} catch {
|
|
37084
|
+
}
|
|
37085
|
+
return this.send(to, subject, body, true, void 0, cc, bcc, replyTo, attachments, headers);
|
|
37086
|
+
}
|
|
37418
37087
|
/**
|
|
37419
37088
|
* Test the SMTP connection without sending an email.
|
|
37420
37089
|
*/
|
|
@@ -37423,14 +37092,14 @@ var init_messenger = __esm({
|
|
|
37423
37092
|
let socket;
|
|
37424
37093
|
if (this.port === 465) {
|
|
37425
37094
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37426
|
-
await new Promise((
|
|
37427
|
-
socket.once("secureConnect",
|
|
37095
|
+
await new Promise((resolve30, reject) => {
|
|
37096
|
+
socket.once("secureConnect", resolve30);
|
|
37428
37097
|
socket.once("error", reject);
|
|
37429
37098
|
});
|
|
37430
37099
|
} else {
|
|
37431
37100
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
37432
|
-
await new Promise((
|
|
37433
|
-
socket.once("connect",
|
|
37101
|
+
await new Promise((resolve30, reject) => {
|
|
37102
|
+
socket.once("connect", resolve30);
|
|
37434
37103
|
socket.once("error", reject);
|
|
37435
37104
|
});
|
|
37436
37105
|
}
|
|
@@ -37465,14 +37134,14 @@ var init_messenger = __esm({
|
|
|
37465
37134
|
const useTls = this.imapEncryption === "tls" || this.imapEncryption === "ssl" || this.imapEncryption === "" && this.imapPort === 993;
|
|
37466
37135
|
if (useTls) {
|
|
37467
37136
|
socket = tls2.connect({ host: this.imapHost, port: this.imapPort, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
37468
|
-
await new Promise((
|
|
37469
|
-
socket.once("secureConnect",
|
|
37137
|
+
await new Promise((resolve30, reject) => {
|
|
37138
|
+
socket.once("secureConnect", resolve30);
|
|
37470
37139
|
socket.once("error", reject);
|
|
37471
37140
|
});
|
|
37472
37141
|
} else {
|
|
37473
37142
|
socket = net3.createConnection({ host: this.imapHost, port: this.imapPort });
|
|
37474
|
-
await new Promise((
|
|
37475
|
-
socket.once("connect",
|
|
37143
|
+
await new Promise((resolve30, reject) => {
|
|
37144
|
+
socket.once("connect", resolve30);
|
|
37476
37145
|
socket.once("error", reject);
|
|
37477
37146
|
});
|
|
37478
37147
|
}
|
|
@@ -37509,7 +37178,7 @@ var init_messenger = __esm({
|
|
|
37509
37178
|
}
|
|
37510
37179
|
try {
|
|
37511
37180
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37512
|
-
const searchResp = await imapCommand(socket, "SEARCH ALL");
|
|
37181
|
+
const searchResp = await imapCommand(socket, "UID SEARCH ALL");
|
|
37513
37182
|
const uids = parseSearchResponse(searchResp);
|
|
37514
37183
|
if (uids.length === 0) return [];
|
|
37515
37184
|
uids.reverse();
|
|
@@ -37517,8 +37186,8 @@ var init_messenger = __esm({
|
|
|
37517
37186
|
if (selected.length === 0) return [];
|
|
37518
37187
|
const messages = [];
|
|
37519
37188
|
for (const uid of selected) {
|
|
37520
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
37521
|
-
messages.push(
|
|
37189
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
37190
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
37522
37191
|
}
|
|
37523
37192
|
return messages;
|
|
37524
37193
|
} catch (err) {
|
|
@@ -37528,7 +37197,7 @@ var init_messenger = __esm({
|
|
|
37528
37197
|
}
|
|
37529
37198
|
}
|
|
37530
37199
|
/**
|
|
37531
|
-
* Read a single message by
|
|
37200
|
+
* Read a single message by its IMAP UID.
|
|
37532
37201
|
*/
|
|
37533
37202
|
async read(uid, folder = "INBOX") {
|
|
37534
37203
|
let socket;
|
|
@@ -37539,11 +37208,11 @@ var init_messenger = __esm({
|
|
|
37539
37208
|
}
|
|
37540
37209
|
try {
|
|
37541
37210
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37542
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY[])`);
|
|
37211
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY[])`);
|
|
37543
37212
|
if (!/\{\d+\}/.test(fetchResp)) {
|
|
37544
37213
|
return null;
|
|
37545
37214
|
}
|
|
37546
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
37215
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
37547
37216
|
return parseFullMessage(uid, fetchResp);
|
|
37548
37217
|
} catch (err) {
|
|
37549
37218
|
throw imapFail("read", err);
|
|
@@ -37570,14 +37239,14 @@ var init_messenger = __esm({
|
|
|
37570
37239
|
}
|
|
37571
37240
|
try {
|
|
37572
37241
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37573
|
-
const searchResp = await imapCommand(socket, `SEARCH ${query}`);
|
|
37242
|
+
const searchResp = await imapCommand(socket, `UID SEARCH ${query}`);
|
|
37574
37243
|
const uids = parseSearchResponse(searchResp);
|
|
37575
37244
|
if (uids.length === 0) return [];
|
|
37576
37245
|
uids.reverse();
|
|
37577
37246
|
const messages = [];
|
|
37578
37247
|
for (const uid of uids.slice(0, limit)) {
|
|
37579
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
37580
|
-
messages.push(
|
|
37248
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
37249
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
37581
37250
|
}
|
|
37582
37251
|
return messages;
|
|
37583
37252
|
} catch (err) {
|
|
@@ -37587,26 +37256,46 @@ var init_messenger = __esm({
|
|
|
37587
37256
|
}
|
|
37588
37257
|
}
|
|
37589
37258
|
/**
|
|
37590
|
-
* Delete a message by UID.
|
|
37259
|
+
* Delete a message by UID (mark \Deleted, then EXPUNGE).
|
|
37260
|
+
*
|
|
37261
|
+
* `delete` is the one cross-framework name (python/php/ruby/node all spell it
|
|
37262
|
+
* `delete`). `deleteMessage` remains as a DEPRECATED alias for one release.
|
|
37591
37263
|
*/
|
|
37592
|
-
async
|
|
37264
|
+
async delete(uid, folder = "INBOX") {
|
|
37593
37265
|
const socket = await this.imapConnect();
|
|
37594
37266
|
try {
|
|
37595
37267
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37596
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Deleted)`);
|
|
37268
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Deleted)`);
|
|
37597
37269
|
await imapCommand(socket, "EXPUNGE");
|
|
37598
37270
|
} finally {
|
|
37599
37271
|
await this.imapDisconnect(socket);
|
|
37600
37272
|
}
|
|
37601
37273
|
}
|
|
37274
|
+
/** @deprecated Use {@link delete} — kept as an alias for one release (G7). */
|
|
37275
|
+
async deleteMessage(uid, folder = "INBOX") {
|
|
37276
|
+
return this.delete(uid, folder);
|
|
37277
|
+
}
|
|
37602
37278
|
/**
|
|
37603
|
-
* Mark a message as read.
|
|
37279
|
+
* Mark a message as read (+FLAGS \Seen).
|
|
37604
37280
|
*/
|
|
37605
37281
|
async markRead(uid, folder = "INBOX") {
|
|
37606
37282
|
const socket = await this.imapConnect();
|
|
37607
37283
|
try {
|
|
37608
37284
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37609
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
37285
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
37286
|
+
} finally {
|
|
37287
|
+
await this.imapDisconnect(socket);
|
|
37288
|
+
}
|
|
37289
|
+
}
|
|
37290
|
+
/**
|
|
37291
|
+
* Mark a message as unread (-FLAGS \Seen) — the inverse of markRead (G7,
|
|
37292
|
+
* parity with Python's mark_unread).
|
|
37293
|
+
*/
|
|
37294
|
+
async markUnread(uid, folder = "INBOX") {
|
|
37295
|
+
const socket = await this.imapConnect();
|
|
37296
|
+
try {
|
|
37297
|
+
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37298
|
+
await imapCommand(socket, `UID STORE ${uid} -FLAGS (\\Seen)`);
|
|
37610
37299
|
} finally {
|
|
37611
37300
|
await this.imapDisconnect(socket);
|
|
37612
37301
|
}
|
|
@@ -37623,7 +37312,7 @@ var init_messenger = __esm({
|
|
|
37623
37312
|
}
|
|
37624
37313
|
try {
|
|
37625
37314
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
37626
|
-
const searchResp = await imapCommand(socket, "SEARCH UNSEEN");
|
|
37315
|
+
const searchResp = await imapCommand(socket, "UID SEARCH UNSEEN");
|
|
37627
37316
|
return parseSearchResponse(searchResp).length;
|
|
37628
37317
|
} catch (err) {
|
|
37629
37318
|
throw imapFail("unread", err);
|
|
@@ -38379,17 +38068,17 @@ __export(ai_exports, {
|
|
|
38379
38068
|
skillBlock: () => skillBlock,
|
|
38380
38069
|
writeOrMerge: () => writeOrMerge
|
|
38381
38070
|
});
|
|
38382
|
-
import { existsSync as
|
|
38071
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync19, writeFileSync as writeFileSync17, readFileSync as readFileSync25 } from "node:fs";
|
|
38383
38072
|
import { homedir } from "node:os";
|
|
38384
|
-
import { join as
|
|
38073
|
+
import { join as join29, resolve as resolve20, relative as relative10, dirname as dirname13 } from "node:path";
|
|
38385
38074
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
38386
38075
|
import { execSync as execSync2, execFileSync as execFileSync3 } from "node:child_process";
|
|
38387
38076
|
import { createInterface } from "node:readline";
|
|
38388
38077
|
function readVersion() {
|
|
38389
38078
|
try {
|
|
38390
|
-
const thisDir =
|
|
38391
|
-
const rootPkg =
|
|
38392
|
-
const pkg = JSON.parse(
|
|
38079
|
+
const thisDir = dirname13(fileURLToPath7(import.meta.url));
|
|
38080
|
+
const rootPkg = resolve20(thisDir, "..", "..", "..", "package.json");
|
|
38081
|
+
const pkg = JSON.parse(readFileSync25(rootPkg, "utf-8"));
|
|
38393
38082
|
return pkg.version ?? "0.0.0";
|
|
38394
38083
|
} catch {
|
|
38395
38084
|
return "0.0.0";
|
|
@@ -38438,8 +38127,8 @@ function downloadSkillsSync(jobs) {
|
|
|
38438
38127
|
function installSkills(root = ".", targets) {
|
|
38439
38128
|
const ref = skillsRef();
|
|
38440
38129
|
const dests = targets ?? [
|
|
38441
|
-
|
|
38442
|
-
|
|
38130
|
+
join29(resolve20(root), ".claude", "skills"),
|
|
38131
|
+
join29(homedir(), ".claude", "skills")
|
|
38443
38132
|
];
|
|
38444
38133
|
const jobs = [];
|
|
38445
38134
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -38457,9 +38146,9 @@ function installSkills(root = ".", targets) {
|
|
|
38457
38146
|
const base = `https://raw.githubusercontent.com/tina4stack/${spec.repo}/${ref}/.claude/skills/${skill}`;
|
|
38458
38147
|
skillMdUrl[skill] = `${base}/SKILL.md`;
|
|
38459
38148
|
for (const dest of dests) {
|
|
38460
|
-
add(`${base}/SKILL.md`,
|
|
38149
|
+
add(`${base}/SKILL.md`, join29(dest, skill, "SKILL.md"));
|
|
38461
38150
|
for (const r of spec.references) {
|
|
38462
|
-
add(`${base}/references/${r}`,
|
|
38151
|
+
add(`${base}/references/${r}`, join29(dest, skill, "references", r));
|
|
38463
38152
|
}
|
|
38464
38153
|
}
|
|
38465
38154
|
}
|
|
@@ -38471,10 +38160,10 @@ function installSkills(root = ".", targets) {
|
|
|
38471
38160
|
return installed;
|
|
38472
38161
|
}
|
|
38473
38162
|
function isInstalled(root, tool) {
|
|
38474
|
-
return
|
|
38163
|
+
return existsSync26(join29(resolve20(root), tool.contextFile));
|
|
38475
38164
|
}
|
|
38476
38165
|
function showMenu(root = ".") {
|
|
38477
|
-
const r =
|
|
38166
|
+
const r = resolve20(root);
|
|
38478
38167
|
console.log("\n Tina4 AI Context Installer\n");
|
|
38479
38168
|
for (let i = 0; i < AI_TOOLS.length; i++) {
|
|
38480
38169
|
const tool = AI_TOOLS[i];
|
|
@@ -38492,16 +38181,16 @@ function showMenu(root = ".") {
|
|
|
38492
38181
|
const tina4AiMarker = tina4AiInstalled ? ` ${GREEN2}[installed]${RESET2}` : "";
|
|
38493
38182
|
console.log(` 8. Install tina4-ai tools (requires Python)${tina4AiMarker}`);
|
|
38494
38183
|
console.log();
|
|
38495
|
-
return new Promise((
|
|
38184
|
+
return new Promise((resolve30) => {
|
|
38496
38185
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
38497
38186
|
rl.question(" Select (comma-separated, or 'all'): ", (answer) => {
|
|
38498
38187
|
rl.close();
|
|
38499
|
-
|
|
38188
|
+
resolve30(answer.trim());
|
|
38500
38189
|
});
|
|
38501
38190
|
});
|
|
38502
38191
|
}
|
|
38503
38192
|
function installSelected(root, selection) {
|
|
38504
|
-
const rootPath =
|
|
38193
|
+
const rootPath = resolve20(root);
|
|
38505
38194
|
const created = [];
|
|
38506
38195
|
let indices;
|
|
38507
38196
|
let doInstallTina4Ai = false;
|
|
@@ -38594,33 +38283,33 @@ function looksLikeOldFrameworkInstall(existing) {
|
|
|
38594
38283
|
function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
38595
38284
|
const block = skillBlock(contextFile);
|
|
38596
38285
|
const [start2, end] = markersFor(contextFile);
|
|
38597
|
-
if (!
|
|
38598
|
-
|
|
38286
|
+
if (!existsSync26(contextPath)) {
|
|
38287
|
+
writeFileSync17(contextPath, frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
38599
38288
|
return "Installed";
|
|
38600
38289
|
}
|
|
38601
|
-
const existing =
|
|
38290
|
+
const existing = readFileSync25(contextPath, "utf-8");
|
|
38602
38291
|
if (hasMarkers(existing, start2, end)) {
|
|
38603
|
-
|
|
38292
|
+
writeFileSync17(contextPath, replaceMarkerBlock(existing, block, start2, end), "utf-8");
|
|
38604
38293
|
return "Refreshed skill block in";
|
|
38605
38294
|
}
|
|
38606
38295
|
if (looksLikeOldFrameworkInstall(existing)) {
|
|
38607
38296
|
const head = existing.replace(/^\s+/, "");
|
|
38608
38297
|
const preamble = existing.slice(0, existing.length - head.length);
|
|
38609
38298
|
const newContent = (preamble.trim() ? preamble.replace(/\s+$/, "") + "\n\n" : "") + frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n";
|
|
38610
|
-
|
|
38299
|
+
writeFileSync17(contextPath, newContent, "utf-8");
|
|
38611
38300
|
return "Migrated (replaced old framework dump in)";
|
|
38612
38301
|
}
|
|
38613
|
-
|
|
38302
|
+
writeFileSync17(contextPath, existing.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
38614
38303
|
return "Appended skill block to";
|
|
38615
38304
|
}
|
|
38616
38305
|
function installForTool(root, tool, context) {
|
|
38617
38306
|
const created = [];
|
|
38618
|
-
const contextPath =
|
|
38307
|
+
const contextPath = join29(root, tool.contextFile);
|
|
38619
38308
|
if (tool.configDir) {
|
|
38620
|
-
|
|
38309
|
+
mkdirSync19(join29(root, tool.configDir), { recursive: true });
|
|
38621
38310
|
}
|
|
38622
|
-
const parentDir =
|
|
38623
|
-
|
|
38311
|
+
const parentDir = dirname13(contextPath);
|
|
38312
|
+
mkdirSync19(parentDir, { recursive: true });
|
|
38624
38313
|
const action = writeOrMerge(contextPath, tool.contextFile, context);
|
|
38625
38314
|
const rel = relative10(root, contextPath);
|
|
38626
38315
|
created.push(rel);
|
|
@@ -38655,7 +38344,7 @@ function installTina4Ai() {
|
|
|
38655
38344
|
function installClaudeSkills(root) {
|
|
38656
38345
|
const created = [];
|
|
38657
38346
|
for (const skill of installSkills(root)) {
|
|
38658
|
-
created.push(
|
|
38347
|
+
created.push(join29(".claude", "skills", skill));
|
|
38659
38348
|
console.log(` ${GREEN2}\u2713${RESET2} Installed .claude/skills/${skill} (project + global)`);
|
|
38660
38349
|
}
|
|
38661
38350
|
return created;
|
|
@@ -38996,11 +38685,11 @@ import { tests, assertEqual, runAll } from "tina4-nodejs";
|
|
|
38996
38685
|
}
|
|
38997
38686
|
function generateClaudeCodeContext() {
|
|
38998
38687
|
try {
|
|
38999
|
-
const thisDir =
|
|
39000
|
-
const repoRoot =
|
|
39001
|
-
const claudeMdPath =
|
|
39002
|
-
if (
|
|
39003
|
-
return
|
|
38688
|
+
const thisDir = dirname13(fileURLToPath7(import.meta.url));
|
|
38689
|
+
const repoRoot = resolve20(thisDir, "..", "..", "..");
|
|
38690
|
+
const claudeMdPath = join29(repoRoot, "CLAUDE.md");
|
|
38691
|
+
if (existsSync26(claudeMdPath)) {
|
|
38692
|
+
return readFileSync25(claudeMdPath, "utf-8");
|
|
39004
38693
|
}
|
|
39005
38694
|
} catch {
|
|
39006
38695
|
}
|
|
@@ -40992,7 +40681,6 @@ __export(src_exports3, {
|
|
|
40992
40681
|
RouteRef: () => RouteRef,
|
|
40993
40682
|
Router: () => Router,
|
|
40994
40683
|
SafeString: () => SafeString2,
|
|
40995
|
-
ScssCompiler: () => ScssCompiler,
|
|
40996
40684
|
SecurityHeadersMiddleware: () => SecurityHeadersMiddleware,
|
|
40997
40685
|
ServiceRunner: () => ServiceRunner,
|
|
40998
40686
|
Session: () => Session,
|
|
@@ -41189,7 +40877,6 @@ var init_src3 = __esm({
|
|
|
41189
40877
|
init_session();
|
|
41190
40878
|
init_i18n();
|
|
41191
40879
|
init_fakeData();
|
|
41192
|
-
init_scss();
|
|
41193
40880
|
init_queue();
|
|
41194
40881
|
init_job();
|
|
41195
40882
|
init_mqtt();
|
|
@@ -41464,20 +41151,20 @@ ${cdStep} npm install
|
|
|
41464
41151
|
}
|
|
41465
41152
|
|
|
41466
41153
|
// src/commands/serve.ts
|
|
41467
|
-
import { resolve as
|
|
41468
|
-
import { existsSync as
|
|
41154
|
+
import { resolve as resolve21 } from "node:path";
|
|
41155
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
41469
41156
|
async function serveProject(options) {
|
|
41470
41157
|
if (options.noReload) {
|
|
41471
41158
|
process.env.TINA4_NO_RELOAD = "true";
|
|
41472
41159
|
}
|
|
41473
41160
|
const port = options.port ?? 7148;
|
|
41474
41161
|
const cwd = process.cwd();
|
|
41475
|
-
const routesDir =
|
|
41476
|
-
const ormDir =
|
|
41477
|
-
const modelsDir =
|
|
41478
|
-
const templatesDir =
|
|
41479
|
-
const staticDir =
|
|
41480
|
-
if (!
|
|
41162
|
+
const routesDir = resolve21(cwd, "src/routes");
|
|
41163
|
+
const ormDir = resolve21(cwd, "src/orm");
|
|
41164
|
+
const modelsDir = resolve21(cwd, "src/models");
|
|
41165
|
+
const templatesDir = resolve21(cwd, "src/templates");
|
|
41166
|
+
const staticDir = resolve21(cwd, "public");
|
|
41167
|
+
if (!existsSync27(routesDir) && !existsSync27(modelsDir) && !existsSync27(ormDir)) {
|
|
41481
41168
|
console.error(" Error: Not a Tina4 project. Run this from a project created with 'tina4 init'.");
|
|
41482
41169
|
process.exit(1);
|
|
41483
41170
|
}
|
|
@@ -41493,12 +41180,12 @@ async function serveProject(options) {
|
|
|
41493
41180
|
|
|
41494
41181
|
// src/commands/migrate.ts
|
|
41495
41182
|
init_dotenv();
|
|
41496
|
-
import { existsSync as
|
|
41497
|
-
import { join as
|
|
41183
|
+
import { existsSync as existsSync28, readdirSync as readdirSync19, readFileSync as readFileSync26 } from "node:fs";
|
|
41184
|
+
import { join as join30, resolve as resolve22 } from "node:path";
|
|
41498
41185
|
async function runMigrations(migrationDir) {
|
|
41499
41186
|
loadEnv();
|
|
41500
|
-
const dir =
|
|
41501
|
-
if (!
|
|
41187
|
+
const dir = resolve22(migrationDir ?? "migrations");
|
|
41188
|
+
if (!existsSync28(dir)) {
|
|
41502
41189
|
console.log(" No migrations/ directory found. Nothing to run.");
|
|
41503
41190
|
return;
|
|
41504
41191
|
}
|
|
@@ -41529,7 +41216,7 @@ async function runMigrations(migrationDir) {
|
|
|
41529
41216
|
process.exit(1);
|
|
41530
41217
|
}
|
|
41531
41218
|
await ensureMigrationTable2();
|
|
41532
|
-
const files =
|
|
41219
|
+
const files = readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql")).sort((a, b) => {
|
|
41533
41220
|
const aMatch = a.match(/^(\d+)/);
|
|
41534
41221
|
const bMatch = b.match(/^(\d+)/);
|
|
41535
41222
|
if (aMatch && bMatch) {
|
|
@@ -41551,7 +41238,7 @@ async function runMigrations(migrationDir) {
|
|
|
41551
41238
|
if (await isMigrationApplied2(name)) {
|
|
41552
41239
|
continue;
|
|
41553
41240
|
}
|
|
41554
|
-
const sql =
|
|
41241
|
+
const sql = readFileSync26(join30(dir, file), "utf-8").trim();
|
|
41555
41242
|
if (!sql) continue;
|
|
41556
41243
|
console.log(` Migrating: ${file}`);
|
|
41557
41244
|
const adapter = getAdapter2();
|
|
@@ -41576,17 +41263,17 @@ async function runMigrations(migrationDir) {
|
|
|
41576
41263
|
}
|
|
41577
41264
|
|
|
41578
41265
|
// src/commands/migrateCreate.ts
|
|
41579
|
-
import { existsSync as
|
|
41580
|
-
import { join as
|
|
41266
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync20, writeFileSync as writeFileSync18 } from "node:fs";
|
|
41267
|
+
import { join as join31, resolve as resolve23 } from "node:path";
|
|
41581
41268
|
async function createMigration2(description) {
|
|
41582
41269
|
if (!description) {
|
|
41583
41270
|
console.error(" Usage: tina4 migrate:create <description>");
|
|
41584
41271
|
console.error(' Example: tina4 migrate:create "create users table"');
|
|
41585
41272
|
process.exit(1);
|
|
41586
41273
|
}
|
|
41587
|
-
const dir =
|
|
41588
|
-
if (!
|
|
41589
|
-
|
|
41274
|
+
const dir = resolve23("migrations");
|
|
41275
|
+
if (!existsSync29(dir)) {
|
|
41276
|
+
mkdirSync20(dir, { recursive: true });
|
|
41590
41277
|
}
|
|
41591
41278
|
const safeName = description.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
41592
41279
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -41600,8 +41287,8 @@ async function createMigration2(description) {
|
|
|
41600
41287
|
].join("");
|
|
41601
41288
|
const upFileName = `${timestamp2}_${safeName}.sql`;
|
|
41602
41289
|
const downFileName = `${timestamp2}_${safeName}.down.sql`;
|
|
41603
|
-
const upPath =
|
|
41604
|
-
const downPath =
|
|
41290
|
+
const upPath = join31(dir, upFileName);
|
|
41291
|
+
const downPath = join31(dir, downFileName);
|
|
41605
41292
|
const upTemplate = `-- Migration: ${description}
|
|
41606
41293
|
-- Created: ${now.toISOString()}
|
|
41607
41294
|
|
|
@@ -41610,8 +41297,8 @@ async function createMigration2(description) {
|
|
|
41610
41297
|
-- Created: ${now.toISOString()}
|
|
41611
41298
|
|
|
41612
41299
|
`;
|
|
41613
|
-
|
|
41614
|
-
|
|
41300
|
+
writeFileSync18(upPath, upTemplate, "utf-8");
|
|
41301
|
+
writeFileSync18(downPath, downTemplate, "utf-8");
|
|
41615
41302
|
console.log(` Created migration: ${upFileName}`);
|
|
41616
41303
|
console.log(` Created rollback: ${downFileName}`);
|
|
41617
41304
|
console.log(` Path: ${dir}`);
|
|
@@ -41619,10 +41306,10 @@ async function createMigration2(description) {
|
|
|
41619
41306
|
|
|
41620
41307
|
// src/commands/migrateStatus.ts
|
|
41621
41308
|
init_dotenv();
|
|
41622
|
-
import { resolve as
|
|
41309
|
+
import { resolve as resolve24 } from "node:path";
|
|
41623
41310
|
async function migrateStatus(migrationDir) {
|
|
41624
41311
|
loadEnv();
|
|
41625
|
-
const dir =
|
|
41312
|
+
const dir = resolve24(migrationDir ?? "migrations");
|
|
41626
41313
|
let initDatabase2;
|
|
41627
41314
|
let ensureMigrationTable2;
|
|
41628
41315
|
let statusFn;
|
|
@@ -41667,10 +41354,10 @@ async function migrateStatus(migrationDir) {
|
|
|
41667
41354
|
|
|
41668
41355
|
// src/commands/migrateRollback.ts
|
|
41669
41356
|
init_dotenv();
|
|
41670
|
-
import { resolve as
|
|
41357
|
+
import { resolve as resolve25 } from "node:path";
|
|
41671
41358
|
async function migrateRollback(migrationDir) {
|
|
41672
41359
|
loadEnv();
|
|
41673
|
-
const dir =
|
|
41360
|
+
const dir = resolve25(migrationDir ?? "migrations");
|
|
41674
41361
|
let initDatabase2;
|
|
41675
41362
|
let ensureMigrationTable2;
|
|
41676
41363
|
let rollbackFn;
|
|
@@ -41710,11 +41397,11 @@ async function migrateRollback(migrationDir) {
|
|
|
41710
41397
|
}
|
|
41711
41398
|
|
|
41712
41399
|
// src/commands/routes.ts
|
|
41713
|
-
import { existsSync as
|
|
41714
|
-
import { resolve as
|
|
41400
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
41401
|
+
import { resolve as resolve26 } from "node:path";
|
|
41715
41402
|
async function listRoutes() {
|
|
41716
|
-
const routesDir =
|
|
41717
|
-
if (!
|
|
41403
|
+
const routesDir = resolve26("src/routes");
|
|
41404
|
+
if (!existsSync30(routesDir)) {
|
|
41718
41405
|
console.error(" No src/routes/ directory found. Are you in a Tina4 project?");
|
|
41719
41406
|
process.exit(1);
|
|
41720
41407
|
}
|
|
@@ -41754,14 +41441,14 @@ async function listRoutes() {
|
|
|
41754
41441
|
}
|
|
41755
41442
|
|
|
41756
41443
|
// src/commands/test.ts
|
|
41757
|
-
import { existsSync as
|
|
41758
|
-
import { resolve as
|
|
41444
|
+
import { existsSync as existsSync31, readdirSync as readdirSync21 } from "node:fs";
|
|
41445
|
+
import { resolve as resolve27, join as join32 } from "node:path";
|
|
41759
41446
|
import { execSync as execSync3 } from "node:child_process";
|
|
41760
41447
|
async function runTests(testPath) {
|
|
41761
41448
|
const cwd = process.cwd();
|
|
41762
41449
|
if (testPath) {
|
|
41763
|
-
const file =
|
|
41764
|
-
if (!
|
|
41450
|
+
const file = resolve27(testPath);
|
|
41451
|
+
if (!existsSync31(file)) {
|
|
41765
41452
|
console.error(` Error: Test file not found: ${testPath}`);
|
|
41766
41453
|
process.exit(1);
|
|
41767
41454
|
}
|
|
@@ -41781,14 +41468,14 @@ async function runTests(testPath) {
|
|
|
41781
41468
|
];
|
|
41782
41469
|
let testFiles = [];
|
|
41783
41470
|
for (const candidate of candidates) {
|
|
41784
|
-
const fullPath =
|
|
41785
|
-
if (!
|
|
41471
|
+
const fullPath = resolve27(cwd, candidate);
|
|
41472
|
+
if (!existsSync31(fullPath)) continue;
|
|
41786
41473
|
if (candidate.endsWith(".ts")) {
|
|
41787
41474
|
testFiles.push(fullPath);
|
|
41788
41475
|
break;
|
|
41789
41476
|
}
|
|
41790
41477
|
try {
|
|
41791
|
-
const files =
|
|
41478
|
+
const files = readdirSync21(fullPath).filter((f) => f.endsWith(".ts")).map((f) => join32(fullPath, f));
|
|
41792
41479
|
testFiles.push(...files);
|
|
41793
41480
|
} catch {
|
|
41794
41481
|
}
|
|
@@ -41817,8 +41504,8 @@ async function runTests(testPath) {
|
|
|
41817
41504
|
}
|
|
41818
41505
|
|
|
41819
41506
|
// src/commands/generate.ts
|
|
41820
|
-
import { existsSync as
|
|
41821
|
-
import { join as
|
|
41507
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync21, writeFileSync as writeFileSync19 } from "node:fs";
|
|
41508
|
+
import { join as join33, resolve as resolve28 } from "node:path";
|
|
41822
41509
|
var FIELD_TYPE_MAP = {
|
|
41823
41510
|
string: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
|
|
41824
41511
|
str: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
|
|
@@ -41835,16 +41522,16 @@ var FIELD_TYPE_MAP = {
|
|
|
41835
41522
|
blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" }
|
|
41836
41523
|
};
|
|
41837
41524
|
function ensureDir(dir) {
|
|
41838
|
-
if (!
|
|
41839
|
-
|
|
41525
|
+
if (!existsSync32(dir)) {
|
|
41526
|
+
mkdirSync21(dir, { recursive: true });
|
|
41840
41527
|
}
|
|
41841
41528
|
}
|
|
41842
41529
|
function writeFileSafe(path8, content) {
|
|
41843
|
-
if (
|
|
41530
|
+
if (existsSync32(path8)) {
|
|
41844
41531
|
console.log(` File already exists: ${path8}`);
|
|
41845
41532
|
return;
|
|
41846
41533
|
}
|
|
41847
|
-
|
|
41534
|
+
writeFileSync19(path8, content, "utf-8");
|
|
41848
41535
|
console.log(` Created ${path8}`);
|
|
41849
41536
|
}
|
|
41850
41537
|
function toSnake(name) {
|
|
@@ -42001,9 +41688,9 @@ async function generate2(what, name, extraArgs = []) {
|
|
|
42001
41688
|
function generateModel(name, flags, emitTest = true) {
|
|
42002
41689
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
42003
41690
|
const table2 = toTableName(name);
|
|
42004
|
-
const dir =
|
|
41691
|
+
const dir = resolve28("src/models");
|
|
42005
41692
|
ensureDir(dir);
|
|
42006
|
-
const path8 =
|
|
41693
|
+
const path8 = join33(dir, `${name}.ts`);
|
|
42007
41694
|
const fieldLines = [
|
|
42008
41695
|
` id: { type: "integer" as const, primaryKey: true, autoIncrement: true },`
|
|
42009
41696
|
];
|
|
@@ -42037,8 +41724,8 @@ function generateRoute(name, flags, emitTest = true) {
|
|
|
42037
41724
|
const singular = routePath.endsWith("s") ? routePath.slice(0, -1) : routePath;
|
|
42038
41725
|
const model = flags.model;
|
|
42039
41726
|
const isPublic = Boolean(flags.public);
|
|
42040
|
-
const base =
|
|
42041
|
-
const idDir =
|
|
41727
|
+
const base = resolve28("src/routes/api", routePath);
|
|
41728
|
+
const idDir = join33(base, "[id]");
|
|
42042
41729
|
ensureDir(base);
|
|
42043
41730
|
ensureDir(idDir);
|
|
42044
41731
|
const table2 = model ? toTableName(model) : "";
|
|
@@ -42049,7 +41736,7 @@ function generateRoute(name, flags, emitTest = true) {
|
|
|
42049
41736
|
const writeDoc = isPublic ? "Public (--public): no token required." : "Secure by default: requires a Bearer token (use --public to open).";
|
|
42050
41737
|
if (model) {
|
|
42051
41738
|
writeFileSafe(
|
|
42052
|
-
|
|
41739
|
+
join33(base, "get.ts"),
|
|
42053
41740
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42054
41741
|
${modelImportBase}
|
|
42055
41742
|
export const meta = { summary: "List all ${routePath}", tags: ["${routePath}"] };
|
|
@@ -42065,7 +41752,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42065
41752
|
);
|
|
42066
41753
|
} else {
|
|
42067
41754
|
writeFileSafe(
|
|
42068
|
-
|
|
41755
|
+
join33(base, "get.ts"),
|
|
42069
41756
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42070
41757
|
|
|
42071
41758
|
export const meta = { summary: "List all ${routePath}", tags: ["${routePath}"] };
|
|
@@ -42084,7 +41771,7 @@ ${aiFill(`list_${routePath}`, {
|
|
|
42084
41771
|
}
|
|
42085
41772
|
if (model) {
|
|
42086
41773
|
writeFileSafe(
|
|
42087
|
-
|
|
41774
|
+
join33(base, "post.ts"),
|
|
42088
41775
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42089
41776
|
${modelImportBase}${secureOptOut(isPublic)}export const meta = { summary: "Create a new ${singular}", tags: ["${routePath}"] };
|
|
42090
41777
|
|
|
@@ -42106,7 +41793,7 @@ ${extend(
|
|
|
42106
41793
|
);
|
|
42107
41794
|
} else {
|
|
42108
41795
|
writeFileSafe(
|
|
42109
|
-
|
|
41796
|
+
join33(base, "post.ts"),
|
|
42110
41797
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42111
41798
|
|
|
42112
41799
|
${secureOptOut(isPublic)}export const meta = { summary: "Create a new ${singular}", tags: ["${routePath}"] };
|
|
@@ -42126,7 +41813,7 @@ ${aiFill(`create_${singular}`, {
|
|
|
42126
41813
|
}
|
|
42127
41814
|
if (model) {
|
|
42128
41815
|
writeFileSafe(
|
|
42129
|
-
|
|
41816
|
+
join33(idDir, "get.ts"),
|
|
42130
41817
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42131
41818
|
${modelImportId}
|
|
42132
41819
|
export const meta = { summary: "Get a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -42144,7 +41831,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42144
41831
|
);
|
|
42145
41832
|
} else {
|
|
42146
41833
|
writeFileSafe(
|
|
42147
|
-
|
|
41834
|
+
join33(idDir, "get.ts"),
|
|
42148
41835
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42149
41836
|
|
|
42150
41837
|
export const meta = { summary: "Get a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -42163,7 +41850,7 @@ ${aiFill(`get_${singular}`, {
|
|
|
42163
41850
|
}
|
|
42164
41851
|
if (model) {
|
|
42165
41852
|
writeFileSafe(
|
|
42166
|
-
|
|
41853
|
+
join33(idDir, "put.ts"),
|
|
42167
41854
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42168
41855
|
${modelImportId}${secureOptOut(isPublic)}export const meta = { summary: "Update a ${singular} by ID", tags: ["${routePath}"] };
|
|
42169
41856
|
|
|
@@ -42191,7 +41878,7 @@ ${extend(
|
|
|
42191
41878
|
);
|
|
42192
41879
|
} else {
|
|
42193
41880
|
writeFileSafe(
|
|
42194
|
-
|
|
41881
|
+
join33(idDir, "put.ts"),
|
|
42195
41882
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42196
41883
|
|
|
42197
41884
|
${secureOptOut(isPublic)}export const meta = { summary: "Update a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -42211,7 +41898,7 @@ ${aiFill(`update_${singular}`, {
|
|
|
42211
41898
|
}
|
|
42212
41899
|
if (model) {
|
|
42213
41900
|
writeFileSafe(
|
|
42214
|
-
|
|
41901
|
+
join33(idDir, "delete.ts"),
|
|
42215
41902
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42216
41903
|
${modelImportId}${secureOptOut(isPublic)}export const meta = { summary: "Delete a ${singular} by ID", tags: ["${routePath}"] };
|
|
42217
41904
|
|
|
@@ -42230,7 +41917,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42230
41917
|
);
|
|
42231
41918
|
} else {
|
|
42232
41919
|
writeFileSafe(
|
|
42233
|
-
|
|
41920
|
+
join33(idDir, "delete.ts"),
|
|
42234
41921
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42235
41922
|
|
|
42236
41923
|
${secureOptOut(isPublic)}export const meta = { summary: "Delete a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -42275,7 +41962,7 @@ function generateCrud(name, flags) {
|
|
|
42275
41962
|
}
|
|
42276
41963
|
function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest = true) {
|
|
42277
41964
|
const ts = timestamp();
|
|
42278
|
-
const dir =
|
|
41965
|
+
const dir = resolve28("migrations");
|
|
42279
41966
|
ensureDir(dir);
|
|
42280
41967
|
let table2;
|
|
42281
41968
|
if (tableOverride) {
|
|
@@ -42287,7 +41974,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
|
|
|
42287
41974
|
const fields = fieldsOverride || parseFields(flags.fields || "");
|
|
42288
41975
|
const isCreate = name.startsWith("create_") || fieldsOverride !== void 0;
|
|
42289
41976
|
const fileName = `${ts}_${name}.sql`;
|
|
42290
|
-
const path8 =
|
|
41977
|
+
const path8 = join33(dir, fileName);
|
|
42291
41978
|
let upSql;
|
|
42292
41979
|
let downSql;
|
|
42293
41980
|
if (isCreate) {
|
|
@@ -42319,7 +42006,7 @@ ${upSql}
|
|
|
42319
42006
|
${downSql}
|
|
42320
42007
|
`;
|
|
42321
42008
|
writeFileSafe(path8, content);
|
|
42322
|
-
const downPath =
|
|
42009
|
+
const downPath = join33(dir, `${ts}_${name}.down.sql`);
|
|
42323
42010
|
const downContent = `-- Rollback: ${name}
|
|
42324
42011
|
-- Created: ${now}
|
|
42325
42012
|
|
|
@@ -42330,9 +42017,9 @@ ${downSql}
|
|
|
42330
42017
|
}
|
|
42331
42018
|
function generateMiddleware(name, _flags) {
|
|
42332
42019
|
const snake = toSnake(name);
|
|
42333
|
-
const dir =
|
|
42020
|
+
const dir = resolve28("src/middleware");
|
|
42334
42021
|
ensureDir(dir);
|
|
42335
|
-
const path8 =
|
|
42022
|
+
const path8 = join33(dir, `${snake}.ts`);
|
|
42336
42023
|
const content = `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42337
42024
|
|
|
42338
42025
|
/**
|
|
@@ -42371,9 +42058,9 @@ function generateTest(name, flags) {
|
|
|
42371
42058
|
const snake = toSnake(name);
|
|
42372
42059
|
const singular = snake.endsWith("s") ? snake.slice(0, -1) : snake;
|
|
42373
42060
|
const model = flags.model;
|
|
42374
|
-
const dir =
|
|
42061
|
+
const dir = resolve28("tests");
|
|
42375
42062
|
ensureDir(dir);
|
|
42376
|
-
const path8 =
|
|
42063
|
+
const path8 = join33(dir, `${snake}.test.ts`);
|
|
42377
42064
|
if (model && flags["secure-writes"]) {
|
|
42378
42065
|
const isPublic = Boolean(flags.public);
|
|
42379
42066
|
const posture = isPublic ? "open (--public)" : "gated";
|
|
@@ -42508,9 +42195,9 @@ function generateForm(name, flags) {
|
|
|
42508
42195
|
datetime: "datetime-local",
|
|
42509
42196
|
blob: "file"
|
|
42510
42197
|
};
|
|
42511
|
-
const dir =
|
|
42198
|
+
const dir = resolve28("src/templates/forms");
|
|
42512
42199
|
ensureDir(dir);
|
|
42513
|
-
const path8 =
|
|
42200
|
+
const path8 = join33(dir, `${table2}.twig`);
|
|
42514
42201
|
let fieldHtml = "";
|
|
42515
42202
|
for (const [fname, ftype] of fields) {
|
|
42516
42203
|
const itype = inputTypes[ftype] || "text";
|
|
@@ -42560,9 +42247,9 @@ function generateView(name, flags) {
|
|
|
42560
42247
|
const table2 = toTableName(name);
|
|
42561
42248
|
const routeName = toPlural(table2);
|
|
42562
42249
|
const cols = fields.map(([f]) => f);
|
|
42563
|
-
const dir =
|
|
42250
|
+
const dir = resolve28("src/templates/pages");
|
|
42564
42251
|
ensureDir(dir);
|
|
42565
|
-
const listPath =
|
|
42252
|
+
const listPath = join33(dir, `${routeName}.twig`);
|
|
42566
42253
|
const th = cols.map((c) => ` <th>${c.replace(/_/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase())}</th>`).join("\n");
|
|
42567
42254
|
const td = cols.map((c) => ` <td>{{ item.${c} }}</td>`).join("\n");
|
|
42568
42255
|
const listContent = `{% extends "base.twig" %}
|
|
@@ -42598,7 +42285,7 @@ ${td}
|
|
|
42598
42285
|
{% endblock %}
|
|
42599
42286
|
`;
|
|
42600
42287
|
writeFileSafe(listPath, listContent);
|
|
42601
|
-
const detailPath =
|
|
42288
|
+
const detailPath = join33(dir, `${table2}.twig`);
|
|
42602
42289
|
const detailFields = cols.map((c) => ` <div class="mb-3"><strong>${c.replace(/_/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase())}:</strong> {{ item.${c} }}</div>`).join("\n");
|
|
42603
42290
|
const detailContent = `{% extends "base.twig" %}
|
|
42604
42291
|
{% block title %}${name} Detail{% endblock %}
|
|
@@ -42620,14 +42307,14 @@ ${detailFields}
|
|
|
42620
42307
|
function generateAuth(_flags) {
|
|
42621
42308
|
console.log("\n Generating authentication scaffolding...\n");
|
|
42622
42309
|
generateModel("User", { fields: "email:string,password:string,role:string" }, false);
|
|
42623
|
-
const registerDir =
|
|
42624
|
-
const loginDir =
|
|
42625
|
-
const meDir =
|
|
42310
|
+
const registerDir = resolve28("src/routes/api/auth/register");
|
|
42311
|
+
const loginDir = resolve28("src/routes/api/auth/login");
|
|
42312
|
+
const meDir = resolve28("src/routes/api/auth/me");
|
|
42626
42313
|
ensureDir(registerDir);
|
|
42627
42314
|
ensureDir(loginDir);
|
|
42628
42315
|
ensureDir(meDir);
|
|
42629
42316
|
writeFileSafe(
|
|
42630
|
-
|
|
42317
|
+
join33(registerDir, "post.ts"),
|
|
42631
42318
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42632
42319
|
import { hashPassword } from "tina4-nodejs";
|
|
42633
42320
|
import User from "../../../../models/User.js";
|
|
@@ -42658,7 +42345,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42658
42345
|
`
|
|
42659
42346
|
);
|
|
42660
42347
|
writeFileSafe(
|
|
42661
|
-
|
|
42348
|
+
join33(loginDir, "post.ts"),
|
|
42662
42349
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42663
42350
|
import { checkPassword, getToken } from "tina4-nodejs";
|
|
42664
42351
|
import User from "../../../../models/User.js";
|
|
@@ -42689,7 +42376,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42689
42376
|
`
|
|
42690
42377
|
);
|
|
42691
42378
|
writeFileSafe(
|
|
42692
|
-
|
|
42379
|
+
join33(meDir, "get.ts"),
|
|
42693
42380
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
42694
42381
|
import { authenticateRequest } from "tina4-nodejs";
|
|
42695
42382
|
|
|
@@ -42705,10 +42392,10 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42705
42392
|
}
|
|
42706
42393
|
`
|
|
42707
42394
|
);
|
|
42708
|
-
const formsDir =
|
|
42395
|
+
const formsDir = resolve28("src/templates/forms");
|
|
42709
42396
|
ensureDir(formsDir);
|
|
42710
42397
|
writeFileSafe(
|
|
42711
|
-
|
|
42398
|
+
join33(formsDir, "login.twig"),
|
|
42712
42399
|
`{% extends "base.twig" %}
|
|
42713
42400
|
{% block title %}Login{% endblock %}
|
|
42714
42401
|
{% block content %}
|
|
@@ -42732,7 +42419,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
42732
42419
|
`
|
|
42733
42420
|
);
|
|
42734
42421
|
writeFileSafe(
|
|
42735
|
-
|
|
42422
|
+
join33(formsDir, "register.twig"),
|
|
42736
42423
|
`{% extends "base.twig" %}
|
|
42737
42424
|
{% block title %}Register{% endblock %}
|
|
42738
42425
|
{% block content %}
|
|
@@ -42766,9 +42453,9 @@ function generateService(name, flags) {
|
|
|
42766
42453
|
const snake = toSnake(name);
|
|
42767
42454
|
const camel = toCamel(toPascal(name)) || snake;
|
|
42768
42455
|
const cron = flags.cron;
|
|
42769
|
-
const dir =
|
|
42456
|
+
const dir = resolve28("src/services");
|
|
42770
42457
|
ensureDir(dir);
|
|
42771
|
-
const path8 =
|
|
42458
|
+
const path8 = join33(dir, `${snake}.ts`);
|
|
42772
42459
|
let scheduleField;
|
|
42773
42460
|
let note;
|
|
42774
42461
|
if (cron && cron !== true) {
|
|
@@ -42817,9 +42504,9 @@ function generateQueue(name, _flags) {
|
|
|
42817
42504
|
const topic = name.replace(/^\//, "");
|
|
42818
42505
|
const slug = toSnake(topic.replace(/[^0-9a-zA-Z]+/g, "_")).replace(/^_+|_+$/g, "") || "topic";
|
|
42819
42506
|
const pascal = toPascal(topic) || "Topic";
|
|
42820
|
-
const dir =
|
|
42507
|
+
const dir = resolve28("src/services");
|
|
42821
42508
|
ensureDir(dir);
|
|
42822
|
-
const path8 =
|
|
42509
|
+
const path8 = join33(dir, `${slug}_consumer.ts`);
|
|
42823
42510
|
const body = aiFill(`handle${pascal}`, {
|
|
42824
42511
|
intent: `process ONE ${topic} job payload`,
|
|
42825
42512
|
given: "payload -> the produced job data (job.payload)",
|
|
@@ -42877,9 +42564,9 @@ export default {
|
|
|
42877
42564
|
emitQueueTest(topic, slug, pascal);
|
|
42878
42565
|
}
|
|
42879
42566
|
function generateValidator(name, _flags) {
|
|
42880
|
-
const dir =
|
|
42567
|
+
const dir = resolve28("src/validators");
|
|
42881
42568
|
ensureDir(dir);
|
|
42882
|
-
const path8 =
|
|
42569
|
+
const path8 = join33(dir, `${toSnake(name)}.ts`);
|
|
42883
42570
|
const rules = extend(
|
|
42884
42571
|
"add / adjust the validation rules for this payload",
|
|
42885
42572
|
`e.g. .email("email").minLength("name", 2).integer("age"); ground: tina4_context("validate request body with Validator", "nodejs")`
|
|
@@ -42904,9 +42591,9 @@ ${rules} validator.required("name"); // starter rule (matches the model's def
|
|
|
42904
42591
|
}
|
|
42905
42592
|
function generateSeeder(name, _flags) {
|
|
42906
42593
|
const table2 = toTableName(name);
|
|
42907
|
-
const dir =
|
|
42594
|
+
const dir = resolve28("src/seeds");
|
|
42908
42595
|
ensureDir(dir);
|
|
42909
|
-
const path8 =
|
|
42596
|
+
const path8 = join33(dir, `${table2}_seeder.ts`);
|
|
42910
42597
|
const overrides = extend(
|
|
42911
42598
|
"override fields that need a specific shape (seedOrm auto-fills the rest)",
|
|
42912
42599
|
`e.g. return { email: (f) => f.email(), status: "active" }; ground: tina4_context("seed ORM model with FakeData", "nodejs")`
|
|
@@ -42948,9 +42635,9 @@ function generateWebsocket(name, _flags) {
|
|
|
42948
42635
|
let slug = toSnake(raw.replace(/^\/+|\/+$/g, "").replace(/[^0-9a-zA-Z]+/g, "_")).replace(/^_+|_+$/g, "") || "ws";
|
|
42949
42636
|
const base = slug.startsWith("ws_") ? slug.slice(3) : slug;
|
|
42950
42637
|
const handlerName = `${toCamel(toPascal(base))}Ws`;
|
|
42951
|
-
const dir =
|
|
42638
|
+
const dir = resolve28("src/routes");
|
|
42952
42639
|
ensureDir(dir);
|
|
42953
|
-
const path8 =
|
|
42640
|
+
const path8 = join33(dir, `ws_${base}.ts`);
|
|
42954
42641
|
const body = aiFill(handlerName, {
|
|
42955
42642
|
intent: `handle an inbound "message" frame on ${wsPath}`,
|
|
42956
42643
|
given: "data -> the message payload (string); connection -> WebSocketConnection",
|
|
@@ -42997,9 +42684,9 @@ function generateListener(name, _flags) {
|
|
|
42997
42684
|
const event = name.trim();
|
|
42998
42685
|
const slug = toSnake(event.replace(/[^0-9a-zA-Z]+/g, "_")).replace(/^_+|_+$/g, "") || "event";
|
|
42999
42686
|
const handlerName = `on${toPascal(slug)}`;
|
|
43000
|
-
const dir =
|
|
42687
|
+
const dir = resolve28("src/listeners");
|
|
43001
42688
|
ensureDir(dir);
|
|
43002
|
-
const path8 =
|
|
42689
|
+
const path8 = join33(dir, `${slug}.ts`);
|
|
43003
42690
|
const body = aiFill(handlerName, {
|
|
43004
42691
|
intent: `react to the '${event}' event`,
|
|
43005
42692
|
given: `args -> whatever Events.emit("${event}", ...args) passed`,
|
|
@@ -43028,9 +42715,9 @@ Events.on("${event}", ${handlerName});
|
|
|
43028
42715
|
emitListenerTest(event, slug);
|
|
43029
42716
|
}
|
|
43030
42717
|
function writeTest(testName, content) {
|
|
43031
|
-
const dir =
|
|
42718
|
+
const dir = resolve28("tests");
|
|
43032
42719
|
ensureDir(dir);
|
|
43033
|
-
writeFileSafe(
|
|
42720
|
+
writeFileSafe(join33(dir, `${testName}.test.ts`), content);
|
|
43034
42721
|
}
|
|
43035
42722
|
function standaloneTest(doc, body) {
|
|
43036
42723
|
return `${doc}
|
|
@@ -43414,15 +43101,15 @@ assert("DOWN drops the ${table2} table", db.tableExists("${table2}") === false);
|
|
|
43414
43101
|
}
|
|
43415
43102
|
|
|
43416
43103
|
// src/commands/seed.ts
|
|
43417
|
-
import { existsSync as
|
|
43418
|
-
import { resolve as
|
|
43104
|
+
import { existsSync as existsSync33, readdirSync as readdirSync22 } from "node:fs";
|
|
43105
|
+
import { resolve as resolve29, join as join34 } from "node:path";
|
|
43419
43106
|
import { execSync as execSync4 } from "node:child_process";
|
|
43420
43107
|
async function runSeeds(seedPath) {
|
|
43421
43108
|
const cwd = process.cwd();
|
|
43422
|
-
const seedDir =
|
|
43109
|
+
const seedDir = resolve29(cwd, "src/seeds");
|
|
43423
43110
|
if (seedPath) {
|
|
43424
|
-
const file =
|
|
43425
|
-
if (!
|
|
43111
|
+
const file = resolve29(seedPath);
|
|
43112
|
+
if (!existsSync33(file)) {
|
|
43426
43113
|
console.error(` Error: Seed file not found: ${seedPath}`);
|
|
43427
43114
|
process.exit(1);
|
|
43428
43115
|
}
|
|
@@ -43435,14 +43122,14 @@ async function runSeeds(seedPath) {
|
|
|
43435
43122
|
}
|
|
43436
43123
|
return;
|
|
43437
43124
|
}
|
|
43438
|
-
if (!
|
|
43125
|
+
if (!existsSync33(seedDir)) {
|
|
43439
43126
|
console.log(" No seeds directory found.");
|
|
43440
43127
|
console.log(" Create seed files in src/seeds/ (e.g. src/seeds/001-users.ts)");
|
|
43441
43128
|
return;
|
|
43442
43129
|
}
|
|
43443
43130
|
let seedFiles;
|
|
43444
43131
|
try {
|
|
43445
|
-
seedFiles =
|
|
43132
|
+
seedFiles = readdirSync22(seedDir).filter((f) => f.endsWith(".ts")).sort().map((f) => join34(seedDir, f));
|
|
43446
43133
|
} catch {
|
|
43447
43134
|
console.log(" Could not read src/seeds/ directory.");
|
|
43448
43135
|
return;
|
|
@@ -43580,8 +43267,8 @@ function runMetrics(args = []) {
|
|
|
43580
43267
|
// src/commands/queue.ts
|
|
43581
43268
|
init_dotenv();
|
|
43582
43269
|
init_queue();
|
|
43583
|
-
import { readdirSync as
|
|
43584
|
-
import { extname as extname9, join as
|
|
43270
|
+
import { readdirSync as readdirSync23, statSync as statSync19 } from "node:fs";
|
|
43271
|
+
import { extname as extname9, join as join35 } from "node:path";
|
|
43585
43272
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
43586
43273
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["once", "json"]);
|
|
43587
43274
|
function parseFlags2(args) {
|
|
@@ -43613,7 +43300,7 @@ async function resolveQueueHandler(servicesDir, topic) {
|
|
|
43613
43300
|
let entries;
|
|
43614
43301
|
try {
|
|
43615
43302
|
if (!statSync19(servicesDir).isDirectory()) return null;
|
|
43616
|
-
entries =
|
|
43303
|
+
entries = readdirSync23(servicesDir);
|
|
43617
43304
|
} catch {
|
|
43618
43305
|
return null;
|
|
43619
43306
|
}
|
|
@@ -43621,7 +43308,7 @@ async function resolveQueueHandler(servicesDir, topic) {
|
|
|
43621
43308
|
if (entry.startsWith("_")) continue;
|
|
43622
43309
|
const ext = extname9(entry);
|
|
43623
43310
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
43624
|
-
const fullPath =
|
|
43311
|
+
const fullPath = join35(servicesDir, entry);
|
|
43625
43312
|
try {
|
|
43626
43313
|
if (!statSync19(fullPath).isFile()) continue;
|
|
43627
43314
|
const mod = await import(pathToFileURL2(fullPath).href);
|
|
@@ -43773,8 +43460,8 @@ async function queueCommand(args = []) {
|
|
|
43773
43460
|
}
|
|
43774
43461
|
|
|
43775
43462
|
// src/commands/build.ts
|
|
43776
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as
|
|
43777
|
-
import { basename as
|
|
43463
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync34, statSync as statSync20 } from "node:fs";
|
|
43464
|
+
import { basename as basename7, delimiter as delimiter2, join as join36 } from "node:path";
|
|
43778
43465
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
43779
43466
|
function parseFlags3(args) {
|
|
43780
43467
|
const flags = {};
|
|
@@ -43803,7 +43490,7 @@ function whichDocker() {
|
|
|
43803
43490
|
for (const dir of pathValue.split(delimiter2)) {
|
|
43804
43491
|
if (!dir) continue;
|
|
43805
43492
|
for (const ext of exts) {
|
|
43806
|
-
const candidate =
|
|
43493
|
+
const candidate = join36(dir, `docker${ext}`);
|
|
43807
43494
|
try {
|
|
43808
43495
|
accessSync2(candidate, constants2.X_OK);
|
|
43809
43496
|
if (statSync20(candidate).isFile()) return candidate;
|
|
@@ -43817,11 +43504,11 @@ function buildImage(args) {
|
|
|
43817
43504
|
const flags = parseFlags3(args);
|
|
43818
43505
|
let tag = typeof flags.tag === "string" ? flags.tag : "";
|
|
43819
43506
|
if (!tag) {
|
|
43820
|
-
const dirName =
|
|
43507
|
+
const dirName = basename7(process.cwd()).toLowerCase();
|
|
43821
43508
|
tag = `${dirName || "tina4app"}:latest`;
|
|
43822
43509
|
}
|
|
43823
43510
|
const dockerfile = typeof flags.file === "string" && flags.file ? flags.file : "Dockerfile";
|
|
43824
|
-
if (!
|
|
43511
|
+
if (!existsSync34(dockerfile) || !statSync20(dockerfile).isFile()) {
|
|
43825
43512
|
console.log(` \u2717 No ${dockerfile} found.`);
|
|
43826
43513
|
console.log(" A Tina4 app deploys as a container. Scaffold a Dockerfile first:");
|
|
43827
43514
|
console.log(" tina4 deploy docker (or: tina4nodejs init)");
|
|
@@ -43849,30 +43536,30 @@ function buildImage(args) {
|
|
|
43849
43536
|
|
|
43850
43537
|
// src/bin.ts
|
|
43851
43538
|
import { execSync as execSync5, spawnSync as spawnSync4 } from "node:child_process";
|
|
43852
|
-
import { existsSync as
|
|
43853
|
-
import { delimiter as delimiter3, dirname as
|
|
43539
|
+
import { existsSync as existsSync35, readFileSync as readFileSync27, statSync as statSync21 } from "node:fs";
|
|
43540
|
+
import { delimiter as delimiter3, dirname as dirname14, join as join37 } from "node:path";
|
|
43854
43541
|
import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL3 } from "node:url";
|
|
43855
43542
|
function readCliVersion() {
|
|
43856
|
-
let dir =
|
|
43543
|
+
let dir = dirname14(fileURLToPath8(import.meta.url));
|
|
43857
43544
|
for (let i = 0; i < 6; i++) {
|
|
43858
|
-
const pkgPath =
|
|
43859
|
-
if (
|
|
43545
|
+
const pkgPath = join37(dir, "package.json");
|
|
43546
|
+
if (existsSync35(pkgPath)) {
|
|
43860
43547
|
try {
|
|
43861
|
-
const pkg = JSON.parse(
|
|
43548
|
+
const pkg = JSON.parse(readFileSync27(pkgPath, "utf-8"));
|
|
43862
43549
|
if (typeof pkg.version === "string" && pkg.version) return pkg.version;
|
|
43863
43550
|
} catch {
|
|
43864
43551
|
}
|
|
43865
43552
|
}
|
|
43866
|
-
const parent =
|
|
43553
|
+
const parent = dirname14(dir);
|
|
43867
43554
|
if (parent === dir) break;
|
|
43868
43555
|
dir = parent;
|
|
43869
43556
|
}
|
|
43870
43557
|
return "0.0.0";
|
|
43871
43558
|
}
|
|
43872
43559
|
function inContainer() {
|
|
43873
|
-
if (
|
|
43560
|
+
if (existsSync35("/.dockerenv") || existsSync35("/run/.containerenv")) return true;
|
|
43874
43561
|
try {
|
|
43875
|
-
const blob =
|
|
43562
|
+
const blob = readFileSync27("/proc/1/cgroup", "utf-8");
|
|
43876
43563
|
return blob.includes("docker") || blob.includes("containerd") || blob.includes("kubepods");
|
|
43877
43564
|
} catch {
|
|
43878
43565
|
return false;
|
|
@@ -44017,7 +43704,7 @@ async function openConsole() {
|
|
|
44017
43704
|
r.context.Database = Database2;
|
|
44018
43705
|
r.context.Log = Log2;
|
|
44019
43706
|
r.context.db = db;
|
|
44020
|
-
await new Promise((
|
|
43707
|
+
await new Promise((resolve30) => r.on("exit", resolve30));
|
|
44021
43708
|
}
|
|
44022
43709
|
async function installAiContext(args) {
|
|
44023
43710
|
const { showMenu: showMenu2, installSelected: installSelected2, installAll: installAll2 } = await Promise.resolve().then(() => (init_ai(), ai_exports));
|
|
@@ -44175,7 +43862,7 @@ function findClient() {
|
|
|
44175
43862
|
for (const dir of (process.env.PATH ?? "").split(delimiter3)) {
|
|
44176
43863
|
if (!dir) continue;
|
|
44177
43864
|
for (const name of names) {
|
|
44178
|
-
const candidate =
|
|
43865
|
+
const candidate = join37(dir, name);
|
|
44179
43866
|
try {
|
|
44180
43867
|
if (statSync21(candidate).isFile()) return candidate;
|
|
44181
43868
|
} catch {
|