tina4-nodejs 3.13.141 → 3.13.143
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 +28 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +817 -538
- package/packages/cli/src/bin.ts +4 -14
- package/packages/cli/src/commands/build.ts +2 -18
- package/packages/cli/src/commands/generate.ts +9 -24
- package/packages/cli/src/commands/init.ts +18 -8
- package/packages/cli/src/commands/lint.ts +3 -13
- package/packages/cli/src/commands/migrate.ts +2 -6
- package/packages/cli/src/commands/migrateRollback.ts +2 -6
- package/packages/cli/src/commands/migrateStatus.ts +2 -6
- package/packages/cli/src/commands/queue.ts +2 -30
- package/packages/cli/src/util.ts +90 -0
- package/packages/core/dist/index.js +592 -262
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +27 -11
- package/packages/core/gallery/queue/src/lib/queueDb.ts +11 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +2 -5
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +2 -5
- package/packages/core/gallery/queue/src/routes/gallery/queue/get.ts +20 -7
- package/packages/core/gallery/templates/src/templates/gallery_page.twig +34 -15
- package/packages/core/src/csp.ts +138 -0
- package/packages/core/src/devAdmin.ts +3 -12
- package/packages/core/src/dispatchPipeline.ts +2 -18
- package/packages/core/src/errorOverlay.ts +80 -37
- package/packages/core/src/etag.ts +24 -0
- package/packages/core/src/feedback.ts +2 -11
- package/packages/core/src/index.ts +10 -0
- package/packages/core/src/middleware.ts +20 -6
- package/packages/core/src/response.ts +14 -3
- package/packages/core/src/server.ts +52 -15
- package/packages/core/src/session.ts +174 -71
- package/packages/core/src/sessionHandlers/respSessionHandler.ts +94 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +22 -73
- package/packages/core/src/sso.ts +1 -1
- package/packages/core/src/static.ts +1 -13
- package/packages/core/src/types.ts +8 -0
- package/packages/core/src/upstreamResponse.ts +31 -0
- package/packages/core/src/websocket.ts +48 -7
- package/packages/core/templates/errors/302.twig +1 -1
- package/packages/core/templates/errors/401.twig +2 -2
- package/packages/core/templates/errors/403.twig +1 -1
- package/packages/core/templates/errors/404.twig +1 -1
- package/packages/core/templates/errors/500.twig +1 -1
- package/packages/core/templates/errors/502.twig +2 -2
- package/packages/core/templates/errors/503.twig +2 -2
- package/packages/core/templates/errors/base.twig +4 -1
- package/packages/frond/dist/index.js +2 -0
- package/packages/frond/src/engine.ts +23 -0
- package/packages/orm/dist/index.js +593 -271
- package/packages/orm/src/graph/adapters/bolt.ts +8 -29
- package/packages/orm/src/graph/adapters/rowMapping.ts +57 -0
- package/packages/orm/src/graph/adapters/ultipa.ts +8 -30
- package/packages/swagger/dist/index.js +4 -4
- package/packages/swagger/src/ui.ts +7 -4
- package/types/cli/src/util.d.ts +28 -0
- package/types/core/src/csp.d.ts +42 -0
- package/types/core/src/etag.d.ts +9 -0
- package/types/core/src/index.d.ts +1 -0
- package/types/core/src/middleware.d.ts +6 -0
- package/types/core/src/session.d.ts +64 -21
- package/types/core/src/sessionHandlers/respSessionHandler.d.ts +52 -0
- package/types/core/src/sessionHandlers/valkeyHandler.d.ts +2 -26
- package/types/core/src/types.d.ts +8 -0
- package/types/core/src/upstreamResponse.d.ts +10 -0
- package/types/core/src/websocket.d.ts +21 -0
- package/types/frond/src/engine.d.ts +7 -0
- package/types/orm/src/graph/adapters/rowMapping.d.ts +26 -0
package/packages/cli/dist/bin.js
CHANGED
|
@@ -1221,8 +1221,8 @@ function hashPassword(password, salt, iterations = 26e4) {
|
|
|
1221
1221
|
}
|
|
1222
1222
|
function checkPassword(password, hash) {
|
|
1223
1223
|
try {
|
|
1224
|
-
const
|
|
1225
|
-
const parts = hash.split(
|
|
1224
|
+
const delimiter4 = hash.includes("$") ? "$" : ":";
|
|
1225
|
+
const parts = hash.split(delimiter4);
|
|
1226
1226
|
if (parts.length !== 4 || parts[0] !== "pbkdf2_sha256") return false;
|
|
1227
1227
|
const iterations = parseInt(parts[1], 10);
|
|
1228
1228
|
const salt = parts[2];
|
|
@@ -8980,7 +8980,7 @@ async function removeMigrationRecord(name) {
|
|
|
8980
8980
|
[name]
|
|
8981
8981
|
);
|
|
8982
8982
|
}
|
|
8983
|
-
async function rollback(migrationsDir,
|
|
8983
|
+
async function rollback(migrationsDir, delimiter4) {
|
|
8984
8984
|
if (migrationsDir instanceof Map) {
|
|
8985
8985
|
const downFunctions = migrationsDir;
|
|
8986
8986
|
const migrations2 = await getLastBatchMigrations();
|
|
@@ -8999,7 +8999,7 @@ async function rollback(migrationsDir, delimiter5) {
|
|
|
8999
8999
|
return rolledBack2;
|
|
9000
9000
|
}
|
|
9001
9001
|
const dir = resolve4(migrationsDir ?? "migrations");
|
|
9002
|
-
const delim =
|
|
9002
|
+
const delim = delimiter4 ?? ";";
|
|
9003
9003
|
const db = getAdapter();
|
|
9004
9004
|
const migrations = await getLastBatchMigrations();
|
|
9005
9005
|
const rolledBack = [];
|
|
@@ -9115,13 +9115,13 @@ function consumeSqlDelimiter(state) {
|
|
|
9115
9115
|
}
|
|
9116
9116
|
return true;
|
|
9117
9117
|
}
|
|
9118
|
-
function splitStatements(sql,
|
|
9118
|
+
function splitStatements(sql, delimiter4 = ";") {
|
|
9119
9119
|
const state = {
|
|
9120
9120
|
sql: normalizeQuotes(sql),
|
|
9121
9121
|
statements: [],
|
|
9122
9122
|
current: "",
|
|
9123
|
-
delimiter:
|
|
9124
|
-
dlen:
|
|
9123
|
+
delimiter: delimiter4,
|
|
9124
|
+
dlen: delimiter4.length,
|
|
9125
9125
|
i: 0,
|
|
9126
9126
|
inDollarBlock: false,
|
|
9127
9127
|
inSlashBlock: false
|
|
@@ -9160,7 +9160,7 @@ function warnUnprefixedMigrations(files) {
|
|
|
9160
9160
|
async function migrate(adapter, options) {
|
|
9161
9161
|
const db = adapter ?? getAdapter();
|
|
9162
9162
|
const dir = resolve4(options?.migrationsDir ?? "migrations");
|
|
9163
|
-
const
|
|
9163
|
+
const delimiter4 = options?.delimiter ?? ";";
|
|
9164
9164
|
const result = { applied: [], skipped: [], failed: [] };
|
|
9165
9165
|
if (!existsSync5(dir)) {
|
|
9166
9166
|
return result;
|
|
@@ -9203,7 +9203,7 @@ async function migrate(adapter, options) {
|
|
|
9203
9203
|
result.skipped.push(file);
|
|
9204
9204
|
continue;
|
|
9205
9205
|
}
|
|
9206
|
-
const statements = splitStatements(sqlContent,
|
|
9206
|
+
const statements = splitStatements(sqlContent, delimiter4);
|
|
9207
9207
|
try {
|
|
9208
9208
|
await adapterStartTransaction(db);
|
|
9209
9209
|
for (const stmt of statements) {
|
|
@@ -13696,6 +13696,36 @@ var init_shapes = __esm({
|
|
|
13696
13696
|
}
|
|
13697
13697
|
});
|
|
13698
13698
|
|
|
13699
|
+
// ../orm/src/graph/adapters/rowMapping.ts
|
|
13700
|
+
function errorMessage(exc) {
|
|
13701
|
+
if (exc instanceof Error) return exc.message;
|
|
13702
|
+
return String(exc);
|
|
13703
|
+
}
|
|
13704
|
+
function nodeFromRow(row) {
|
|
13705
|
+
if (row === null || row === void 0) return null;
|
|
13706
|
+
return new GraphNode(
|
|
13707
|
+
String(row.id),
|
|
13708
|
+
row.labels ?? [],
|
|
13709
|
+
row.props ?? {}
|
|
13710
|
+
);
|
|
13711
|
+
}
|
|
13712
|
+
function edgeFromRow(row) {
|
|
13713
|
+
if (row === null || row === void 0) return null;
|
|
13714
|
+
return new GraphEdge(
|
|
13715
|
+
String(row.id),
|
|
13716
|
+
String(row.type),
|
|
13717
|
+
String(row.f),
|
|
13718
|
+
String(row.t),
|
|
13719
|
+
row.props ?? {}
|
|
13720
|
+
);
|
|
13721
|
+
}
|
|
13722
|
+
var init_rowMapping = __esm({
|
|
13723
|
+
"../orm/src/graph/adapters/rowMapping.ts"() {
|
|
13724
|
+
"use strict";
|
|
13725
|
+
init_shapes();
|
|
13726
|
+
}
|
|
13727
|
+
});
|
|
13728
|
+
|
|
13699
13729
|
// ../orm/src/graph/connectTimeout.ts
|
|
13700
13730
|
function resolveGraphConnectTimeout() {
|
|
13701
13731
|
const raw = (process.env[GRAPH_CONNECT_TIMEOUT_VARIABLE] ?? "").trim();
|
|
@@ -13735,15 +13765,12 @@ function propClause(properties) {
|
|
|
13735
13765
|
for (const key of keys) params[`p_${key}`] = props[key];
|
|
13736
13766
|
return { clause: `{${pairs}}`, params };
|
|
13737
13767
|
}
|
|
13738
|
-
function errorMessage(exc) {
|
|
13739
|
-
if (exc instanceof Error) return exc.message;
|
|
13740
|
-
return String(exc);
|
|
13741
|
-
}
|
|
13742
13768
|
var DRIVER_PACKAGE, driver, UltipaClient, UltipaConnectError, UNBOUNDED_CONNECT_SECONDS, UltipaGraphAdapter;
|
|
13743
13769
|
var init_ultipa = __esm({
|
|
13744
13770
|
async "../orm/src/graph/adapters/ultipa.ts"() {
|
|
13745
13771
|
"use strict";
|
|
13746
13772
|
init_shapes();
|
|
13773
|
+
init_rowMapping();
|
|
13747
13774
|
init_errors();
|
|
13748
13775
|
init_connectTimeout2();
|
|
13749
13776
|
DRIVER_PACKAGE = "tina4-ultipa";
|
|
@@ -13801,12 +13828,7 @@ var init_ultipa = __esm({
|
|
|
13801
13828
|
}
|
|
13802
13829
|
// -- portable node/edge/traverse core (GQL) ----------------------------
|
|
13803
13830
|
nodeFromRow(row) {
|
|
13804
|
-
|
|
13805
|
-
return new GraphNode(
|
|
13806
|
-
String(row.id),
|
|
13807
|
-
row.labels ?? [],
|
|
13808
|
-
row.props ?? {}
|
|
13809
|
-
);
|
|
13831
|
+
return nodeFromRow(row);
|
|
13810
13832
|
}
|
|
13811
13833
|
async addNode(label, properties = null) {
|
|
13812
13834
|
const { clause, params } = propClause(properties);
|
|
@@ -13820,15 +13842,7 @@ var init_ultipa = __esm({
|
|
|
13820
13842
|
params.to_id = toId;
|
|
13821
13843
|
const gql = `MATCH (a), (b) WHERE id(a) = $from_id AND id(b) = $to_id INSERT (a)-[e:\`${type2}\` ${clause}]->(b) RETURN id(e) AS id, type(e) AS type, id(a) AS f, id(b) AS t, properties(e) AS props`;
|
|
13822
13844
|
const rows = (await this.run(gql, params, false)).dicts();
|
|
13823
|
-
|
|
13824
|
-
const row = rows[0];
|
|
13825
|
-
return new GraphEdge(
|
|
13826
|
-
String(row.id),
|
|
13827
|
-
String(row.type),
|
|
13828
|
-
String(row.f),
|
|
13829
|
-
String(row.t),
|
|
13830
|
-
row.props ?? {}
|
|
13831
|
-
);
|
|
13845
|
+
return edgeFromRow(rows[0]);
|
|
13832
13846
|
}
|
|
13833
13847
|
async getNode(nodeId) {
|
|
13834
13848
|
const gql = `MATCH (n) WHERE id(n) = $id RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
|
|
@@ -13893,10 +13907,6 @@ var bolt_exports = {};
|
|
|
13893
13907
|
__export(bolt_exports, {
|
|
13894
13908
|
BoltGraphAdapter: () => BoltGraphAdapter
|
|
13895
13909
|
});
|
|
13896
|
-
function errorMessage2(exc) {
|
|
13897
|
-
if (exc instanceof Error) return exc.message;
|
|
13898
|
-
return String(exc);
|
|
13899
|
-
}
|
|
13900
13910
|
function boltId(id) {
|
|
13901
13911
|
const value = Number(id);
|
|
13902
13912
|
return Number.isNaN(value) ? -1 : value;
|
|
@@ -13906,6 +13916,7 @@ var init_bolt = __esm({
|
|
|
13906
13916
|
async "../orm/src/graph/adapters/bolt.ts"() {
|
|
13907
13917
|
"use strict";
|
|
13908
13918
|
init_shapes();
|
|
13919
|
+
init_rowMapping();
|
|
13909
13920
|
init_errors();
|
|
13910
13921
|
init_connectTimeout2();
|
|
13911
13922
|
DRIVER_PACKAGE2 = "neo4j-driver";
|
|
@@ -13941,7 +13952,7 @@ var init_bolt = __esm({
|
|
|
13941
13952
|
const result = await session.run(cypher, params ?? {});
|
|
13942
13953
|
return result.records.map((record) => record.toObject());
|
|
13943
13954
|
} catch (exc) {
|
|
13944
|
-
this.lastError =
|
|
13955
|
+
this.lastError = errorMessage(exc);
|
|
13945
13956
|
const code = exc?.code ?? "";
|
|
13946
13957
|
const message = this.lastError.toLowerCase();
|
|
13947
13958
|
if (code === "ServiceUnavailable" || code === neo4j.error?.SERVICE_UNAVAILABLE || message.includes("timed out") || message.includes("timeout")) {
|
|
@@ -13965,12 +13976,7 @@ var init_bolt = __esm({
|
|
|
13965
13976
|
}
|
|
13966
13977
|
// -- portable node/edge/traverse core (Cypher) -------------------------
|
|
13967
13978
|
nodeFromRow(row) {
|
|
13968
|
-
|
|
13969
|
-
return new GraphNode(
|
|
13970
|
-
String(row.id),
|
|
13971
|
-
row.labels ?? [],
|
|
13972
|
-
row.props ?? {}
|
|
13973
|
-
);
|
|
13979
|
+
return nodeFromRow(row);
|
|
13974
13980
|
}
|
|
13975
13981
|
async addNode(label, properties = null) {
|
|
13976
13982
|
const cypher = `CREATE (n:\`${label}\` $props) RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
|
|
@@ -13984,15 +13990,7 @@ var init_bolt = __esm({
|
|
|
13984
13990
|
to_id: boltId(toId),
|
|
13985
13991
|
props: properties ?? {}
|
|
13986
13992
|
});
|
|
13987
|
-
|
|
13988
|
-
const row = rows[0];
|
|
13989
|
-
return new GraphEdge(
|
|
13990
|
-
String(row.id),
|
|
13991
|
-
String(row.type),
|
|
13992
|
-
String(row.f),
|
|
13993
|
-
String(row.t),
|
|
13994
|
-
row.props ?? {}
|
|
13995
|
-
);
|
|
13993
|
+
return edgeFromRow(rows[0]);
|
|
13996
13994
|
}
|
|
13997
13995
|
async getNode(nodeId) {
|
|
13998
13996
|
const cypher = `MATCH (n) WHERE id(n) = $id RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
|
|
@@ -14058,7 +14056,7 @@ function cleanProps(doc) {
|
|
|
14058
14056
|
}
|
|
14059
14057
|
return props;
|
|
14060
14058
|
}
|
|
14061
|
-
function
|
|
14059
|
+
function errorMessage2(exc) {
|
|
14062
14060
|
if (exc instanceof Error) return exc.message;
|
|
14063
14061
|
return String(exc);
|
|
14064
14062
|
}
|
|
@@ -14097,14 +14095,14 @@ var init_arango = __esm({
|
|
|
14097
14095
|
this.db = new ArangoDatabase(config);
|
|
14098
14096
|
}
|
|
14099
14097
|
connectOrError(exc) {
|
|
14100
|
-
const text =
|
|
14098
|
+
const text = errorMessage2(exc).toLowerCase();
|
|
14101
14099
|
if (text.includes("timed out") || text.includes("timeout") || text.includes("connection") || text.includes("econnrefused") || text.includes("etimedout") || text.includes("max retries")) {
|
|
14102
14100
|
return new GraphConnectTimeout(
|
|
14103
14101
|
`Graph connect to ${this.url.host}:${this.url.port} timed out (${GRAPH_CONNECT_TIMEOUT_VARIABLE}). Raise ${GRAPH_CONNECT_TIMEOUT_VARIABLE} if the server is simply slow, or set it to 0 to wait indefinitely.`,
|
|
14104
14102
|
exc
|
|
14105
14103
|
);
|
|
14106
14104
|
}
|
|
14107
|
-
return new GraphError(
|
|
14105
|
+
return new GraphError(errorMessage2(exc), exc);
|
|
14108
14106
|
}
|
|
14109
14107
|
async ensureCollections() {
|
|
14110
14108
|
if (this.ensured) return;
|
|
@@ -14115,7 +14113,7 @@ var init_arango = __esm({
|
|
|
14115
14113
|
if (!await edges.exists()) await this.db.createEdgeCollection(EDGE_COLLECTION);
|
|
14116
14114
|
this.ensured = true;
|
|
14117
14115
|
} catch (exc) {
|
|
14118
|
-
this.lastError =
|
|
14116
|
+
this.lastError = errorMessage2(exc);
|
|
14119
14117
|
throw this.connectOrError(exc);
|
|
14120
14118
|
}
|
|
14121
14119
|
}
|
|
@@ -14125,7 +14123,7 @@ var init_arango = __esm({
|
|
|
14125
14123
|
const cursor = await this.db.query({ query, bindVars: bind2 ?? {} });
|
|
14126
14124
|
return await cursor.all();
|
|
14127
14125
|
} catch (exc) {
|
|
14128
|
-
this.lastError =
|
|
14126
|
+
this.lastError = errorMessage2(exc);
|
|
14129
14127
|
throw this.connectOrError(exc);
|
|
14130
14128
|
}
|
|
14131
14129
|
}
|
|
@@ -17433,6 +17431,73 @@ var init_trustedProxy = __esm({
|
|
|
17433
17431
|
}
|
|
17434
17432
|
});
|
|
17435
17433
|
|
|
17434
|
+
// ../core/src/csp.ts
|
|
17435
|
+
import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
|
|
17436
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
17437
|
+
function generateNonce() {
|
|
17438
|
+
return randomBytes4(16).toString("base64");
|
|
17439
|
+
}
|
|
17440
|
+
function runWithCspNonce(nonce, fn) {
|
|
17441
|
+
return nonceStore.run({ nonce }, fn);
|
|
17442
|
+
}
|
|
17443
|
+
function setCurrentNonce(nonce) {
|
|
17444
|
+
const store = nonceStore.getStore();
|
|
17445
|
+
if (store) store.nonce = nonce;
|
|
17446
|
+
else nonceFallback = nonce;
|
|
17447
|
+
}
|
|
17448
|
+
function clearCurrentNonce() {
|
|
17449
|
+
setCurrentNonce(void 0);
|
|
17450
|
+
}
|
|
17451
|
+
function currentCspNonce() {
|
|
17452
|
+
const store = nonceStore.getStore();
|
|
17453
|
+
let value = store ? store.nonce : nonceFallback;
|
|
17454
|
+
if (!value) {
|
|
17455
|
+
value = generateNonce();
|
|
17456
|
+
if (store) store.nonce = value;
|
|
17457
|
+
else nonceFallback = value;
|
|
17458
|
+
}
|
|
17459
|
+
return value;
|
|
17460
|
+
}
|
|
17461
|
+
function cspNonce() {
|
|
17462
|
+
return currentCspNonce();
|
|
17463
|
+
}
|
|
17464
|
+
function injectNonceIntoCsp(csp, nonce) {
|
|
17465
|
+
const token = `'nonce-${nonce}'`;
|
|
17466
|
+
const directives = [];
|
|
17467
|
+
for (const rawPart of csp.split(";")) {
|
|
17468
|
+
const part = rawPart.trim();
|
|
17469
|
+
if (!part) continue;
|
|
17470
|
+
const spaceAt = part.search(/\s/);
|
|
17471
|
+
const name = (spaceAt === -1 ? part : part.slice(0, spaceAt)).toLowerCase();
|
|
17472
|
+
const value = spaceAt === -1 ? "" : part.slice(spaceAt + 1).trim();
|
|
17473
|
+
directives.push([name, value]);
|
|
17474
|
+
}
|
|
17475
|
+
const defaultEntry = directives.find(([name]) => name === "default-src");
|
|
17476
|
+
const defaultValue = defaultEntry && defaultEntry[1] ? defaultEntry[1] : "'self'";
|
|
17477
|
+
for (const directive of ["style-src", "script-src"]) {
|
|
17478
|
+
const existing = directives.find(([name]) => name === directive);
|
|
17479
|
+
if (existing) {
|
|
17480
|
+
if (!existing[1].split(/\s+/).includes(token)) {
|
|
17481
|
+
existing[1] = `${existing[1]} ${token}`.trim();
|
|
17482
|
+
}
|
|
17483
|
+
} else {
|
|
17484
|
+
directives.push([directive, `${defaultValue} ${token}`.trim()]);
|
|
17485
|
+
}
|
|
17486
|
+
}
|
|
17487
|
+
return directives.map(([name, value]) => value ? `${name} ${value}` : name).join("; ");
|
|
17488
|
+
}
|
|
17489
|
+
function resolveCspHeader(nonce) {
|
|
17490
|
+
const csp = process.env.TINA4_CSP || "default-src 'self'";
|
|
17491
|
+
return injectNonceIntoCsp(csp, nonce);
|
|
17492
|
+
}
|
|
17493
|
+
var nonceStore, nonceFallback;
|
|
17494
|
+
var init_csp = __esm({
|
|
17495
|
+
"../core/src/csp.ts"() {
|
|
17496
|
+
"use strict";
|
|
17497
|
+
nonceStore = new AsyncLocalStorage3();
|
|
17498
|
+
}
|
|
17499
|
+
});
|
|
17500
|
+
|
|
17436
17501
|
// ../frond/src/engine.ts
|
|
17437
17502
|
var engine_exports = {};
|
|
17438
17503
|
__export(engine_exports, {
|
|
@@ -17442,9 +17507,10 @@ __export(engine_exports, {
|
|
|
17442
17507
|
expressionFormCache: () => expressionFormCache,
|
|
17443
17508
|
filterChainCache: () => filterChainCache,
|
|
17444
17509
|
pathParseCache: () => pathParseCache,
|
|
17510
|
+
setCspNonceProvider: () => setCspNonceProvider,
|
|
17445
17511
|
setFormTokenSessionId: () => setFormTokenSessionId
|
|
17446
17512
|
});
|
|
17447
|
-
import { createHash as createHash3, createHmac as createHmac3, randomBytes as
|
|
17513
|
+
import { createHash as createHash3, createHmac as createHmac3, randomBytes as randomBytes5 } from "node:crypto";
|
|
17448
17514
|
import { readFileSync as readFileSync6, existsSync as existsSync8, statSync as statSync6, realpathSync } from "node:fs";
|
|
17449
17515
|
import { join as join11, resolve as resolve7, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
|
|
17450
17516
|
function jsonText(value) {
|
|
@@ -18511,6 +18577,9 @@ function _b64url(data) {
|
|
|
18511
18577
|
function setFormTokenSessionId(sessionId) {
|
|
18512
18578
|
_formTokenSessionId = sessionId || "";
|
|
18513
18579
|
}
|
|
18580
|
+
function setCspNonceProvider(fn) {
|
|
18581
|
+
_cspNonceProvider = fn;
|
|
18582
|
+
}
|
|
18514
18583
|
function _buildFormTokenJwt(descriptor = "") {
|
|
18515
18584
|
const secret = process.env.TINA4_SECRET ?? "";
|
|
18516
18585
|
const secretBytes = Buffer.byteLength(secret, "utf8");
|
|
@@ -18522,7 +18591,7 @@ function _buildFormTokenJwt(descriptor = "") {
|
|
|
18522
18591
|
const ttlMinutes = parseInt(process.env.TINA4_TOKEN_LIMIT || "60", 10);
|
|
18523
18592
|
const header = { alg: "HS256", typ: "JWT" };
|
|
18524
18593
|
const now = Math.floor(Date.now() / 1e3);
|
|
18525
|
-
const payload = { type: "form", nonce:
|
|
18594
|
+
const payload = { type: "form", nonce: randomBytes5(8).toString("hex"), iat: now, exp: now + ttlMinutes * 60 };
|
|
18526
18595
|
if (descriptor) {
|
|
18527
18596
|
if (descriptor.includes("|")) {
|
|
18528
18597
|
const [ctx, ref] = descriptor.split("|", 2);
|
|
@@ -18549,7 +18618,7 @@ function _generateFormToken(descriptor = "") {
|
|
|
18549
18618
|
function _generateFormTokenValue(descriptor = "") {
|
|
18550
18619
|
return new SafeString(_buildFormTokenJwt(descriptor));
|
|
18551
18620
|
}
|
|
18552
|
-
var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FAST_FILTERS, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, EXPR_NOT_MATCHED, ARITHMETIC_OPERATIONS, EXPR_EVALUATORS, expressionFormCache, VarRef, JS_SAFE_RE, MEDIA_TYPE_RE, SANDBOX_EXPR_KEYWORDS, SANDBOX_IDENT_RE, SANDBOX_STRING_RE, UNSAFE_KEYS, BUILTIN_FILTERS, _formTokenSessionId, Frond;
|
|
18621
|
+
var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FAST_FILTERS, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, EXPR_NOT_MATCHED, ARITHMETIC_OPERATIONS, EXPR_EVALUATORS, expressionFormCache, VarRef, JS_SAFE_RE, MEDIA_TYPE_RE, SANDBOX_EXPR_KEYWORDS, SANDBOX_IDENT_RE, SANDBOX_STRING_RE, UNSAFE_KEYS, BUILTIN_FILTERS, _formTokenSessionId, _cspNonceProvider, Frond;
|
|
18553
18622
|
var init_engine = __esm({
|
|
18554
18623
|
"../frond/src/engine.ts"() {
|
|
18555
18624
|
"use strict";
|
|
@@ -18903,6 +18972,7 @@ var init_engine = __esm({
|
|
|
18903
18972
|
js_escape: (v) => new SafeString(jsEscape(v))
|
|
18904
18973
|
};
|
|
18905
18974
|
_formTokenSessionId = "";
|
|
18975
|
+
_cspNonceProvider = () => "";
|
|
18906
18976
|
Frond = class _Frond {
|
|
18907
18977
|
// ── Class-level registries ──────────────────────────────────
|
|
18908
18978
|
// Persist globals, filters, and tests across hot-reloads and at
|
|
@@ -19056,6 +19126,7 @@ var init_engine = __esm({
|
|
|
19056
19126
|
this.globals.formTokenValue = (descriptor) => _generateFormTokenValue(descriptor || "");
|
|
19057
19127
|
this.globals.form_token_value = (descriptor) => _generateFormTokenValue(descriptor || "");
|
|
19058
19128
|
this.globals.dump = (value) => renderDump(value);
|
|
19129
|
+
this.globals.csp_nonce = () => _cspNonceProvider();
|
|
19059
19130
|
for (const [k, v] of _Frond.classFilters) this.filters[k] = v;
|
|
19060
19131
|
for (const [k, v] of _Frond.classGlobals) this.globals[k] = v;
|
|
19061
19132
|
for (const [k, v] of _Frond.classTests) this.tests[k] = v;
|
|
@@ -20319,7 +20390,8 @@ async function getFrond() {
|
|
|
20319
20390
|
const dir = _defaultTemplatesDir ?? nodePath.resolve(process.cwd(), "src/templates");
|
|
20320
20391
|
let engine = _frondCache.get(dir);
|
|
20321
20392
|
if (!engine) {
|
|
20322
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20393
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20394
|
+
setCspNonceProvider2(currentCspNonce);
|
|
20323
20395
|
engine = new Frond2(dir);
|
|
20324
20396
|
_frondCache.set(dir, engine);
|
|
20325
20397
|
}
|
|
@@ -20330,7 +20402,8 @@ async function getFrameworkFrond() {
|
|
|
20330
20402
|
const frameworkDir = nodePath.resolve(nodePath.dirname(import.meta.url.replace("file://", "")), "..", "templates");
|
|
20331
20403
|
if (!_frameworkFrond && fs2.existsSync(frameworkDir)) {
|
|
20332
20404
|
try {
|
|
20333
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20405
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20406
|
+
setCspNonceProvider2(currentCspNonce);
|
|
20334
20407
|
_frameworkFrond = new Frond2(frameworkDir);
|
|
20335
20408
|
} catch {
|
|
20336
20409
|
return null;
|
|
@@ -20430,6 +20503,7 @@ function createResponse(res) {
|
|
|
20430
20503
|
return response;
|
|
20431
20504
|
};
|
|
20432
20505
|
response.raw = res;
|
|
20506
|
+
response.cspNonce = currentCspNonce();
|
|
20433
20507
|
response.json = function(data, status2) {
|
|
20434
20508
|
if (res.headersSent) return response;
|
|
20435
20509
|
if (status2 !== void 0) res.statusCode = status2;
|
|
@@ -20575,7 +20649,8 @@ function createResponse(res) {
|
|
|
20575
20649
|
};
|
|
20576
20650
|
response.render = async function(templateName, data, status2, templateDir) {
|
|
20577
20651
|
try {
|
|
20578
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20652
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
20653
|
+
setCspNonceProvider2(currentCspNonce);
|
|
20579
20654
|
const dir = templateDir ?? _defaultTemplatesDir ?? nodePath.resolve(process.cwd(), "src/templates");
|
|
20580
20655
|
let engine = _frondCache.get(dir);
|
|
20581
20656
|
if (!engine) {
|
|
@@ -20693,6 +20768,7 @@ var HEADER_TOKEN, _frondCache, _defaultTemplatesDir, _globalFrond, _frameworkFro
|
|
|
20693
20768
|
var init_response = __esm({
|
|
20694
20769
|
"../core/src/response.ts"() {
|
|
20695
20770
|
"use strict";
|
|
20771
|
+
init_csp();
|
|
20696
20772
|
HEADER_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
20697
20773
|
_frondCache = /* @__PURE__ */ new Map();
|
|
20698
20774
|
_defaultTemplatesDir = null;
|
|
@@ -20904,6 +20980,7 @@ var init_middleware = __esm({
|
|
|
20904
20980
|
init_dotenv();
|
|
20905
20981
|
init_router();
|
|
20906
20982
|
init_trustedProxy();
|
|
20983
|
+
init_csp();
|
|
20907
20984
|
init_response();
|
|
20908
20985
|
MiddlewareChain = class {
|
|
20909
20986
|
middlewares = [];
|
|
@@ -21336,12 +21413,18 @@ var init_middleware = __esm({
|
|
|
21336
21413
|
* overrides, WITHOUT Strict-Transport-Security, which depends on the
|
|
21337
21414
|
* request's scheme. Also what a transport rejection carries (ADR-0068
|
|
21338
21415
|
* section 4), written before the scheme is known.
|
|
21416
|
+
*
|
|
21417
|
+
* Content-Security-Policy always carries a per-response nonce in style-src
|
|
21418
|
+
* AND script-src (ADR-0088), read from the request async-context (minted on
|
|
21419
|
+
* first access), so the framework's own inline content runs under the strict
|
|
21420
|
+
* default without 'unsafe-inline'. The nonce is random per response, so a
|
|
21421
|
+
* caller comparing this header must assert its STRUCTURE, never a byte string.
|
|
21339
21422
|
*/
|
|
21340
21423
|
static canonicalHeaders() {
|
|
21341
21424
|
return {
|
|
21342
21425
|
"X-Frame-Options": process.env.TINA4_FRAME_OPTIONS ?? "SAMEORIGIN",
|
|
21343
21426
|
"X-Content-Type-Options": "nosniff",
|
|
21344
|
-
"Content-Security-Policy":
|
|
21427
|
+
"Content-Security-Policy": resolveCspHeader(currentCspNonce()),
|
|
21345
21428
|
"Referrer-Policy": process.env.TINA4_REFERRER_POLICY ?? "strict-origin-when-cross-origin",
|
|
21346
21429
|
"X-XSS-Protection": "0",
|
|
21347
21430
|
"Permissions-Policy": process.env.TINA4_PERMISSIONS_POLICY ?? "camera=(), microphone=(), geolocation=()"
|
|
@@ -21383,7 +21466,7 @@ var init_middleware = __esm({
|
|
|
21383
21466
|
static warnCspDefaultOnce() {
|
|
21384
21467
|
if (_SecurityHeadersMiddleware.cspDefaultWarned) return;
|
|
21385
21468
|
_SecurityHeadersMiddleware.cspDefaultWarned = true;
|
|
21386
|
-
const message = `TINA4_CSP is not set, so Tina4 is serving the default Content-Security-Policy "default-src 'self'" on every response.
|
|
21469
|
+
const message = `TINA4_CSP is not set, so Tina4 is serving the default Content-Security-Policy "default-src 'self'" on every response. The framework injects a per-response nonce into style-src and script-src, so its own inline <style>/<script> (and your templates using the csp_nonce() Frond global) work under this policy. The default still blocks cross-origin fonts/scripts/CDNs, data: URIs, and cross-origin WebSocket/XHR (e.g. a separate API or LiveKit host). If your app uses any of these, set TINA4_CSP to a policy that allows them (see https://tina4.com); to silence this notice without changing behaviour, set TINA4_CSP="default-src 'self'".`;
|
|
21387
21470
|
try {
|
|
21388
21471
|
Log.warning(message);
|
|
21389
21472
|
} catch {
|
|
@@ -21621,14 +21704,14 @@ function extractBoundary(contentType) {
|
|
|
21621
21704
|
function parseMultipart(body, boundary) {
|
|
21622
21705
|
const fields = {};
|
|
21623
21706
|
const files = {};
|
|
21624
|
-
const
|
|
21707
|
+
const delimiter4 = Buffer.from(`--${boundary}`);
|
|
21625
21708
|
const closeDelimiter = Buffer.from(`--${boundary}--`);
|
|
21626
21709
|
const crlf = Buffer.from("\r\n");
|
|
21627
21710
|
const doubleCrlf = Buffer.from("\r\n\r\n");
|
|
21628
21711
|
let offset = 0;
|
|
21629
|
-
const firstIdx = bufferIndexOf(body,
|
|
21712
|
+
const firstIdx = bufferIndexOf(body, delimiter4, offset);
|
|
21630
21713
|
if (firstIdx === -1) return { fields, files };
|
|
21631
|
-
offset = firstIdx +
|
|
21714
|
+
offset = firstIdx + delimiter4.length;
|
|
21632
21715
|
if (body[offset] === 13 && body[offset + 1] === 10) {
|
|
21633
21716
|
offset += 2;
|
|
21634
21717
|
}
|
|
@@ -21637,7 +21720,7 @@ function parseMultipart(body, boundary) {
|
|
|
21637
21720
|
if (headersEnd === -1) break;
|
|
21638
21721
|
const headersStr = body.subarray(offset, headersEnd).toString("utf-8");
|
|
21639
21722
|
offset = headersEnd + doubleCrlf.length;
|
|
21640
|
-
const nextDelimIdx = bufferIndexOf(body,
|
|
21723
|
+
const nextDelimIdx = bufferIndexOf(body, delimiter4, offset);
|
|
21641
21724
|
if (nextDelimIdx === -1) break;
|
|
21642
21725
|
const contentEnd = nextDelimIdx - crlf.length;
|
|
21643
21726
|
const content = body.subarray(offset, contentEnd);
|
|
@@ -21660,7 +21743,7 @@ function parseMultipart(body, boundary) {
|
|
|
21660
21743
|
} else if (disposition.name) {
|
|
21661
21744
|
fields[disposition.name] = content.toString("utf-8");
|
|
21662
21745
|
}
|
|
21663
|
-
offset = nextDelimIdx +
|
|
21746
|
+
offset = nextDelimIdx + delimiter4.length;
|
|
21664
21747
|
if (body[offset] === 45 && body[offset + 1] === 45) {
|
|
21665
21748
|
break;
|
|
21666
21749
|
}
|
|
@@ -22408,6 +22491,21 @@ var init_portTakeover = __esm({
|
|
|
22408
22491
|
}
|
|
22409
22492
|
});
|
|
22410
22493
|
|
|
22494
|
+
// ../core/src/etag.ts
|
|
22495
|
+
function etagMatches(ifNoneMatch, etag) {
|
|
22496
|
+
const strip = (tag) => tag.trim().replace(/^W\//, "");
|
|
22497
|
+
const target = strip(etag);
|
|
22498
|
+
return ifNoneMatch.split(",").some((candidate) => {
|
|
22499
|
+
const trimmed = candidate.trim();
|
|
22500
|
+
return trimmed === "*" || strip(trimmed) === target;
|
|
22501
|
+
});
|
|
22502
|
+
}
|
|
22503
|
+
var init_etag = __esm({
|
|
22504
|
+
"../core/src/etag.ts"() {
|
|
22505
|
+
"use strict";
|
|
22506
|
+
}
|
|
22507
|
+
});
|
|
22508
|
+
|
|
22411
22509
|
// ../core/src/sessionHandlers/syncBridge.ts
|
|
22412
22510
|
import { Worker } from "node:worker_threads";
|
|
22413
22511
|
function getBridge(key, workerSource, workerData) {
|
|
@@ -22689,42 +22787,35 @@ var init_respClient = __esm({
|
|
|
22689
22787
|
}
|
|
22690
22788
|
});
|
|
22691
22789
|
|
|
22692
|
-
// ../core/src/sessionHandlers/
|
|
22693
|
-
var
|
|
22694
|
-
var
|
|
22695
|
-
"../core/src/sessionHandlers/
|
|
22790
|
+
// ../core/src/sessionHandlers/respSessionHandler.ts
|
|
22791
|
+
var RespSessionHandler;
|
|
22792
|
+
var init_respSessionHandler = __esm({
|
|
22793
|
+
"../core/src/sessionHandlers/respSessionHandler.ts"() {
|
|
22696
22794
|
"use strict";
|
|
22697
22795
|
init_respClient();
|
|
22698
|
-
|
|
22699
|
-
|
|
22700
|
-
|
|
22701
|
-
|
|
22702
|
-
prefix;
|
|
22703
|
-
db;
|
|
22704
|
-
constructor(config) {
|
|
22705
|
-
this.host = config?.host ?? process.env.TINA4_SESSION_VALKEY_HOST ?? "127.0.0.1";
|
|
22706
|
-
this.port = config?.port ?? (process.env.TINA4_SESSION_VALKEY_PORT ? parseInt(process.env.TINA4_SESSION_VALKEY_PORT, 10) : 6379);
|
|
22707
|
-
this.password = config?.password ?? process.env.TINA4_SESSION_VALKEY_PASSWORD ?? "";
|
|
22708
|
-
this.prefix = config?.prefix ?? process.env.TINA4_SESSION_VALKEY_PREFIX ?? "tina4:session:";
|
|
22709
|
-
this.db = config?.db ?? (process.env.TINA4_SESSION_VALKEY_DB ? parseInt(process.env.TINA4_SESSION_VALKEY_DB, 10) : 0);
|
|
22796
|
+
RespSessionHandler = class {
|
|
22797
|
+
constructor(conn, label) {
|
|
22798
|
+
this.conn = conn;
|
|
22799
|
+
this.label = label;
|
|
22710
22800
|
}
|
|
22801
|
+
conn;
|
|
22802
|
+
label;
|
|
22711
22803
|
/**
|
|
22712
|
-
* Execute a RESP command synchronously against the live
|
|
22804
|
+
* Execute a RESP command synchronously against the live server.
|
|
22713
22805
|
*
|
|
22714
|
-
* Delegates to the shared {@link respCommandSync} transport: a genuine key
|
|
22715
|
-
* yields `""`, and a transport/connection FAILURE
|
|
22716
|
-
*
|
|
22717
|
-
* (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
|
|
22806
|
+
* Delegates to the shared {@link respCommandSync} transport: a genuine key
|
|
22807
|
+
* miss yields `""`, and a transport/connection FAILURE THROWS so the caller
|
|
22808
|
+
* can distinguish "not found" from "backend failed". Backend-failure parity.
|
|
22718
22809
|
*/
|
|
22719
22810
|
execSync(args) {
|
|
22720
22811
|
return respCommandSync(
|
|
22721
|
-
{ host: this.host, port: this.port, password: this.password, db: this.db },
|
|
22812
|
+
{ host: this.conn.host, port: this.conn.port, password: this.conn.password, db: this.conn.db },
|
|
22722
22813
|
args,
|
|
22723
|
-
|
|
22814
|
+
this.label
|
|
22724
22815
|
);
|
|
22725
22816
|
}
|
|
22726
22817
|
key(sessionId) {
|
|
22727
|
-
return `${this.prefix}${sessionId}`;
|
|
22818
|
+
return `${this.conn.prefix}${sessionId}`;
|
|
22728
22819
|
}
|
|
22729
22820
|
read(sessionId) {
|
|
22730
22821
|
const raw = this.execSync(["GET", this.key(sessionId)]);
|
|
@@ -22735,7 +22826,7 @@ var init_valkeyHandler = __esm({
|
|
|
22735
22826
|
return null;
|
|
22736
22827
|
}
|
|
22737
22828
|
}
|
|
22738
|
-
write(sessionId, data, ttl) {
|
|
22829
|
+
write(sessionId, data, ttl = 0) {
|
|
22739
22830
|
const json = JSON.stringify(data);
|
|
22740
22831
|
if (ttl > 0) {
|
|
22741
22832
|
this.execSync(["SETEX", this.key(sessionId), String(ttl), json]);
|
|
@@ -22750,6 +22841,29 @@ var init_valkeyHandler = __esm({
|
|
|
22750
22841
|
}
|
|
22751
22842
|
});
|
|
22752
22843
|
|
|
22844
|
+
// ../core/src/sessionHandlers/valkeyHandler.ts
|
|
22845
|
+
var ValkeySessionHandler;
|
|
22846
|
+
var init_valkeyHandler = __esm({
|
|
22847
|
+
"../core/src/sessionHandlers/valkeyHandler.ts"() {
|
|
22848
|
+
"use strict";
|
|
22849
|
+
init_respSessionHandler();
|
|
22850
|
+
ValkeySessionHandler = class extends RespSessionHandler {
|
|
22851
|
+
constructor(config) {
|
|
22852
|
+
super(
|
|
22853
|
+
{
|
|
22854
|
+
host: config?.host ?? process.env.TINA4_SESSION_VALKEY_HOST ?? "127.0.0.1",
|
|
22855
|
+
port: config?.port ?? (process.env.TINA4_SESSION_VALKEY_PORT ? parseInt(process.env.TINA4_SESSION_VALKEY_PORT, 10) : 6379),
|
|
22856
|
+
password: config?.password ?? process.env.TINA4_SESSION_VALKEY_PASSWORD ?? "",
|
|
22857
|
+
prefix: config?.prefix ?? process.env.TINA4_SESSION_VALKEY_PREFIX ?? "tina4:session:",
|
|
22858
|
+
db: config?.db ?? (process.env.TINA4_SESSION_VALKEY_DB ? parseInt(process.env.TINA4_SESSION_VALKEY_DB, 10) : 0)
|
|
22859
|
+
},
|
|
22860
|
+
"Valkey"
|
|
22861
|
+
);
|
|
22862
|
+
}
|
|
22863
|
+
};
|
|
22864
|
+
}
|
|
22865
|
+
});
|
|
22866
|
+
|
|
22753
22867
|
// ../core/src/sessionHandlers/memcachedHandler.ts
|
|
22754
22868
|
import { createHash as createHash4 } from "node:crypto";
|
|
22755
22869
|
var MAX_KEY_BYTES, MAX_RELATIVE_EXPTIME, MemcachedSessionHandler;
|
|
@@ -23765,7 +23879,7 @@ __export(session_exports, {
|
|
|
23765
23879
|
sessionCookieName: () => sessionCookieName,
|
|
23766
23880
|
sessionStrictMode: () => sessionStrictMode
|
|
23767
23881
|
});
|
|
23768
|
-
import { createHash as createHash5, randomBytes as
|
|
23882
|
+
import { createHash as createHash5, randomBytes as randomBytes6 } from "node:crypto";
|
|
23769
23883
|
import { existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync8, unlinkSync as unlinkSync5, readdirSync as readdirSync6 } from "node:fs";
|
|
23770
23884
|
import { join as join15 } from "node:path";
|
|
23771
23885
|
function isValidSessionId(sessionId) {
|
|
@@ -23783,6 +23897,20 @@ function resolveBackend(name) {
|
|
|
23783
23897
|
}
|
|
23784
23898
|
return normalised;
|
|
23785
23899
|
}
|
|
23900
|
+
function fingerprint(value) {
|
|
23901
|
+
try {
|
|
23902
|
+
return JSON.stringify(value);
|
|
23903
|
+
} catch {
|
|
23904
|
+
return void 0;
|
|
23905
|
+
}
|
|
23906
|
+
}
|
|
23907
|
+
function fingerprints(data) {
|
|
23908
|
+
const prints = /* @__PURE__ */ new Map();
|
|
23909
|
+
for (const [key, value] of Object.entries(data)) {
|
|
23910
|
+
prints.set(key, fingerprint(value));
|
|
23911
|
+
}
|
|
23912
|
+
return prints;
|
|
23913
|
+
}
|
|
23786
23914
|
function isSecureScheme(forwardedProto, socketEncrypted) {
|
|
23787
23915
|
const forwarded = (forwardedProto ?? "").trim();
|
|
23788
23916
|
if (forwarded !== "") {
|
|
@@ -23815,7 +23943,7 @@ var init_session = __esm({
|
|
|
23815
23943
|
"use strict";
|
|
23816
23944
|
init_logger();
|
|
23817
23945
|
init_dotenv();
|
|
23818
|
-
|
|
23946
|
+
init_respSessionHandler();
|
|
23819
23947
|
init_valkeyHandler();
|
|
23820
23948
|
init_memcachedHandler();
|
|
23821
23949
|
init_mongoHandler();
|
|
@@ -23941,57 +24069,19 @@ var init_session = __esm({
|
|
|
23941
24069
|
}
|
|
23942
24070
|
}
|
|
23943
24071
|
};
|
|
23944
|
-
RedisSessionHandler = class {
|
|
23945
|
-
host;
|
|
23946
|
-
port;
|
|
23947
|
-
password;
|
|
23948
|
-
prefix;
|
|
23949
|
-
db;
|
|
24072
|
+
RedisSessionHandler = class extends RespSessionHandler {
|
|
23950
24073
|
constructor(config) {
|
|
23951
|
-
|
|
23952
|
-
|
|
23953
|
-
|
|
23954
|
-
|
|
23955
|
-
|
|
23956
|
-
|
|
23957
|
-
|
|
23958
|
-
|
|
23959
|
-
*
|
|
23960
|
-
* Delegates to the shared {@link respCommandSync} transport: a genuine key miss
|
|
23961
|
-
* yields `""`, and a transport/connection FAILURE (server unreachable, rejected
|
|
23962
|
-
* AUTH, timeout) THROWS so the Session boundary can distinguish "not found"
|
|
23963
|
-
* (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
|
|
23964
|
-
*/
|
|
23965
|
-
execSync(args) {
|
|
23966
|
-
return respCommandSync(
|
|
23967
|
-
{ host: this.host, port: this.port, password: this.password, db: this.db },
|
|
23968
|
-
args,
|
|
24074
|
+
super(
|
|
24075
|
+
{
|
|
24076
|
+
host: config?.redisHost ?? process.env.TINA4_SESSION_REDIS_HOST ?? "127.0.0.1",
|
|
24077
|
+
port: config?.redisPort ?? (process.env.TINA4_SESSION_REDIS_PORT ? parseInt(process.env.TINA4_SESSION_REDIS_PORT, 10) : 6379),
|
|
24078
|
+
password: config?.redisPassword ?? process.env.TINA4_SESSION_REDIS_PASSWORD ?? "",
|
|
24079
|
+
prefix: config?.redisPrefix ?? process.env.TINA4_SESSION_REDIS_PREFIX ?? "tina4:session:",
|
|
24080
|
+
db: config?.redisDb ?? (process.env.TINA4_SESSION_REDIS_DB ? parseInt(process.env.TINA4_SESSION_REDIS_DB, 10) : 0)
|
|
24081
|
+
},
|
|
23969
24082
|
"Redis"
|
|
23970
24083
|
);
|
|
23971
24084
|
}
|
|
23972
|
-
key(sessionId) {
|
|
23973
|
-
return `${this.prefix}${sessionId}`;
|
|
23974
|
-
}
|
|
23975
|
-
read(sessionId) {
|
|
23976
|
-
const raw = this.execSync(["GET", this.key(sessionId)]);
|
|
23977
|
-
if (!raw) return null;
|
|
23978
|
-
try {
|
|
23979
|
-
return JSON.parse(raw);
|
|
23980
|
-
} catch {
|
|
23981
|
-
return null;
|
|
23982
|
-
}
|
|
23983
|
-
}
|
|
23984
|
-
write(sessionId, data, ttl = 0) {
|
|
23985
|
-
const json = JSON.stringify(data);
|
|
23986
|
-
if (ttl > 0) {
|
|
23987
|
-
this.execSync(["SETEX", this.key(sessionId), String(ttl), json]);
|
|
23988
|
-
} else {
|
|
23989
|
-
this.execSync(["SET", this.key(sessionId), json]);
|
|
23990
|
-
}
|
|
23991
|
-
}
|
|
23992
|
-
destroy(sessionId) {
|
|
23993
|
-
this.execSync(["DEL", this.key(sessionId)]);
|
|
23994
|
-
}
|
|
23995
24085
|
};
|
|
23996
24086
|
FLASH_PREFIX = "_flash_";
|
|
23997
24087
|
Session = class {
|
|
@@ -24018,6 +24108,29 @@ var init_session = __esm({
|
|
|
24018
24108
|
* Lets `start()` tell "no such session" from "the store is unreachable".
|
|
24019
24109
|
*/
|
|
24020
24110
|
lastReadFailed = false;
|
|
24111
|
+
/**
|
|
24112
|
+
* What this request last saw of the stored record, one fingerprint per key:
|
|
24113
|
+
* taken by start() and again by every save. save() compares the live data
|
|
24114
|
+
* against it to find the keys THIS request changed.
|
|
24115
|
+
*/
|
|
24116
|
+
loaded = /* @__PURE__ */ new Map();
|
|
24117
|
+
/**
|
|
24118
|
+
* True when this request is working on a record the store holds: start()
|
|
24119
|
+
* adopted one, or a save has written one since. A save never re-creates such
|
|
24120
|
+
* a record once another request has removed it.
|
|
24121
|
+
*/
|
|
24122
|
+
stored = false;
|
|
24123
|
+
/**
|
|
24124
|
+
* True when clear() ran since the last save: the save then replaces the
|
|
24125
|
+
* stored record instead of merging into it.
|
|
24126
|
+
*/
|
|
24127
|
+
cleared = false;
|
|
24128
|
+
/**
|
|
24129
|
+
* True once this request found its session ended by another request (the
|
|
24130
|
+
* record it loaded is gone). It stays ended for the rest of the request:
|
|
24131
|
+
* regenerate() mints nothing either.
|
|
24132
|
+
*/
|
|
24133
|
+
ended = false;
|
|
24021
24134
|
constructor(backend, config) {
|
|
24022
24135
|
const backendType = resolveBackend(
|
|
24023
24136
|
backend ?? config?.backend ?? process.env.TINA4_SESSION_BACKEND ?? "file"
|
|
@@ -24162,6 +24275,10 @@ var init_session = __esm({
|
|
|
24162
24275
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
24163
24276
|
this.data = { _created: now2, _accessed: now2 };
|
|
24164
24277
|
this.dirty = false;
|
|
24278
|
+
this.stored = false;
|
|
24279
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
24280
|
+
this.cleared = false;
|
|
24281
|
+
this.ended = false;
|
|
24165
24282
|
return sessionId;
|
|
24166
24283
|
}
|
|
24167
24284
|
if (loaded) {
|
|
@@ -24171,14 +24288,22 @@ var init_session = __esm({
|
|
|
24171
24288
|
this.data._accessed = now2;
|
|
24172
24289
|
this.dirty = false;
|
|
24173
24290
|
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
24291
|
+
this.stored = true;
|
|
24292
|
+
this.loaded = fingerprints(this.data);
|
|
24293
|
+
this.cleared = false;
|
|
24294
|
+
this.ended = false;
|
|
24174
24295
|
return sessionId;
|
|
24175
24296
|
}
|
|
24176
24297
|
}
|
|
24177
|
-
this.sessionId =
|
|
24298
|
+
this.sessionId = randomBytes6(16).toString("hex");
|
|
24178
24299
|
const now = Math.floor(Date.now() / 1e3);
|
|
24179
24300
|
this.data = { _created: now, _accessed: now };
|
|
24180
24301
|
this.dirty = false;
|
|
24181
24302
|
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
24303
|
+
this.stored = false;
|
|
24304
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
24305
|
+
this.cleared = false;
|
|
24306
|
+
this.ended = false;
|
|
24182
24307
|
return this.sessionId;
|
|
24183
24308
|
}
|
|
24184
24309
|
/**
|
|
@@ -24219,9 +24344,7 @@ var init_session = __esm({
|
|
|
24219
24344
|
if (this.sessionId) {
|
|
24220
24345
|
this.safeDestroy(this.sessionId);
|
|
24221
24346
|
}
|
|
24222
|
-
this.
|
|
24223
|
-
this.data = null;
|
|
24224
|
-
this.dirty = false;
|
|
24347
|
+
this.forget();
|
|
24225
24348
|
}
|
|
24226
24349
|
/**
|
|
24227
24350
|
* Get all session data (excluding internal keys).
|
|
@@ -24244,6 +24367,7 @@ var init_session = __esm({
|
|
|
24244
24367
|
const now = Math.floor(Date.now() / 1e3);
|
|
24245
24368
|
this.data = { _created: this.data._created, _accessed: now };
|
|
24246
24369
|
this.dirty = true;
|
|
24370
|
+
this.cleared = true;
|
|
24247
24371
|
this.save();
|
|
24248
24372
|
}
|
|
24249
24373
|
/**
|
|
@@ -24260,17 +24384,36 @@ var init_session = __esm({
|
|
|
24260
24384
|
* session fixation — the pre-auth ID is destroyed and the data is carried
|
|
24261
24385
|
* onto a fresh, unguessable ID. A backend destroy/write failure is logged
|
|
24262
24386
|
* (never silent) but does not throw under the default policy.
|
|
24387
|
+
*
|
|
24388
|
+
* What is carried is the session as it is stored now with this request's
|
|
24389
|
+
* own changes applied, the same merge save() does. If another request ended
|
|
24390
|
+
* the session after this one loaded it (a logout, or a regenerate of its
|
|
24391
|
+
* own), it stays ended: nothing is carried, no id is minted and null is
|
|
24392
|
+
* returned, so no cookie goes out to replace the one that request sent.
|
|
24263
24393
|
*/
|
|
24264
24394
|
regenerate() {
|
|
24395
|
+
if (this.ended) return null;
|
|
24396
|
+
if (this.sessionId && this.stored && this.data) {
|
|
24397
|
+
const current = this.safeRead(this.sessionId);
|
|
24398
|
+
if (!this.lastReadFailed) {
|
|
24399
|
+
if (!current) {
|
|
24400
|
+
this.forget();
|
|
24401
|
+
this.ended = true;
|
|
24402
|
+
return null;
|
|
24403
|
+
}
|
|
24404
|
+
if (!this.cleared) this.data = this.merged(current);
|
|
24405
|
+
}
|
|
24406
|
+
}
|
|
24265
24407
|
const oldId = this.sessionId;
|
|
24266
24408
|
const oldData = this.data;
|
|
24267
24409
|
if (oldId) {
|
|
24268
24410
|
this.safeDestroy(oldId);
|
|
24269
24411
|
}
|
|
24270
|
-
this.sessionId =
|
|
24412
|
+
this.sessionId = randomBytes6(16).toString("hex");
|
|
24271
24413
|
this.data = oldData ?? { _created: Math.floor(Date.now() / 1e3), _accessed: Math.floor(Date.now() / 1e3) };
|
|
24272
24414
|
this.data._accessed = Math.floor(Date.now() / 1e3);
|
|
24273
24415
|
this.dirty = true;
|
|
24416
|
+
this.stored = false;
|
|
24274
24417
|
this.save();
|
|
24275
24418
|
return this.sessionId;
|
|
24276
24419
|
}
|
|
@@ -24346,17 +24489,84 @@ var init_session = __esm({
|
|
|
24346
24489
|
*
|
|
24347
24490
|
* Returns true on a successful persist, false if the backend was unreachable
|
|
24348
24491
|
* (logged). The dirty flag is cleared only on success so a later save()
|
|
24349
|
-
* retries once the backend recovers. A
|
|
24492
|
+
* retries once the backend recovers. A call with no session returns true.
|
|
24493
|
+
*
|
|
24494
|
+
* Other requests on the same session run while this one does, so the save
|
|
24495
|
+
* writes only THIS request's changes, onto the record as it is stored now.
|
|
24496
|
+
* Writing back the whole snapshot start() loaded undid whatever another
|
|
24497
|
+
* request did in between: a logout (destroy, clear, regenerate) or a
|
|
24498
|
+
* privilege change made with set(). A record removed since this request
|
|
24499
|
+
* loaded it is never re-created: the session has ended, for this request
|
|
24500
|
+
* too. A request that has not loaded a stored record (a new session, the new
|
|
24501
|
+
* id regenerate() mints) writes its data whole, as before.
|
|
24502
|
+
*
|
|
24503
|
+
* A session the store already holds is re-written even when this request
|
|
24504
|
+
* changed nothing: that write re-stamps the backend deadline to
|
|
24505
|
+
* now + TINA4_SESSION_TTL, so a session expires after that span of INACTIVITY
|
|
24506
|
+
* rather than that long after its last CHANGE (ADR-0087 — a session's expiry
|
|
24507
|
+
* slides on activity). The re-write is the re-read, merged record, so sliding
|
|
24508
|
+
* the deadline is concurrent-safe: it never clobbers another request's change,
|
|
24509
|
+
* never re-creates an ended record, and TINA4_SESSION_TTL=0 still re-stamps a
|
|
24510
|
+
* never-expires deadline (ADR-0027). A request with no stored session and no
|
|
24511
|
+
* change writes nothing — there is no deadline to move.
|
|
24350
24512
|
*/
|
|
24351
24513
|
save() {
|
|
24352
24514
|
if (!this.sessionId || !this.data) return true;
|
|
24353
|
-
if (!this.dirty) return true;
|
|
24354
|
-
|
|
24515
|
+
if (!this.dirty && !this.stored) return true;
|
|
24516
|
+
let record = this.data;
|
|
24517
|
+
if (this.stored) {
|
|
24518
|
+
const current = this.safeRead(this.sessionId);
|
|
24519
|
+
if (this.lastReadFailed) return false;
|
|
24520
|
+
if (!current) {
|
|
24521
|
+
this.forget();
|
|
24522
|
+
this.ended = true;
|
|
24523
|
+
return true;
|
|
24524
|
+
}
|
|
24525
|
+
if (!this.cleared) record = this.merged(current);
|
|
24526
|
+
}
|
|
24527
|
+
if (this.safeWrite(this.sessionId, record, this.ttl)) {
|
|
24355
24528
|
this.dirty = false;
|
|
24529
|
+
this.stored = true;
|
|
24530
|
+
this.cleared = false;
|
|
24531
|
+
this.loaded = fingerprints(this.data);
|
|
24356
24532
|
return true;
|
|
24357
24533
|
}
|
|
24358
24534
|
return false;
|
|
24359
24535
|
}
|
|
24536
|
+
/**
|
|
24537
|
+
* The stored record with this request's changes applied: every key whose
|
|
24538
|
+
* value differs from what this request loaded or last saved, and the
|
|
24539
|
+
* removal of every key it deleted. Keys it did not touch keep whatever is
|
|
24540
|
+
* stored now.
|
|
24541
|
+
*/
|
|
24542
|
+
merged(current) {
|
|
24543
|
+
const data = this.data;
|
|
24544
|
+
const record = { ...current };
|
|
24545
|
+
for (const [key, value] of Object.entries(data)) {
|
|
24546
|
+
const print = fingerprint(value);
|
|
24547
|
+
if (print === void 0 || !this.loaded.has(key) || this.loaded.get(key) !== print) {
|
|
24548
|
+
record[key] = value;
|
|
24549
|
+
}
|
|
24550
|
+
}
|
|
24551
|
+
for (const key of this.loaded.keys()) {
|
|
24552
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
24553
|
+
delete record[key];
|
|
24554
|
+
}
|
|
24555
|
+
}
|
|
24556
|
+
return record;
|
|
24557
|
+
}
|
|
24558
|
+
/**
|
|
24559
|
+
* End the session for this request without touching the store: no id and
|
|
24560
|
+
* no data, so nothing more is written and no cookie goes out for it.
|
|
24561
|
+
*/
|
|
24562
|
+
forget() {
|
|
24563
|
+
this.sessionId = null;
|
|
24564
|
+
this.data = null;
|
|
24565
|
+
this.dirty = false;
|
|
24566
|
+
this.stored = false;
|
|
24567
|
+
this.cleared = false;
|
|
24568
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
24569
|
+
}
|
|
24360
24570
|
};
|
|
24361
24571
|
}
|
|
24362
24572
|
});
|
|
@@ -24401,14 +24611,6 @@ function headStripIntercept(rawReq, rawRes) {
|
|
|
24401
24611
|
function isCompressibleContentType(contentType) {
|
|
24402
24612
|
return COMPRESSIBLE_PREFIXES.some((prefix) => contentType.includes(prefix));
|
|
24403
24613
|
}
|
|
24404
|
-
function etagMatchesInm(ifNoneMatch, etag) {
|
|
24405
|
-
const strip = (tag) => tag.trim().replace(/^W\//, "");
|
|
24406
|
-
const target = strip(etag);
|
|
24407
|
-
return ifNoneMatch.split(",").some((candidate) => {
|
|
24408
|
-
const trimmed = candidate.trim();
|
|
24409
|
-
return trimmed === "*" || strip(trimmed) === target;
|
|
24410
|
-
});
|
|
24411
|
-
}
|
|
24412
24614
|
function maybeCompressResponse(body, rawReq, rawRes) {
|
|
24413
24615
|
const acceptEncoding = String(rawReq.headers["accept-encoding"] ?? "");
|
|
24414
24616
|
const contentTypeHeader = rawRes.getHeader("content-type");
|
|
@@ -24424,7 +24626,7 @@ function maybeCompressResponse(body, rawReq, rawRes) {
|
|
|
24424
24626
|
}
|
|
24425
24627
|
function responseIsNotModified(rawReq, rawRes, etag) {
|
|
24426
24628
|
const ifNoneMatch = String(rawReq.headers["if-none-match"] ?? "");
|
|
24427
|
-
if (ifNoneMatch) return
|
|
24629
|
+
if (ifNoneMatch) return etagMatches(ifNoneMatch, etag);
|
|
24428
24630
|
const lastModifiedHeader = rawRes.getHeader("last-modified");
|
|
24429
24631
|
if (typeof lastModifiedHeader !== "string") return false;
|
|
24430
24632
|
const ifModifiedSince = String(rawReq.headers["if-modified-since"] ?? "");
|
|
@@ -24552,6 +24754,7 @@ var init_dispatchPipeline = __esm({
|
|
|
24552
24754
|
"../core/src/dispatchPipeline.ts"() {
|
|
24553
24755
|
"use strict";
|
|
24554
24756
|
init_logger();
|
|
24757
|
+
init_etag();
|
|
24555
24758
|
COMPRESSIBLE_PREFIXES = [
|
|
24556
24759
|
"text/",
|
|
24557
24760
|
"application/json",
|
|
@@ -24647,18 +24850,11 @@ function isNotModified(req2, etag, stat) {
|
|
|
24647
24850
|
}
|
|
24648
24851
|
return false;
|
|
24649
24852
|
}
|
|
24650
|
-
function etagMatches(ifNoneMatch, etag) {
|
|
24651
|
-
const strip = (tag) => tag.trim().replace(/^W\//, "");
|
|
24652
|
-
const target = strip(etag);
|
|
24653
|
-
return ifNoneMatch.split(",").some((candidate) => {
|
|
24654
|
-
const trimmed = candidate.trim();
|
|
24655
|
-
return trimmed === "*" || strip(trimmed) === target;
|
|
24656
|
-
});
|
|
24657
|
-
}
|
|
24658
24853
|
var COMPRESSIBLE_PREFIXES2, MIME_TYPES;
|
|
24659
24854
|
var init_static = __esm({
|
|
24660
24855
|
"../core/src/static.ts"() {
|
|
24661
24856
|
"use strict";
|
|
24857
|
+
init_etag();
|
|
24662
24858
|
COMPRESSIBLE_PREFIXES2 = [
|
|
24663
24859
|
"text/",
|
|
24664
24860
|
"application/json",
|
|
@@ -24739,11 +24935,11 @@ function formatSourceBlock(filename, lineno) {
|
|
|
24739
24935
|
const lines = readSourceLines(filename, lineno);
|
|
24740
24936
|
if (lines.length === 0) return "";
|
|
24741
24937
|
const rows = lines.map(([num, text, isError]) => {
|
|
24742
|
-
const
|
|
24938
|
+
const rowClass = isError ? "eo-line eo-line-err" : "eo-line";
|
|
24743
24939
|
const marker = isError ? "▶" : " ";
|
|
24744
|
-
return `<div
|
|
24940
|
+
return `<div class="${rowClass}"><span class="eo-ln">${num}</span><span class="eo-marker">${marker}</span><span class="eo-code">${esc(text)}</span></div>`;
|
|
24745
24941
|
}).join("\n");
|
|
24746
|
-
return `<div
|
|
24942
|
+
return `<div class="eo-source">` + rows + `</div>`;
|
|
24747
24943
|
}
|
|
24748
24944
|
function formatFrame(frame, capturedAt = 0) {
|
|
24749
24945
|
const source = frame.file && frame.line > 0 ? formatSourceBlock(frame.file, frame.line) : "";
|
|
@@ -24755,23 +24951,23 @@ function formatFrame(frame, capturedAt = 0) {
|
|
|
24755
24951
|
if (mtime > capturedAt + 0.5) {
|
|
24756
24952
|
const d = new Date(mtime * 1e3);
|
|
24757
24953
|
const mtimeIso = `${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}:${String(d.getUTCSeconds()).padStart(2, "0")}`;
|
|
24758
|
-
staleBadge = ` <span
|
|
24954
|
+
staleBadge = ` <span class="eo-stale">FILE MODIFIED @ ${mtimeIso} UTC — source may not match what failed</span>`;
|
|
24759
24955
|
}
|
|
24760
24956
|
} catch {
|
|
24761
24957
|
}
|
|
24762
24958
|
}
|
|
24763
|
-
return `<div
|
|
24959
|
+
return `<div class="eo-frame"><div class="eo-frame-head"><span class="eo-file">${esc(frame.file)}</span><span class="eo-sep"> : </span><span class="eo-lineno">${frame.line}</span><span class="eo-sep"> in </span><span class="eo-fn">${esc(frame.func)}</span>` + staleBadge + `</div>` + source + `</div>`;
|
|
24764
24960
|
}
|
|
24765
24961
|
function collapsible(title, content, openByDefault = false) {
|
|
24766
24962
|
const open = openByDefault ? " open" : "";
|
|
24767
|
-
return `<details
|
|
24963
|
+
return `<details class="eo-details"${open}><summary class="eo-summary">${esc(title)}</summary><div class="eo-details-body">${content}</div></details>`;
|
|
24768
24964
|
}
|
|
24769
24965
|
function table(pairs) {
|
|
24770
|
-
if (pairs.length === 0) return `<span
|
|
24966
|
+
if (pairs.length === 0) return `<span class="eo-none">None</span>`;
|
|
24771
24967
|
const rows = pairs.map(
|
|
24772
|
-
([key, val]) => `<tr><td
|
|
24968
|
+
([key, val]) => `<tr><td class="eo-key">${esc(key)}</td><td class="eo-val">${esc(val)}</td></tr>`
|
|
24773
24969
|
).join("");
|
|
24774
|
-
return `<table
|
|
24970
|
+
return `<table class="eo-table">${rows}</table>`;
|
|
24775
24971
|
}
|
|
24776
24972
|
function renderErrorOverlay(error2, request) {
|
|
24777
24973
|
const capturedAt = Date.now() / 1e3;
|
|
@@ -24784,7 +24980,7 @@ function renderErrorOverlay(error2, request) {
|
|
|
24784
24980
|
}
|
|
24785
24981
|
const hidden = frames.length - Math.min(frames.length, MAX_FRAMES);
|
|
24786
24982
|
if (hidden > 0) {
|
|
24787
|
-
framesHtml += `<div
|
|
24983
|
+
framesHtml += `<div class="eo-hidden-frames">… ${hidden} more stack frames hidden (truncated at ${MAX_FRAMES})</div>`;
|
|
24788
24984
|
}
|
|
24789
24985
|
const requestPairs = [];
|
|
24790
24986
|
if (request != null) {
|
|
@@ -24821,37 +25017,72 @@ function renderErrorOverlay(error2, request) {
|
|
|
24821
25017
|
];
|
|
24822
25018
|
const envSection = collapsible("Environment", table(envPairs));
|
|
24823
25019
|
const stackSection = collapsible("Stack Trace", framesHtml, true);
|
|
25020
|
+
const nonce = currentCspNonce();
|
|
24824
25021
|
return `<!DOCTYPE html>
|
|
24825
25022
|
<html lang="en">
|
|
24826
25023
|
<head>
|
|
24827
25024
|
<meta charset="utf-8">
|
|
24828
25025
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
24829
25026
|
<title>Tina4 Error \u2014 ${esc(excType)}</title>
|
|
24830
|
-
<style>
|
|
24831
|
-
|
|
24832
|
-
body{background:${BG};color:${TEXT};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px;line-height:1.5;}
|
|
25027
|
+
<style nonce="${nonce}">
|
|
25028
|
+
${overlayStylesheet()}
|
|
24833
25029
|
</style>
|
|
24834
25030
|
</head>
|
|
24835
25031
|
<body>
|
|
24836
|
-
<div
|
|
24837
|
-
<div
|
|
24838
|
-
<div
|
|
24839
|
-
<span
|
|
24840
|
-
<span
|
|
25032
|
+
<div class="eo-wrap">
|
|
25033
|
+
<div class="eo-header">
|
|
25034
|
+
<div class="eo-badge-row">
|
|
25035
|
+
<span class="eo-badge">Error</span>
|
|
25036
|
+
<span class="eo-sub">Tina4 Debug Overlay</span>
|
|
24841
25037
|
</div>
|
|
24842
|
-
<h1
|
|
24843
|
-
<p
|
|
25038
|
+
<h1 class="eo-type">${esc(excType)}</h1>
|
|
25039
|
+
<p class="eo-msg">${esc(excMsg)}</p>
|
|
24844
25040
|
</div>
|
|
24845
25041
|
${stackSection}
|
|
24846
25042
|
${requestSection}
|
|
24847
25043
|
${envSection}
|
|
24848
|
-
<div
|
|
25044
|
+
<div class="eo-footer">
|
|
24849
25045
|
Tina4 Debug Overlay — This page is only shown in debug mode. Set TINA4_DEBUG=false in production.
|
|
24850
25046
|
</div>
|
|
24851
25047
|
</div>
|
|
24852
25048
|
</body>
|
|
24853
25049
|
</html>`;
|
|
24854
25050
|
}
|
|
25051
|
+
function overlayStylesheet() {
|
|
25052
|
+
return `
|
|
25053
|
+
*{margin:0;padding:0;box-sizing:border-box;}
|
|
25054
|
+
body{background:${BG};color:${TEXT};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px;line-height:1.5;}
|
|
25055
|
+
.eo-wrap{max-width:960px;margin:0 auto;}
|
|
25056
|
+
.eo-header{margin-bottom:24px;}
|
|
25057
|
+
.eo-badge-row{display:flex;align-items:center;gap:12px;margin-bottom:12px;}
|
|
25058
|
+
.eo-badge{background:${RED};color:${BG};padding:4px 12px;border-radius:4px;font-weight:700;font-size:13px;text-transform:uppercase;}
|
|
25059
|
+
.eo-sub{color:${SUBTEXT};font-size:14px;}
|
|
25060
|
+
.eo-type{color:${RED};font-size:28px;font-weight:700;margin-bottom:8px;}
|
|
25061
|
+
.eo-msg{color:${TEXT};font-size:18px;font-family:'SF Mono','Fira Code','Consolas',monospace;background:${SURFACE};padding:12px 16px;border-radius:6px;border-left:4px solid ${RED};}
|
|
25062
|
+
.eo-footer{margin-top:32px;padding-top:16px;border-top:1px solid ${OVERLAY};color:${SUBTEXT};font-size:12px;}
|
|
25063
|
+
.eo-source{background:${SURFACE};border-radius:6px;padding:12px;overflow-x:auto;font-family:'SF Mono','Fira Code','Consolas',monospace;font-size:13px;line-height:1.6;}
|
|
25064
|
+
.eo-line{display:flex;padding:1px 0;}
|
|
25065
|
+
.eo-line-err{background:${ERROR_LINE_BG};}
|
|
25066
|
+
.eo-ln{color:${YELLOW};min-width:3.5em;text-align:right;padding-right:1em;user-select:none;}
|
|
25067
|
+
.eo-marker{color:${RED};width:1.2em;user-select:none;}
|
|
25068
|
+
.eo-code{color:${TEXT};white-space:pre-wrap;tab-size:4;}
|
|
25069
|
+
.eo-frame{margin-bottom:16px;}
|
|
25070
|
+
.eo-frame-head{margin-bottom:4px;}
|
|
25071
|
+
.eo-file{color:${BLUE};}
|
|
25072
|
+
.eo-sep{color:${SUBTEXT};}
|
|
25073
|
+
.eo-lineno{color:${YELLOW};}
|
|
25074
|
+
.eo-fn{color:${GREEN};}
|
|
25075
|
+
.eo-stale{background:${PEACH};color:${BG};padding:1px 8px;border-radius:3px;font-size:11px;font-weight:700;margin-left:6px;}
|
|
25076
|
+
.eo-details{margin-top:16px;}
|
|
25077
|
+
.eo-summary{cursor:pointer;color:${LAVENDER};font-weight:600;font-size:15px;padding:8px 0;user-select:none;}
|
|
25078
|
+
.eo-details-body{padding:8px 0;}
|
|
25079
|
+
.eo-table{border-collapse:collapse;width:100%;}
|
|
25080
|
+
.eo-key{color:${PEACH};padding:4px 16px 4px 0;vertical-align:top;white-space:nowrap;}
|
|
25081
|
+
.eo-val{color:${TEXT};padding:4px 0;word-break:break-all;}
|
|
25082
|
+
.eo-none{color:${SUBTEXT};}
|
|
25083
|
+
.eo-hidden-frames{color:${SUBTEXT};padding:8px 0;font-size:13px;}
|
|
25084
|
+
`;
|
|
25085
|
+
}
|
|
24855
25086
|
function isDebugMode() {
|
|
24856
25087
|
return isTruthy(process.env.TINA4_DEBUG);
|
|
24857
25088
|
}
|
|
@@ -24860,6 +25091,7 @@ var init_errorOverlay = __esm({
|
|
|
24860
25091
|
"../core/src/errorOverlay.ts"() {
|
|
24861
25092
|
"use strict";
|
|
24862
25093
|
init_dotenv();
|
|
25094
|
+
init_csp();
|
|
24863
25095
|
MAX_FRAMES = 50;
|
|
24864
25096
|
SENSITIVE_KEY_RE = /password|passwd|secret|token|authorization|cookie|key/i;
|
|
24865
25097
|
REDACTED2 = "[redacted]";
|
|
@@ -25235,6 +25467,25 @@ Generated by DevMailbox.seed() for development purposes.`,
|
|
|
25235
25467
|
}
|
|
25236
25468
|
});
|
|
25237
25469
|
|
|
25470
|
+
// ../core/src/upstreamResponse.ts
|
|
25471
|
+
async function relayUpstreamResponse(res, upstream) {
|
|
25472
|
+
const raw = await upstream.text();
|
|
25473
|
+
const status2 = upstream.status || 200;
|
|
25474
|
+
try {
|
|
25475
|
+
res.json(JSON.parse(raw), status2);
|
|
25476
|
+
} catch {
|
|
25477
|
+
res.raw.writeHead(status2, {
|
|
25478
|
+
"Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
|
|
25479
|
+
});
|
|
25480
|
+
res.raw.end(raw);
|
|
25481
|
+
}
|
|
25482
|
+
}
|
|
25483
|
+
var init_upstreamResponse = __esm({
|
|
25484
|
+
"../core/src/upstreamResponse.ts"() {
|
|
25485
|
+
"use strict";
|
|
25486
|
+
}
|
|
25487
|
+
});
|
|
25488
|
+
|
|
25238
25489
|
// ../core/src/metrics.ts
|
|
25239
25490
|
import * as fs3 from "node:fs";
|
|
25240
25491
|
import * as path2 from "node:path";
|
|
@@ -25450,6 +25701,7 @@ var init_feedback = __esm({
|
|
|
25450
25701
|
"use strict";
|
|
25451
25702
|
init_auth();
|
|
25452
25703
|
init_devAdmin();
|
|
25704
|
+
init_upstreamResponse();
|
|
25453
25705
|
RATE_LIMIT_WINDOW_SEC = 3600;
|
|
25454
25706
|
RATE_LIMIT_MAX = 5;
|
|
25455
25707
|
_rateLimitHits = /* @__PURE__ */ new Map();
|
|
@@ -25496,16 +25748,7 @@ var init_feedback = __esm({
|
|
|
25496
25748
|
return;
|
|
25497
25749
|
}
|
|
25498
25750
|
clearTimeout(timer);
|
|
25499
|
-
|
|
25500
|
-
const status2 = upstream.status || 200;
|
|
25501
|
-
try {
|
|
25502
|
-
res.json(JSON.parse(raw), status2);
|
|
25503
|
-
} catch {
|
|
25504
|
-
res.raw.writeHead(status2, {
|
|
25505
|
-
"Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
|
|
25506
|
-
});
|
|
25507
|
-
res.raw.end(raw);
|
|
25508
|
-
}
|
|
25751
|
+
await relayUpstreamResponse(res, upstream);
|
|
25509
25752
|
};
|
|
25510
25753
|
__feedbackDirname = dirname8(fileURLToPath3(import.meta.url));
|
|
25511
25754
|
WIDGET_BUNDLE_PATH = resolve10(__feedbackDirname, "__feedback", "widget.js");
|
|
@@ -25555,6 +25798,61 @@ var init_version = __esm({
|
|
|
25555
25798
|
}
|
|
25556
25799
|
});
|
|
25557
25800
|
|
|
25801
|
+
// src/util.ts
|
|
25802
|
+
import { delimiter as delimiter2, join as join21 } from "node:path";
|
|
25803
|
+
import { statSync as statSync11 } from "node:fs";
|
|
25804
|
+
function parseFlags(args, booleanFlags) {
|
|
25805
|
+
const flags = {};
|
|
25806
|
+
const positional = [];
|
|
25807
|
+
let i = 0;
|
|
25808
|
+
while (i < args.length) {
|
|
25809
|
+
const arg = args[i];
|
|
25810
|
+
if (arg.startsWith("--")) {
|
|
25811
|
+
const key = arg.slice(2);
|
|
25812
|
+
if (booleanFlags?.has(key)) {
|
|
25813
|
+
flags[key] = true;
|
|
25814
|
+
i += 1;
|
|
25815
|
+
} else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
25816
|
+
flags[key] = args[i + 1];
|
|
25817
|
+
i += 2;
|
|
25818
|
+
} else {
|
|
25819
|
+
flags[key] = true;
|
|
25820
|
+
i += 1;
|
|
25821
|
+
}
|
|
25822
|
+
} else {
|
|
25823
|
+
positional.push(arg);
|
|
25824
|
+
i += 1;
|
|
25825
|
+
}
|
|
25826
|
+
}
|
|
25827
|
+
return { flags, positional };
|
|
25828
|
+
}
|
|
25829
|
+
function findOnPath(names) {
|
|
25830
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter2)) {
|
|
25831
|
+
if (!dir) continue;
|
|
25832
|
+
for (const name of names) {
|
|
25833
|
+
const candidate = join21(dir, name);
|
|
25834
|
+
try {
|
|
25835
|
+
if (statSync11(candidate).isFile()) return candidate;
|
|
25836
|
+
} catch {
|
|
25837
|
+
}
|
|
25838
|
+
}
|
|
25839
|
+
}
|
|
25840
|
+
return null;
|
|
25841
|
+
}
|
|
25842
|
+
async function initDatabaseOrExit(initDatabase2) {
|
|
25843
|
+
try {
|
|
25844
|
+
await initDatabase2();
|
|
25845
|
+
} catch (err) {
|
|
25846
|
+
console.error(` Error initialising database: ${err instanceof Error ? err.message : String(err)}`);
|
|
25847
|
+
process.exit(1);
|
|
25848
|
+
}
|
|
25849
|
+
}
|
|
25850
|
+
var init_util = __esm({
|
|
25851
|
+
"src/util.ts"() {
|
|
25852
|
+
"use strict";
|
|
25853
|
+
}
|
|
25854
|
+
});
|
|
25855
|
+
|
|
25558
25856
|
// src/commands/generate.ts
|
|
25559
25857
|
var generate_exports = {};
|
|
25560
25858
|
__export(generate_exports, {
|
|
@@ -25579,7 +25877,7 @@ __export(generate_exports, {
|
|
|
25579
25877
|
toTableName: () => toTableName
|
|
25580
25878
|
});
|
|
25581
25879
|
import { existsSync as existsSync16, mkdirSync as mkdirSync13, writeFileSync as writeFileSync10 } from "node:fs";
|
|
25582
|
-
import { join as
|
|
25880
|
+
import { join as join22, relative as relative2, resolve as resolve11, sep as sep5 } from "node:path";
|
|
25583
25881
|
function ensureDir(dir) {
|
|
25584
25882
|
if (__resolution.dryRun) return;
|
|
25585
25883
|
if (!existsSync16(dir)) {
|
|
@@ -25819,28 +26117,7 @@ function parseCliArgs(args) {
|
|
|
25819
26117
|
"json",
|
|
25820
26118
|
"dry-run"
|
|
25821
26119
|
]);
|
|
25822
|
-
|
|
25823
|
-
const positional = [];
|
|
25824
|
-
let i = 0;
|
|
25825
|
-
while (i < args.length) {
|
|
25826
|
-
if (args[i].startsWith("--")) {
|
|
25827
|
-
const key = args[i].slice(2);
|
|
25828
|
-
if (booleanFlags.has(key)) {
|
|
25829
|
-
flags[key] = true;
|
|
25830
|
-
i += 1;
|
|
25831
|
-
} else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
25832
|
-
flags[key] = args[i + 1];
|
|
25833
|
-
i += 2;
|
|
25834
|
-
} else {
|
|
25835
|
-
flags[key] = true;
|
|
25836
|
-
i += 1;
|
|
25837
|
-
}
|
|
25838
|
-
} else {
|
|
25839
|
-
positional.push(args[i]);
|
|
25840
|
-
i += 1;
|
|
25841
|
-
}
|
|
25842
|
-
}
|
|
25843
|
-
return { flags, positional };
|
|
26120
|
+
return parseFlags(args, booleanFlags);
|
|
25844
26121
|
}
|
|
25845
26122
|
function parseEvery(every) {
|
|
25846
26123
|
if (!every || every === true) return 60;
|
|
@@ -25939,7 +26216,7 @@ function generateModel(name, flags, emitTest = true) {
|
|
|
25939
26216
|
const table2 = resolveTable(name, flags, { announce: true });
|
|
25940
26217
|
const dir = resolve11("src/models");
|
|
25941
26218
|
ensureDir(dir);
|
|
25942
|
-
const path10 =
|
|
26219
|
+
const path10 = join22(dir, `${name}.ts`);
|
|
25943
26220
|
setResolutionField("class_name", name);
|
|
25944
26221
|
setResolutionField("table_name", table2);
|
|
25945
26222
|
setResolutionField("file_path", `src/models/${name}.ts`);
|
|
@@ -25979,7 +26256,7 @@ function generateRoute(name, flags, emitTest = true) {
|
|
|
25979
26256
|
const model = flags.model;
|
|
25980
26257
|
const isPublic = Boolean(flags.public);
|
|
25981
26258
|
const base = resolve11("src/routes/api", routePath);
|
|
25982
|
-
const idDir =
|
|
26259
|
+
const idDir = join22(base, "[id]");
|
|
25983
26260
|
ensureDir(base);
|
|
25984
26261
|
ensureDir(idDir);
|
|
25985
26262
|
pushRoute(`/api/${routePath}`);
|
|
@@ -25995,7 +26272,7 @@ function generateRoute(name, flags, emitTest = true) {
|
|
|
25995
26272
|
const writeDoc = isPublic ? "Public (--public): no token required." : "Secure by default: requires a Bearer token (use --public to open).";
|
|
25996
26273
|
if (model) {
|
|
25997
26274
|
writeFileSafe(
|
|
25998
|
-
|
|
26275
|
+
join22(base, "get.ts"),
|
|
25999
26276
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26000
26277
|
${modelImportBase}
|
|
26001
26278
|
export const meta = { summary: "List all ${routePath}", tags: ["${routePath}"] };
|
|
@@ -26012,7 +26289,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26012
26289
|
);
|
|
26013
26290
|
} else {
|
|
26014
26291
|
writeFileSafe(
|
|
26015
|
-
|
|
26292
|
+
join22(base, "get.ts"),
|
|
26016
26293
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26017
26294
|
|
|
26018
26295
|
export const meta = { summary: "List all ${routePath}", tags: ["${routePath}"] };
|
|
@@ -26031,7 +26308,7 @@ ${aiFill(`list_${routePath}`, {
|
|
|
26031
26308
|
}
|
|
26032
26309
|
if (model) {
|
|
26033
26310
|
writeFileSafe(
|
|
26034
|
-
|
|
26311
|
+
join22(base, "post.ts"),
|
|
26035
26312
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26036
26313
|
${modelImportBase}${secureOptOut(isPublic)}export const meta = { summary: "Create a new ${singular}", tags: ["${routePath}"] };
|
|
26037
26314
|
|
|
@@ -26054,7 +26331,7 @@ ${extend(
|
|
|
26054
26331
|
);
|
|
26055
26332
|
} else {
|
|
26056
26333
|
writeFileSafe(
|
|
26057
|
-
|
|
26334
|
+
join22(base, "post.ts"),
|
|
26058
26335
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26059
26336
|
|
|
26060
26337
|
${secureOptOut(isPublic)}export const meta = { summary: "Create a new ${singular}", tags: ["${routePath}"] };
|
|
@@ -26075,7 +26352,7 @@ ${aiFill(`create_${singular}`, {
|
|
|
26075
26352
|
}
|
|
26076
26353
|
if (model) {
|
|
26077
26354
|
writeFileSafe(
|
|
26078
|
-
|
|
26355
|
+
join22(idDir, "get.ts"),
|
|
26079
26356
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26080
26357
|
${modelImportId}
|
|
26081
26358
|
export const meta = { summary: "Get a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -26093,7 +26370,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26093
26370
|
);
|
|
26094
26371
|
} else {
|
|
26095
26372
|
writeFileSafe(
|
|
26096
|
-
|
|
26373
|
+
join22(idDir, "get.ts"),
|
|
26097
26374
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26098
26375
|
|
|
26099
26376
|
export const meta = { summary: "Get a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -26112,7 +26389,7 @@ ${aiFill(`get_${singular}`, {
|
|
|
26112
26389
|
}
|
|
26113
26390
|
if (model) {
|
|
26114
26391
|
writeFileSafe(
|
|
26115
|
-
|
|
26392
|
+
join22(idDir, "put.ts"),
|
|
26116
26393
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26117
26394
|
${modelImportId}${secureOptOut(isPublic)}export const meta = { summary: "Update a ${singular} by ID", tags: ["${routePath}"] };
|
|
26118
26395
|
|
|
@@ -26141,7 +26418,7 @@ ${extend(
|
|
|
26141
26418
|
);
|
|
26142
26419
|
} else {
|
|
26143
26420
|
writeFileSafe(
|
|
26144
|
-
|
|
26421
|
+
join22(idDir, "put.ts"),
|
|
26145
26422
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26146
26423
|
|
|
26147
26424
|
${secureOptOut(isPublic)}export const meta = { summary: "Update a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -26162,7 +26439,7 @@ ${aiFill(`update_${singular}`, {
|
|
|
26162
26439
|
}
|
|
26163
26440
|
if (model) {
|
|
26164
26441
|
writeFileSafe(
|
|
26165
|
-
|
|
26442
|
+
join22(idDir, "delete.ts"),
|
|
26166
26443
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26167
26444
|
${modelImportId}${secureOptOut(isPublic)}export const meta = { summary: "Delete a ${singular} by ID", tags: ["${routePath}"] };
|
|
26168
26445
|
|
|
@@ -26181,7 +26458,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26181
26458
|
);
|
|
26182
26459
|
} else {
|
|
26183
26460
|
writeFileSafe(
|
|
26184
|
-
|
|
26461
|
+
join22(idDir, "delete.ts"),
|
|
26185
26462
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26186
26463
|
|
|
26187
26464
|
${secureOptOut(isPublic)}export const meta = { summary: "Delete a ${singular} by ID", tags: ["${routePath}"] };
|
|
@@ -26243,7 +26520,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
|
|
|
26243
26520
|
const fields = fieldsOverride || parseFields(flags.fields || "");
|
|
26244
26521
|
const isCreate = name.startsWith("create_") || fieldsOverride !== void 0;
|
|
26245
26522
|
const fileName = `${ts}_${name}.sql`;
|
|
26246
|
-
const path10 =
|
|
26523
|
+
const path10 = join22(dir, fileName);
|
|
26247
26524
|
setResolutionField("migration_path", `migrations/${fileName}`);
|
|
26248
26525
|
if (__resolution.target === "migration") {
|
|
26249
26526
|
setResolutionField("file_path", `migrations/${fileName}`);
|
|
@@ -26282,7 +26559,7 @@ ${upSql}
|
|
|
26282
26559
|
${downSql}
|
|
26283
26560
|
`;
|
|
26284
26561
|
writeFileSafe(path10, content);
|
|
26285
|
-
const downPath =
|
|
26562
|
+
const downPath = join22(dir, `${ts}_${name}.down.sql`);
|
|
26286
26563
|
const downContent = `-- Rollback: ${name}
|
|
26287
26564
|
-- Created: ${now}
|
|
26288
26565
|
|
|
@@ -26295,7 +26572,7 @@ function generateMiddleware(name, _flags) {
|
|
|
26295
26572
|
const snake = toSnake(name);
|
|
26296
26573
|
const dir = resolve11("src/middleware");
|
|
26297
26574
|
ensureDir(dir);
|
|
26298
|
-
const path10 =
|
|
26575
|
+
const path10 = join22(dir, `${snake}.ts`);
|
|
26299
26576
|
if (__resolution.target === "middleware") {
|
|
26300
26577
|
setResolutionField("class_name", name);
|
|
26301
26578
|
setResolutionField("file_path", `src/middleware/${snake}.ts`);
|
|
@@ -26342,7 +26619,7 @@ function generateTest(name, flags) {
|
|
|
26342
26619
|
const model = flags.model;
|
|
26343
26620
|
const dir = resolve11("tests");
|
|
26344
26621
|
ensureDir(dir);
|
|
26345
|
-
const path10 =
|
|
26622
|
+
const path10 = join22(dir, `${snake}.test.ts`);
|
|
26346
26623
|
if (model && flags["secure-writes"]) {
|
|
26347
26624
|
const isPublic = Boolean(flags.public);
|
|
26348
26625
|
const posture = isPublic ? "open (--public)" : "gated";
|
|
@@ -26480,7 +26757,7 @@ function generateForm(name, flags) {
|
|
|
26480
26757
|
};
|
|
26481
26758
|
const dir = resolve11("src/templates/forms");
|
|
26482
26759
|
ensureDir(dir);
|
|
26483
|
-
const path10 =
|
|
26760
|
+
const path10 = join22(dir, `${table2}.twig`);
|
|
26484
26761
|
let fieldHtml = "";
|
|
26485
26762
|
for (const [fname, ftype] of fields) {
|
|
26486
26763
|
const itype = inputTypes[ftype] || "text";
|
|
@@ -26533,7 +26810,7 @@ function generateView(name, flags) {
|
|
|
26533
26810
|
const cols = fields.map(([f]) => f);
|
|
26534
26811
|
const dir = resolve11("src/templates/pages");
|
|
26535
26812
|
ensureDir(dir);
|
|
26536
|
-
const listPath =
|
|
26813
|
+
const listPath = join22(dir, `${routeName}.twig`);
|
|
26537
26814
|
const th = cols.map((c) => ` <th>${c.replace(/_/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase())}</th>`).join("\n");
|
|
26538
26815
|
const td = cols.map((c) => ` <td>{{ item.${c} }}</td>`).join("\n");
|
|
26539
26816
|
const listContent = `{% extends "base.twig" %}
|
|
@@ -26570,7 +26847,7 @@ ${td}
|
|
|
26570
26847
|
{% endblock %}
|
|
26571
26848
|
`;
|
|
26572
26849
|
writeFileSafe(listPath, listContent);
|
|
26573
|
-
const detailPath =
|
|
26850
|
+
const detailPath = join22(dir, `${table2}.twig`);
|
|
26574
26851
|
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");
|
|
26575
26852
|
const detailContent = `{% extends "base.twig" %}
|
|
26576
26853
|
{% block title %}${name} Detail{% endblock %}
|
|
@@ -26600,7 +26877,7 @@ function generateAuth(_flags) {
|
|
|
26600
26877
|
ensureDir(loginDir);
|
|
26601
26878
|
ensureDir(meDir);
|
|
26602
26879
|
writeFileSafe(
|
|
26603
|
-
|
|
26880
|
+
join22(registerDir, "post.ts"),
|
|
26604
26881
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26605
26882
|
import { hashPassword } from "tina4-nodejs";
|
|
26606
26883
|
import User from "../../../../models/User.js";
|
|
@@ -26632,7 +26909,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26632
26909
|
`
|
|
26633
26910
|
);
|
|
26634
26911
|
writeFileSafe(
|
|
26635
|
-
|
|
26912
|
+
join22(loginDir, "post.ts"),
|
|
26636
26913
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26637
26914
|
import { checkPassword, getToken } from "tina4-nodejs";
|
|
26638
26915
|
import User from "../../../../models/User.js";
|
|
@@ -26665,7 +26942,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26665
26942
|
`
|
|
26666
26943
|
);
|
|
26667
26944
|
writeFileSafe(
|
|
26668
|
-
|
|
26945
|
+
join22(meDir, "get.ts"),
|
|
26669
26946
|
`import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
26670
26947
|
import { authenticateRequest } from "tina4-nodejs";
|
|
26671
26948
|
|
|
@@ -26684,11 +26961,15 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26684
26961
|
const formsDir = resolve11("src/templates/forms");
|
|
26685
26962
|
ensureDir(formsDir);
|
|
26686
26963
|
writeFileSafe(
|
|
26687
|
-
|
|
26964
|
+
join22(formsDir, "login.twig"),
|
|
26688
26965
|
`{% extends "base.twig" %}
|
|
26689
26966
|
{% block title %}Login{% endblock %}
|
|
26690
26967
|
{% block content %}
|
|
26691
|
-
|
|
26968
|
+
{# No inline style attribute: a nonce lets this <style> block run under the
|
|
26969
|
+
strict default Content-Security-Policy. csp_nonce() is a built-in Frond global
|
|
26970
|
+
that returns this response's nonce (ADR-0088). #}
|
|
26971
|
+
<style nonce="{{ csp_nonce() }}">.auth-form{max-width:400px;margin:0 auto}</style>
|
|
26972
|
+
<div class="container mt-4 auth-form">
|
|
26692
26973
|
<h1>Login</h1>
|
|
26693
26974
|
<form method="post" action="/api/auth/login">
|
|
26694
26975
|
{{ form_token() }}
|
|
@@ -26708,11 +26989,12 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
26708
26989
|
`
|
|
26709
26990
|
);
|
|
26710
26991
|
writeFileSafe(
|
|
26711
|
-
|
|
26992
|
+
join22(formsDir, "register.twig"),
|
|
26712
26993
|
`{% extends "base.twig" %}
|
|
26713
26994
|
{% block title %}Register{% endblock %}
|
|
26714
26995
|
{% block content %}
|
|
26715
|
-
<
|
|
26996
|
+
<style nonce="{{ csp_nonce() }}">.auth-form{max-width:400px;margin:0 auto}</style>
|
|
26997
|
+
<div class="container mt-4 auth-form">
|
|
26716
26998
|
<h1>Register</h1>
|
|
26717
26999
|
<form method="post" action="/api/auth/register">
|
|
26718
27000
|
{{ form_token() }}
|
|
@@ -26746,7 +27028,7 @@ function generateService(name, flags) {
|
|
|
26746
27028
|
const cron = flags.cron;
|
|
26747
27029
|
const dir = resolve11("src/services");
|
|
26748
27030
|
ensureDir(dir);
|
|
26749
|
-
const path10 =
|
|
27031
|
+
const path10 = join22(dir, `${snake}.ts`);
|
|
26750
27032
|
let scheduleField;
|
|
26751
27033
|
let note;
|
|
26752
27034
|
if (cron && cron !== true) {
|
|
@@ -26798,7 +27080,7 @@ function generateQueue(name, _flags) {
|
|
|
26798
27080
|
const pascal = toPascal(topic) || "Topic";
|
|
26799
27081
|
const dir = resolve11("src/services");
|
|
26800
27082
|
ensureDir(dir);
|
|
26801
|
-
const path10 =
|
|
27083
|
+
const path10 = join22(dir, `${slug}_consumer.ts`);
|
|
26802
27084
|
const body = aiFill(`handle${pascal}`, {
|
|
26803
27085
|
intent: `process ONE ${topic} job payload`,
|
|
26804
27086
|
given: "payload -> the produced job data (job.payload)",
|
|
@@ -26859,7 +27141,7 @@ export default {
|
|
|
26859
27141
|
function generateValidator(name, _flags) {
|
|
26860
27142
|
const dir = resolve11("src/validators");
|
|
26861
27143
|
ensureDir(dir);
|
|
26862
|
-
const path10 =
|
|
27144
|
+
const path10 = join22(dir, `${toSnake(name)}.ts`);
|
|
26863
27145
|
const rules = extend(
|
|
26864
27146
|
"add / adjust the validation rules for this payload",
|
|
26865
27147
|
`e.g. .email("email").minLength("name", 2).integer("age"); ground: tina4_context("validate request body with Validator", "nodejs")`
|
|
@@ -26887,7 +27169,7 @@ function generateSeeder(name, flags) {
|
|
|
26887
27169
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
26888
27170
|
const dir = resolve11("src/seeds");
|
|
26889
27171
|
ensureDir(dir);
|
|
26890
|
-
const path10 =
|
|
27172
|
+
const path10 = join22(dir, `${table2}_seeder.ts`);
|
|
26891
27173
|
const overrides = extend(
|
|
26892
27174
|
"override fields that need a specific shape (seedOrm auto-fills the rest)",
|
|
26893
27175
|
`e.g. return { email: (f) => f.email(), status: "active" }; ground: tina4_context("seed ORM model with FakeData", "nodejs")`
|
|
@@ -26932,7 +27214,7 @@ function generateWebsocket(name, _flags) {
|
|
|
26932
27214
|
const handlerName = `${toCamel(toPascal(base))}Ws`;
|
|
26933
27215
|
const dir = resolve11("src/routes");
|
|
26934
27216
|
ensureDir(dir);
|
|
26935
|
-
const path10 =
|
|
27217
|
+
const path10 = join22(dir, `ws_${base}.ts`);
|
|
26936
27218
|
const body = aiFill(handlerName, {
|
|
26937
27219
|
intent: `handle an inbound "message" frame on ${wsPath}`,
|
|
26938
27220
|
given: "data -> the message payload (string); connection -> WebSocketConnection",
|
|
@@ -26983,7 +27265,7 @@ function generateListener(name, _flags) {
|
|
|
26983
27265
|
const handlerName = `on${toPascal(slug)}`;
|
|
26984
27266
|
const dir = resolve11("src/listeners");
|
|
26985
27267
|
ensureDir(dir);
|
|
26986
|
-
const path10 =
|
|
27268
|
+
const path10 = join22(dir, `${slug}.ts`);
|
|
26987
27269
|
const body = aiFill(handlerName, {
|
|
26988
27270
|
intent: `react to the '${event}' event`,
|
|
26989
27271
|
given: `args -> whatever Events.emit("${event}", ...args) passed`,
|
|
@@ -27015,7 +27297,7 @@ Events.on("${event}", ${handlerName});
|
|
|
27015
27297
|
function writeTest(testName, content) {
|
|
27016
27298
|
const dir = resolve11("tests");
|
|
27017
27299
|
ensureDir(dir);
|
|
27018
|
-
writeFileSafe(
|
|
27300
|
+
writeFileSafe(join22(dir, `${testName}.test.ts`), content);
|
|
27019
27301
|
}
|
|
27020
27302
|
function standaloneTest(doc, body) {
|
|
27021
27303
|
return `${doc}
|
|
@@ -27401,6 +27683,7 @@ var FIELD_TYPE_MAP, SQL_RESERVED_TABLE_NAMES, RESOLUTION_ENVELOPE_VERSION, __res
|
|
|
27401
27683
|
var init_generate = __esm({
|
|
27402
27684
|
"src/commands/generate.ts"() {
|
|
27403
27685
|
"use strict";
|
|
27686
|
+
init_util();
|
|
27404
27687
|
FIELD_TYPE_MAP = {
|
|
27405
27688
|
string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
|
|
27406
27689
|
str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
|
|
@@ -27648,7 +27931,7 @@ import * as path3 from "node:path";
|
|
|
27648
27931
|
import * as os from "node:os";
|
|
27649
27932
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
27650
27933
|
import { createRequire as createRequire8 } from "node:module";
|
|
27651
|
-
import { randomBytes as
|
|
27934
|
+
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
27652
27935
|
function reqSibling(pkg) {
|
|
27653
27936
|
return req(`../../${pkg}/src/index.ts`);
|
|
27654
27937
|
}
|
|
@@ -28970,7 +29253,7 @@ var init_mcp = __esm({
|
|
|
28970
29253
|
// ── Session lifecycle + protocol negotiation ──────────────────
|
|
28971
29254
|
/** Mint a new session id and remember it. Called on `initialize`. */
|
|
28972
29255
|
openSession() {
|
|
28973
|
-
const sid =
|
|
29256
|
+
const sid = randomBytes7(16).toString("hex");
|
|
28974
29257
|
this._sessions.set(sid, Date.now());
|
|
28975
29258
|
return sid;
|
|
28976
29259
|
}
|
|
@@ -30459,7 +30742,7 @@ __export(context_exports, {
|
|
|
30459
30742
|
});
|
|
30460
30743
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
30461
30744
|
import { existsSync as existsSync18, mkdirSync as mkdirSync15, readFileSync as readFileSync15, readdirSync as readdirSync10, realpathSync as realpathSync5 } from "node:fs";
|
|
30462
|
-
import { basename as basename5, dirname as dirname11, extname as extname5, isAbsolute as isAbsolute6, join as
|
|
30745
|
+
import { basename as basename5, dirname as dirname11, extname as extname5, isAbsolute as isAbsolute6, join as join24, relative as relative4, resolve as resolve13 } from "node:path";
|
|
30463
30746
|
function fts5Supported() {
|
|
30464
30747
|
try {
|
|
30465
30748
|
const conn = new DatabaseSync4(":memory:");
|
|
@@ -30482,13 +30765,13 @@ function realResolve(abs) {
|
|
|
30482
30765
|
} catch {
|
|
30483
30766
|
}
|
|
30484
30767
|
try {
|
|
30485
|
-
return
|
|
30768
|
+
return join24(realpathSync5(dirname11(abs)), basename5(abs));
|
|
30486
30769
|
} catch {
|
|
30487
30770
|
return abs;
|
|
30488
30771
|
}
|
|
30489
30772
|
}
|
|
30490
30773
|
function dbKey(db) {
|
|
30491
|
-
return resolve13(db ? String(db) :
|
|
30774
|
+
return resolve13(db ? String(db) : join24(process.cwd(), ".tina4", "context.db"));
|
|
30492
30775
|
}
|
|
30493
30776
|
function defaultContext(root, db) {
|
|
30494
30777
|
const key = dbKey(db);
|
|
@@ -30661,11 +30944,11 @@ var init_context = __esm({
|
|
|
30661
30944
|
files.sort();
|
|
30662
30945
|
for (const fn of files) {
|
|
30663
30946
|
if (!_Context.eligible(fn)) continue;
|
|
30664
|
-
const full =
|
|
30947
|
+
const full = join24(dir, fn);
|
|
30665
30948
|
const rel = relative4(rootAbs, full);
|
|
30666
30949
|
total += this.indexPath(full, rel);
|
|
30667
30950
|
}
|
|
30668
|
-
for (const d of subdirs) walk2(
|
|
30951
|
+
for (const d of subdirs) walk2(join24(dir, d));
|
|
30669
30952
|
};
|
|
30670
30953
|
walk2(rootAbs);
|
|
30671
30954
|
return total;
|
|
@@ -30681,7 +30964,7 @@ var init_context = __esm({
|
|
|
30681
30964
|
reindexFile(changedPath) {
|
|
30682
30965
|
if (!this.available || this.root === null) return -1;
|
|
30683
30966
|
const raw = String(changedPath);
|
|
30684
|
-
const abs = isAbsolute6(raw) ? raw :
|
|
30967
|
+
const abs = isAbsolute6(raw) ? raw : join24(process.cwd(), raw);
|
|
30685
30968
|
const resolved = realResolve(resolve13(abs));
|
|
30686
30969
|
const rel = relative4(this.root, resolved);
|
|
30687
30970
|
if (rel === "" || rel.startsWith("..") || isAbsolute6(rel)) {
|
|
@@ -31019,6 +31302,7 @@ __export(websocket_exports, {
|
|
|
31019
31302
|
CLOSE_NORMAL: () => CLOSE_NORMAL,
|
|
31020
31303
|
CLOSE_POLICY_VIOLATION: () => CLOSE_POLICY_VIOLATION,
|
|
31021
31304
|
CLOSE_PROTOCOL_ERROR: () => CLOSE_PROTOCOL_ERROR,
|
|
31305
|
+
MessageAssembler: () => MessageAssembler,
|
|
31022
31306
|
OP_BINARY: () => OP_BINARY,
|
|
31023
31307
|
OP_CLOSE: () => OP_CLOSE,
|
|
31024
31308
|
OP_CONTINUATION: () => OP_CONTINUATION,
|
|
@@ -31276,21 +31560,24 @@ function serveWebSocketRoute(req2, socket, head) {
|
|
|
31276
31560
|
state.closed = true;
|
|
31277
31561
|
void Promise.resolve().then(() => conn._onClose ? conn._onClose() : handler(conn, "close", "")).catch((e) => Log.error(`WebSocket close handler error: ${e.message}`)).finally(() => wsRouteManager.remove(conn.id));
|
|
31278
31562
|
};
|
|
31563
|
+
const assembler = new MessageAssembler();
|
|
31279
31564
|
socket.on("data", (chunk) => {
|
|
31280
31565
|
buffer = Buffer.concat([buffer, chunk]);
|
|
31281
31566
|
while (buffer.length > 0) {
|
|
31282
31567
|
const frame = parseFrame(buffer);
|
|
31283
31568
|
if (!frame) break;
|
|
31284
31569
|
buffer = buffer.subarray(frame.bytesConsumed);
|
|
31285
|
-
|
|
31570
|
+
const whole = assembler.push(frame);
|
|
31571
|
+
if (!whole) continue;
|
|
31572
|
+
switch (whole.opcode) {
|
|
31286
31573
|
case OP_TEXT: {
|
|
31287
|
-
const text =
|
|
31574
|
+
const text = whole.payload.toString("utf-8");
|
|
31288
31575
|
void Promise.resolve().then(() => conn._onMessage ? conn._onMessage(text) : handler(conn, "message", text)).catch((e) => Log.error(`WebSocket message handler error: ${e.message}`));
|
|
31289
31576
|
break;
|
|
31290
31577
|
}
|
|
31291
31578
|
case OP_PING:
|
|
31292
31579
|
try {
|
|
31293
|
-
socket.write(buildFrame(OP_PONG,
|
|
31580
|
+
socket.write(buildFrame(OP_PONG, whole.payload));
|
|
31294
31581
|
} catch {
|
|
31295
31582
|
}
|
|
31296
31583
|
break;
|
|
@@ -31309,7 +31596,7 @@ function serveWebSocketRoute(req2, socket, head) {
|
|
|
31309
31596
|
socket.on("error", fireClose);
|
|
31310
31597
|
return true;
|
|
31311
31598
|
}
|
|
31312
|
-
var MAGIC_STRING, OP_CONTINUATION, OP_TEXT, OP_BINARY, OP_CLOSE, OP_PING, OP_PONG, CLOSE_NORMAL, CLOSE_GOING_AWAY, CLOSE_PROTOCOL_ERROR, CLOSE_POLICY_VIOLATION, WebSocketServer, WsRouteManager, wsRouteManager, DevReloadWsManager, devReloadWs;
|
|
31599
|
+
var MAGIC_STRING, OP_CONTINUATION, OP_TEXT, OP_BINARY, OP_CLOSE, OP_PING, OP_PONG, CLOSE_NORMAL, CLOSE_GOING_AWAY, CLOSE_PROTOCOL_ERROR, CLOSE_POLICY_VIOLATION, MessageAssembler, WebSocketServer, WsRouteManager, wsRouteManager, DevReloadWsManager, devReloadWs;
|
|
31313
31600
|
var init_websocket = __esm({
|
|
31314
31601
|
"../core/src/websocket.ts"() {
|
|
31315
31602
|
"use strict";
|
|
@@ -31328,6 +31615,30 @@ var init_websocket = __esm({
|
|
|
31328
31615
|
CLOSE_GOING_AWAY = 1001;
|
|
31329
31616
|
CLOSE_PROTOCOL_ERROR = 1002;
|
|
31330
31617
|
CLOSE_POLICY_VIOLATION = 1008;
|
|
31618
|
+
MessageAssembler = class {
|
|
31619
|
+
parts = [];
|
|
31620
|
+
opcode = 0;
|
|
31621
|
+
push(frame) {
|
|
31622
|
+
if (frame.opcode >= 8) return { opcode: frame.opcode, payload: frame.payload };
|
|
31623
|
+
if (frame.opcode === OP_CONTINUATION) {
|
|
31624
|
+
if (!this.opcode) return null;
|
|
31625
|
+
this.parts.push(frame.payload);
|
|
31626
|
+
if (!frame.fin) return null;
|
|
31627
|
+
const whole = { opcode: this.opcode, payload: Buffer.concat(this.parts) };
|
|
31628
|
+
this.parts = [];
|
|
31629
|
+
this.opcode = 0;
|
|
31630
|
+
return whole;
|
|
31631
|
+
}
|
|
31632
|
+
if (frame.fin) {
|
|
31633
|
+
this.parts = [];
|
|
31634
|
+
this.opcode = 0;
|
|
31635
|
+
return { opcode: frame.opcode, payload: frame.payload };
|
|
31636
|
+
}
|
|
31637
|
+
this.opcode = frame.opcode;
|
|
31638
|
+
this.parts = [frame.payload];
|
|
31639
|
+
return null;
|
|
31640
|
+
}
|
|
31641
|
+
};
|
|
31331
31642
|
WebSocketServer = class {
|
|
31332
31643
|
port;
|
|
31333
31644
|
server = null;
|
|
@@ -31770,6 +32081,15 @@ var init_websocket = __esm({
|
|
|
31770
32081
|
this.emit("error", err, client);
|
|
31771
32082
|
});
|
|
31772
32083
|
}
|
|
32084
|
+
assemblers = /* @__PURE__ */ new WeakMap();
|
|
32085
|
+
assemblerFor(client) {
|
|
32086
|
+
let a = this.assemblers.get(client);
|
|
32087
|
+
if (!a) {
|
|
32088
|
+
a = new MessageAssembler();
|
|
32089
|
+
this.assemblers.set(client, a);
|
|
32090
|
+
}
|
|
32091
|
+
return a;
|
|
32092
|
+
}
|
|
31773
32093
|
processBuffer(client, buffer, setBuffer) {
|
|
31774
32094
|
let remaining = buffer;
|
|
31775
32095
|
while (remaining.length > 0) {
|
|
@@ -31777,15 +32097,17 @@ var init_websocket = __esm({
|
|
|
31777
32097
|
if (!frame) break;
|
|
31778
32098
|
remaining = remaining.subarray(frame.bytesConsumed);
|
|
31779
32099
|
client.lastActivity = Date.now();
|
|
31780
|
-
|
|
32100
|
+
const whole = this.assemblerFor(client).push(frame);
|
|
32101
|
+
if (!whole) continue;
|
|
32102
|
+
switch (whole.opcode) {
|
|
31781
32103
|
case OP_TEXT:
|
|
31782
|
-
this.emit("message", client,
|
|
32104
|
+
this.emit("message", client, whole.payload.toString("utf-8"));
|
|
31783
32105
|
break;
|
|
31784
32106
|
case OP_BINARY:
|
|
31785
|
-
this.emit("message", client,
|
|
32107
|
+
this.emit("message", client, whole.payload);
|
|
31786
32108
|
break;
|
|
31787
32109
|
case OP_PING: {
|
|
31788
|
-
const pongFrame = buildFrame(OP_PONG,
|
|
32110
|
+
const pongFrame = buildFrame(OP_PONG, whole.payload);
|
|
31789
32111
|
try {
|
|
31790
32112
|
client.socket.write(pongFrame);
|
|
31791
32113
|
} catch {
|
|
@@ -32704,7 +33026,7 @@ var init_job = __esm({
|
|
|
32704
33026
|
|
|
32705
33027
|
// ../core/src/queueBackends/liteBackend.ts
|
|
32706
33028
|
import { mkdirSync as mkdirSync16, readdirSync as readdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync7, existsSync as existsSync19 } from "node:fs";
|
|
32707
|
-
import { join as
|
|
33029
|
+
import { join as join25 } from "node:path";
|
|
32708
33030
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
32709
33031
|
var LiteBackend;
|
|
32710
33032
|
var init_liteBackend = __esm({
|
|
@@ -32728,22 +33050,22 @@ var init_liteBackend = __esm({
|
|
|
32728
33050
|
this.visibilityTimeout = visibilityTimeout;
|
|
32729
33051
|
}
|
|
32730
33052
|
ensureDir(queue) {
|
|
32731
|
-
const dir =
|
|
33053
|
+
const dir = join25(this.basePath, queue);
|
|
32732
33054
|
mkdirSync16(dir, { recursive: true });
|
|
32733
33055
|
return dir;
|
|
32734
33056
|
}
|
|
32735
33057
|
ensureFailedDir(queue) {
|
|
32736
|
-
const dir =
|
|
33058
|
+
const dir = join25(this.basePath, queue, "failed");
|
|
32737
33059
|
mkdirSync16(dir, { recursive: true });
|
|
32738
33060
|
return dir;
|
|
32739
33061
|
}
|
|
32740
33062
|
ensureReservedDir(queue) {
|
|
32741
|
-
const dir =
|
|
33063
|
+
const dir = join25(this.basePath, queue, "reserved");
|
|
32742
33064
|
mkdirSync16(dir, { recursive: true });
|
|
32743
33065
|
return dir;
|
|
32744
33066
|
}
|
|
32745
33067
|
reservedPath(queue, jobId) {
|
|
32746
|
-
return
|
|
33068
|
+
return join25(this.ensureReservedDir(queue), `${jobId}.queue-data`);
|
|
32747
33069
|
}
|
|
32748
33070
|
nowIso() {
|
|
32749
33071
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -32781,7 +33103,7 @@ var init_liteBackend = __esm({
|
|
|
32781
33103
|
error: void 0
|
|
32782
33104
|
};
|
|
32783
33105
|
const prefix = this.nextPrefix();
|
|
32784
|
-
writeFileSync12(
|
|
33106
|
+
writeFileSync12(join25(dir, `${prefix}_${id}.queue-data`), JSON.stringify(job, null, 2));
|
|
32785
33107
|
return id;
|
|
32786
33108
|
}
|
|
32787
33109
|
/**
|
|
@@ -32800,7 +33122,7 @@ var init_liteBackend = __esm({
|
|
|
32800
33122
|
}
|
|
32801
33123
|
const candidates = [];
|
|
32802
33124
|
for (const filename of filenames) {
|
|
32803
|
-
const filePath =
|
|
33125
|
+
const filePath = join25(dir, filename);
|
|
32804
33126
|
let job;
|
|
32805
33127
|
try {
|
|
32806
33128
|
job = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
@@ -32864,7 +33186,7 @@ var init_liteBackend = __esm({
|
|
|
32864
33186
|
return;
|
|
32865
33187
|
}
|
|
32866
33188
|
for (const filename of filenames) {
|
|
32867
|
-
const filePath =
|
|
33189
|
+
const filePath = join25(reservedDir, filename);
|
|
32868
33190
|
let record;
|
|
32869
33191
|
try {
|
|
32870
33192
|
record = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
@@ -32902,7 +33224,7 @@ var init_liteBackend = __esm({
|
|
|
32902
33224
|
this.reclaimExpired(queue, bridge.getMaxRetries(), this.nowIso());
|
|
32903
33225
|
const now = this.nowIso();
|
|
32904
33226
|
for (const [filename, job] of this.availableCandidates(queue, now)) {
|
|
32905
|
-
const filePath =
|
|
33227
|
+
const filePath = join25(dir, filename);
|
|
32906
33228
|
job.topic = queue;
|
|
32907
33229
|
job.priority = job.priority ?? 0;
|
|
32908
33230
|
this.writeReserved(queue, job);
|
|
@@ -32927,7 +33249,7 @@ var init_liteBackend = __esm({
|
|
|
32927
33249
|
const results = [];
|
|
32928
33250
|
for (const [filename, job] of this.availableCandidates(queue, now)) {
|
|
32929
33251
|
if (results.length >= count) break;
|
|
32930
|
-
const filePath =
|
|
33252
|
+
const filePath = join25(dir, filename);
|
|
32931
33253
|
job.topic = queue;
|
|
32932
33254
|
job.priority = job.priority ?? 0;
|
|
32933
33255
|
this.writeReserved(queue, job);
|
|
@@ -32980,7 +33302,7 @@ var init_liteBackend = __esm({
|
|
|
32980
33302
|
let count = 0;
|
|
32981
33303
|
for (const file of files) {
|
|
32982
33304
|
try {
|
|
32983
|
-
const job = JSON.parse(readFileSync16(
|
|
33305
|
+
const job = JSON.parse(readFileSync16(join25(scanDir, file), "utf-8"));
|
|
32984
33306
|
if (job.status === status2) count++;
|
|
32985
33307
|
} catch {
|
|
32986
33308
|
}
|
|
@@ -32993,28 +33315,28 @@ var init_liteBackend = __esm({
|
|
|
32993
33315
|
try {
|
|
32994
33316
|
const files = readdirSync11(dir).filter((f) => f.endsWith(".queue-data"));
|
|
32995
33317
|
for (const file of files) {
|
|
32996
|
-
unlinkSync7(
|
|
33318
|
+
unlinkSync7(join25(dir, file));
|
|
32997
33319
|
count++;
|
|
32998
33320
|
}
|
|
32999
33321
|
} catch {
|
|
33000
33322
|
}
|
|
33001
|
-
const failedDir =
|
|
33323
|
+
const failedDir = join25(dir, "failed");
|
|
33002
33324
|
try {
|
|
33003
33325
|
if (existsSync19(failedDir)) {
|
|
33004
33326
|
const files = readdirSync11(failedDir).filter((f) => f.endsWith(".queue-data"));
|
|
33005
33327
|
for (const file of files) {
|
|
33006
|
-
unlinkSync7(
|
|
33328
|
+
unlinkSync7(join25(failedDir, file));
|
|
33007
33329
|
count++;
|
|
33008
33330
|
}
|
|
33009
33331
|
}
|
|
33010
33332
|
} catch {
|
|
33011
33333
|
}
|
|
33012
|
-
const reservedDir =
|
|
33334
|
+
const reservedDir = join25(dir, "reserved");
|
|
33013
33335
|
try {
|
|
33014
33336
|
if (existsSync19(reservedDir)) {
|
|
33015
33337
|
const files = readdirSync11(reservedDir).filter((f) => f.endsWith(".queue-data"));
|
|
33016
33338
|
for (const file of files) {
|
|
33017
|
-
unlinkSync7(
|
|
33339
|
+
unlinkSync7(join25(reservedDir, file));
|
|
33018
33340
|
count++;
|
|
33019
33341
|
}
|
|
33020
33342
|
}
|
|
@@ -33037,7 +33359,7 @@ var init_liteBackend = __esm({
|
|
|
33037
33359
|
const files = readdirSync11(dir).filter((f) => f.endsWith(".queue-data")).sort();
|
|
33038
33360
|
for (const file of files) {
|
|
33039
33361
|
try {
|
|
33040
|
-
const job = JSON.parse(readFileSync16(
|
|
33362
|
+
const job = JSON.parse(readFileSync16(join25(dir, file), "utf-8"));
|
|
33041
33363
|
const attempts = job.attempts || 0;
|
|
33042
33364
|
if (attempts > 0 && attempts < maxRetries) {
|
|
33043
33365
|
results.push(job);
|
|
@@ -33060,8 +33382,8 @@ var init_liteBackend = __esm({
|
|
|
33060
33382
|
try {
|
|
33061
33383
|
const queues = readdirSync11(this.basePath);
|
|
33062
33384
|
for (const q of queues) {
|
|
33063
|
-
const failedDir =
|
|
33064
|
-
const filePath =
|
|
33385
|
+
const failedDir = join25(this.basePath, q, "failed");
|
|
33386
|
+
const filePath = join25(failedDir, `${jobId}.queue-data`);
|
|
33065
33387
|
if (existsSync19(filePath)) {
|
|
33066
33388
|
const job = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
33067
33389
|
job.status = "pending";
|
|
@@ -33070,8 +33392,8 @@ var init_liteBackend = __esm({
|
|
|
33070
33392
|
job.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
33071
33393
|
job.delayUntil = delaySeconds ? new Date(Date.now() + delaySeconds * 1e3).toISOString() : null;
|
|
33072
33394
|
const prefix = this.nextPrefix();
|
|
33073
|
-
const queueDir =
|
|
33074
|
-
writeFileSync12(
|
|
33395
|
+
const queueDir = join25(this.basePath, q);
|
|
33396
|
+
writeFileSync12(join25(queueDir, `${prefix}_${jobId}.queue-data`), JSON.stringify(job, null, 2));
|
|
33075
33397
|
unlinkSync7(filePath);
|
|
33076
33398
|
return true;
|
|
33077
33399
|
}
|
|
@@ -33087,7 +33409,7 @@ var init_liteBackend = __esm({
|
|
|
33087
33409
|
const files = readdirSync11(failedDir).filter((f) => f.endsWith(".queue-data")).sort();
|
|
33088
33410
|
for (const file of files) {
|
|
33089
33411
|
try {
|
|
33090
|
-
const job = JSON.parse(readFileSync16(
|
|
33412
|
+
const job = JSON.parse(readFileSync16(join25(failedDir, file), "utf-8"));
|
|
33091
33413
|
if ((job.attempts || 0) >= maxRetries) {
|
|
33092
33414
|
job.status = "dead";
|
|
33093
33415
|
results.push(job);
|
|
@@ -33108,7 +33430,7 @@ var init_liteBackend = __esm({
|
|
|
33108
33430
|
const files = readdirSync11(failedDir).filter((f) => f.endsWith(".queue-data"));
|
|
33109
33431
|
for (const file of files) {
|
|
33110
33432
|
try {
|
|
33111
|
-
unlinkSync7(
|
|
33433
|
+
unlinkSync7(join25(failedDir, file));
|
|
33112
33434
|
count++;
|
|
33113
33435
|
} catch {
|
|
33114
33436
|
}
|
|
@@ -33121,9 +33443,9 @@ var init_liteBackend = __esm({
|
|
|
33121
33443
|
const files = readdirSync11(dir).filter((f) => f.endsWith(".queue-data"));
|
|
33122
33444
|
for (const file of files) {
|
|
33123
33445
|
try {
|
|
33124
|
-
const job = JSON.parse(readFileSync16(
|
|
33446
|
+
const job = JSON.parse(readFileSync16(join25(dir, file), "utf-8"));
|
|
33125
33447
|
if (job.status === status2) {
|
|
33126
|
-
unlinkSync7(
|
|
33448
|
+
unlinkSync7(join25(dir, file));
|
|
33127
33449
|
count++;
|
|
33128
33450
|
}
|
|
33129
33451
|
} catch {
|
|
@@ -33147,7 +33469,7 @@ var init_liteBackend = __esm({
|
|
|
33147
33469
|
const files = readdirSync11(failedDir).filter((f) => f.endsWith(".queue-data"));
|
|
33148
33470
|
for (const file of files) {
|
|
33149
33471
|
try {
|
|
33150
|
-
const filePath =
|
|
33472
|
+
const filePath = join25(failedDir, file);
|
|
33151
33473
|
const job = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
33152
33474
|
if ((job.attempts || 0) >= maxRetries) {
|
|
33153
33475
|
continue;
|
|
@@ -33157,7 +33479,7 @@ var init_liteBackend = __esm({
|
|
|
33157
33479
|
job.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
33158
33480
|
job.delayUntil = null;
|
|
33159
33481
|
const prefix = this.nextPrefix();
|
|
33160
|
-
writeFileSync12(
|
|
33482
|
+
writeFileSync12(join25(queueDir, `${prefix}_${job.id}.queue-data`), JSON.stringify(job, null, 2));
|
|
33161
33483
|
unlinkSync7(filePath);
|
|
33162
33484
|
count++;
|
|
33163
33485
|
} catch {
|
|
@@ -33177,7 +33499,7 @@ var init_liteBackend = __esm({
|
|
|
33177
33499
|
}
|
|
33178
33500
|
for (const file of files) {
|
|
33179
33501
|
if (!file.includes(id)) continue;
|
|
33180
|
-
const filePath =
|
|
33502
|
+
const filePath = join25(dir, file);
|
|
33181
33503
|
let job;
|
|
33182
33504
|
try {
|
|
33183
33505
|
job = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
@@ -33225,7 +33547,7 @@ var init_liteBackend = __esm({
|
|
|
33225
33547
|
error: error2
|
|
33226
33548
|
};
|
|
33227
33549
|
const prefix = this.nextPrefix();
|
|
33228
|
-
writeFileSync12(
|
|
33550
|
+
writeFileSync12(join25(dir, `${prefix}_${job.id}.queue-data`), JSON.stringify(jobData, null, 2));
|
|
33229
33551
|
}
|
|
33230
33552
|
/**
|
|
33231
33553
|
* Move the job to the dead-letter (failed/) directory. Terminal until a
|
|
@@ -33245,7 +33567,7 @@ var init_liteBackend = __esm({
|
|
|
33245
33567
|
error: error2,
|
|
33246
33568
|
failedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
33247
33569
|
};
|
|
33248
|
-
writeFileSync12(
|
|
33570
|
+
writeFileSync12(join25(failedDir, `${job.id}.queue-data`), JSON.stringify(jobData, null, 2));
|
|
33249
33571
|
}
|
|
33250
33572
|
/**
|
|
33251
33573
|
* Record a failed attempt.
|
|
@@ -33295,7 +33617,7 @@ var init_liteBackend = __esm({
|
|
|
33295
33617
|
retryJob(queue, job, delaySeconds) {
|
|
33296
33618
|
this.clearReservation(queue, job.id);
|
|
33297
33619
|
try {
|
|
33298
|
-
unlinkSync7(
|
|
33620
|
+
unlinkSync7(join25(this.ensureFailedDir(queue), `${job.id}.queue-data`));
|
|
33299
33621
|
} catch {
|
|
33300
33622
|
}
|
|
33301
33623
|
job.attempts = (job.attempts || 0) + 1;
|
|
@@ -35915,8 +36237,8 @@ ${end}
|
|
|
35915
36237
|
|
|
35916
36238
|
// ../core/src/devAdmin.ts
|
|
35917
36239
|
import { cpus as osCpus } from "node:os";
|
|
35918
|
-
import { readFileSync as readFileSync20, writeFileSync as writeFileSync16, existsSync as existsSync23, readdirSync as readdirSync15, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, statSync as
|
|
35919
|
-
import { join as
|
|
36240
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync16, existsSync as existsSync23, readdirSync as readdirSync15, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, statSync as statSync18, openSync as openSync4, closeSync as closeSync4, fstatSync as fstatSync2, fchmodSync as fchmodSync2, ftruncateSync, constants as constants3, realpathSync as realpathSync6, lstatSync } from "node:fs";
|
|
36241
|
+
import { join as join29, dirname as dirname13, resolve as resolve17, relative as relative9, sep as sep8, basename as basename6 } from "node:path";
|
|
35920
36242
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
35921
36243
|
import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
35922
36244
|
function escapeHtml(value) {
|
|
@@ -36060,7 +36382,7 @@ function readQueueDir(dir, topic, status2) {
|
|
|
36060
36382
|
for (const filename of readdirSync15(dir).sort()) {
|
|
36061
36383
|
if (!filename.endsWith(".queue-data")) continue;
|
|
36062
36384
|
try {
|
|
36063
|
-
jobs.push(mapQueueJob(JSON.parse(readFileSync20(
|
|
36385
|
+
jobs.push(mapQueueJob(JSON.parse(readFileSync20(join29(dir, filename), "utf-8")), topic, status2));
|
|
36064
36386
|
} catch {
|
|
36065
36387
|
}
|
|
36066
36388
|
}
|
|
@@ -36163,21 +36485,12 @@ async function proxyToSupervisor(req2, res, downstreamPath) {
|
|
|
36163
36485
|
return;
|
|
36164
36486
|
}
|
|
36165
36487
|
clearTimeout(timer);
|
|
36166
|
-
|
|
36167
|
-
const status2 = upstream.status || 200;
|
|
36168
|
-
try {
|
|
36169
|
-
res.json(JSON.parse(raw), status2);
|
|
36170
|
-
} catch {
|
|
36171
|
-
res.raw.writeHead(status2, {
|
|
36172
|
-
"Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
|
|
36173
|
-
});
|
|
36174
|
-
res.raw.end(raw);
|
|
36175
|
-
}
|
|
36488
|
+
await relayUpstreamResponse(res, upstream);
|
|
36176
36489
|
}
|
|
36177
36490
|
function resolveDevEnvVar(key) {
|
|
36178
36491
|
const live = process.env[key];
|
|
36179
36492
|
if (live !== void 0 && live !== "") return live;
|
|
36180
|
-
const envPath =
|
|
36493
|
+
const envPath = join29(process.cwd(), ".env");
|
|
36181
36494
|
if (!existsSync23(envPath)) return "";
|
|
36182
36495
|
for (const line of readFileSync20(envPath, "utf-8").split("\n")) {
|
|
36183
36496
|
const t = line.trim();
|
|
@@ -36188,7 +36501,7 @@ function resolveDevEnvVar(key) {
|
|
|
36188
36501
|
return "";
|
|
36189
36502
|
}
|
|
36190
36503
|
function upsertDevEnvVar(key, value) {
|
|
36191
|
-
const envPath =
|
|
36504
|
+
const envPath = join29(process.cwd(), ".env");
|
|
36192
36505
|
const lines = existsSync23(envPath) ? readFileSync20(envPath, "utf-8").split("\n") : [];
|
|
36193
36506
|
let found = false;
|
|
36194
36507
|
const out = [];
|
|
@@ -36219,7 +36532,7 @@ function formatUptime(seconds) {
|
|
|
36219
36532
|
return parts.join(" ");
|
|
36220
36533
|
}
|
|
36221
36534
|
function parseEnvFile() {
|
|
36222
|
-
const envPath =
|
|
36535
|
+
const envPath = join29(process.cwd(), ".env");
|
|
36223
36536
|
const result = {};
|
|
36224
36537
|
if (!existsSync23(envPath)) return result;
|
|
36225
36538
|
const lines = readFileSync20(envPath, "utf-8").split("\n");
|
|
@@ -36235,8 +36548,8 @@ function walkDirRecursive(dir) {
|
|
|
36235
36548
|
const results = [];
|
|
36236
36549
|
if (!existsSync23(dir)) return results;
|
|
36237
36550
|
for (const entry of readdirSync15(dir)) {
|
|
36238
|
-
const full =
|
|
36239
|
-
if (
|
|
36551
|
+
const full = join29(dir, entry);
|
|
36552
|
+
if (statSync18(full).isDirectory()) {
|
|
36240
36553
|
results.push(...walkDirRecursive(full));
|
|
36241
36554
|
} else {
|
|
36242
36555
|
results.push(full);
|
|
@@ -36253,7 +36566,7 @@ function handleGalleryDeploy(router) {
|
|
|
36253
36566
|
return;
|
|
36254
36567
|
}
|
|
36255
36568
|
const galleryDir = resolve17(__devAdminDirname, "..", "gallery");
|
|
36256
|
-
const gallerySrc =
|
|
36569
|
+
const gallerySrc = join29(galleryDir, name, "src");
|
|
36257
36570
|
if (!existsSync23(gallerySrc)) {
|
|
36258
36571
|
res.json({ error: `Gallery item '${name}' not found` }, 404);
|
|
36259
36572
|
return;
|
|
@@ -36263,7 +36576,7 @@ function handleGalleryDeploy(router) {
|
|
|
36263
36576
|
const allFiles = walkDirRecursive(gallerySrc);
|
|
36264
36577
|
for (const srcFile of allFiles) {
|
|
36265
36578
|
const rel = relative9(gallerySrc, srcFile);
|
|
36266
|
-
const dest =
|
|
36579
|
+
const dest = join29(projectSrc, rel);
|
|
36267
36580
|
mkdirSync19(dirname13(dest), { recursive: true });
|
|
36268
36581
|
copyFileSync2(srcFile, dest);
|
|
36269
36582
|
copied.push(rel);
|
|
@@ -36299,7 +36612,7 @@ function realPathOf(target) {
|
|
|
36299
36612
|
tail.unshift(basename6(existing));
|
|
36300
36613
|
existing = dirname13(existing);
|
|
36301
36614
|
}
|
|
36302
|
-
return
|
|
36615
|
+
return join29(realpathSync6(existing), ...tail);
|
|
36303
36616
|
}
|
|
36304
36617
|
function safeJoin(projectRoot3, rel, extraRoots = []) {
|
|
36305
36618
|
try {
|
|
@@ -36614,6 +36927,7 @@ var init_devAdmin = __esm({
|
|
|
36614
36927
|
"../core/src/devAdmin.ts"() {
|
|
36615
36928
|
"use strict";
|
|
36616
36929
|
init_devMailbox();
|
|
36930
|
+
init_upstreamResponse();
|
|
36617
36931
|
init_dotenv();
|
|
36618
36932
|
init_metrics();
|
|
36619
36933
|
init_feedback();
|
|
@@ -36715,8 +37029,8 @@ var init_devAdmin = __esm({
|
|
|
36715
37029
|
* Duplicate errors (same message) increment count and update last_seen.
|
|
36716
37030
|
*/
|
|
36717
37031
|
static capture(errorType, message, traceback = "", file = "", line = 0) {
|
|
36718
|
-
const
|
|
36719
|
-
const existing = this.errors.find((e) => e.fingerprint ===
|
|
37032
|
+
const fingerprint2 = `${errorType}|${message}|${file}|${line}`;
|
|
37033
|
+
const existing = this.errors.find((e) => e.fingerprint === fingerprint2);
|
|
36720
37034
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
36721
37035
|
if (existing) {
|
|
36722
37036
|
existing.count = (existing.count || 1) + 1;
|
|
@@ -36729,7 +37043,7 @@ var init_devAdmin = __esm({
|
|
|
36729
37043
|
message,
|
|
36730
37044
|
stack: traceback || void 0,
|
|
36731
37045
|
resolved: false,
|
|
36732
|
-
...{ fingerprint, error_type: errorType, file, line, count: 1, first_seen: now, last_seen: now }
|
|
37046
|
+
...{ fingerprint: fingerprint2, error_type: errorType, file, line, count: 1, first_seen: now, last_seen: now }
|
|
36733
37047
|
});
|
|
36734
37048
|
if (this.errors.length > this.maxErrors) {
|
|
36735
37049
|
this.errors = this.errors.slice(-this.maxErrors);
|
|
@@ -36981,7 +37295,7 @@ var init_devAdmin = __esm({
|
|
|
36981
37295
|
const p = (url.searchParams.get("path") || "").toString();
|
|
36982
37296
|
const scanRoot = metricsScanRoot();
|
|
36983
37297
|
let target = p ? safeJoin(process.cwd(), p, [scanRoot]) : "";
|
|
36984
|
-
if (target === null && scanRoot) target = safeJoin(process.cwd(),
|
|
37298
|
+
if (target === null && scanRoot) target = safeJoin(process.cwd(), join29(scanRoot, p), [scanRoot]);
|
|
36985
37299
|
if (target === null) {
|
|
36986
37300
|
res.status(403).json({ error: "Path outside project" });
|
|
36987
37301
|
return;
|
|
@@ -37296,16 +37610,16 @@ var init_devAdmin = __esm({
|
|
|
37296
37610
|
failed: queue.size("failed"),
|
|
37297
37611
|
reserved: queue.size("reserved")
|
|
37298
37612
|
};
|
|
37299
|
-
const topicDir =
|
|
37613
|
+
const topicDir = join29(queueBasePath2(), topic);
|
|
37300
37614
|
const jobs = [];
|
|
37301
37615
|
if (!statusFilter || statusFilter === "pending") {
|
|
37302
37616
|
jobs.push(...readQueueDir(topicDir, topic, "pending"));
|
|
37303
37617
|
}
|
|
37304
37618
|
if (!statusFilter || statusFilter === "reserved") {
|
|
37305
|
-
jobs.push(...readQueueDir(
|
|
37619
|
+
jobs.push(...readQueueDir(join29(topicDir, "reserved"), topic, "reserved"));
|
|
37306
37620
|
}
|
|
37307
37621
|
if (!statusFilter || statusFilter === "failed" || statusFilter === "dead") {
|
|
37308
|
-
jobs.push(...readQueueDir(
|
|
37622
|
+
jobs.push(...readQueueDir(join29(topicDir, "failed"), topic, "dead_letter"));
|
|
37309
37623
|
}
|
|
37310
37624
|
res.json({ stats, jobs });
|
|
37311
37625
|
} catch (e) {
|
|
@@ -37324,7 +37638,7 @@ var init_devAdmin = __esm({
|
|
|
37324
37638
|
if (existsSync23(queueDir)) {
|
|
37325
37639
|
topics = readdirSync15(queueDir).filter((d) => {
|
|
37326
37640
|
try {
|
|
37327
|
-
return
|
|
37641
|
+
return statSync18(join29(queueDir, d)).isDirectory();
|
|
37328
37642
|
} catch {
|
|
37329
37643
|
return false;
|
|
37330
37644
|
}
|
|
@@ -37782,7 +38096,7 @@ var init_devAdmin = __esm({
|
|
|
37782
38096
|
return;
|
|
37783
38097
|
}
|
|
37784
38098
|
try {
|
|
37785
|
-
const envPath =
|
|
38099
|
+
const envPath = join29(process.cwd(), ".env");
|
|
37786
38100
|
const fd = openSync4(envPath, constants3.O_RDWR | constants3.O_APPEND | constants3.O_CREAT | (constants3.O_NOFOLLOW ?? 0) | (constants3.O_NONBLOCK ?? 0), 384);
|
|
37787
38101
|
try {
|
|
37788
38102
|
const info = fstatSync2(fd);
|
|
@@ -37834,13 +38148,13 @@ var init_devAdmin = __esm({
|
|
|
37834
38148
|
if (existsSync23(galleryDir)) {
|
|
37835
38149
|
const entries = readdirSync15(galleryDir).sort();
|
|
37836
38150
|
for (const entry of entries) {
|
|
37837
|
-
const entryPath =
|
|
37838
|
-
const metaFile =
|
|
37839
|
-
if (
|
|
38151
|
+
const entryPath = join29(galleryDir, entry);
|
|
38152
|
+
const metaFile = join29(entryPath, "meta.json");
|
|
38153
|
+
if (statSync18(entryPath).isDirectory() && existsSync23(metaFile)) {
|
|
37840
38154
|
try {
|
|
37841
38155
|
const meta = JSON.parse(readFileSync20(metaFile, "utf-8"));
|
|
37842
38156
|
meta.id = entry;
|
|
37843
|
-
const srcDir =
|
|
38157
|
+
const srcDir = join29(entryPath, "src");
|
|
37844
38158
|
if (existsSync23(srcDir)) {
|
|
37845
38159
|
const allFiles = walkDirRecursive(srcDir);
|
|
37846
38160
|
meta.files = allFiles.map((f) => relative9(srcDir, f));
|
|
@@ -37848,7 +38162,7 @@ var init_devAdmin = __esm({
|
|
|
37848
38162
|
const projectSrc = resolve17(process.cwd(), "src");
|
|
37849
38163
|
if (existsSync23(srcDir) && meta.files) {
|
|
37850
38164
|
meta.deployed = meta.files.every(
|
|
37851
|
-
(f) => existsSync23(
|
|
38165
|
+
(f) => existsSync23(join29(projectSrc, f))
|
|
37852
38166
|
);
|
|
37853
38167
|
} else {
|
|
37854
38168
|
meta.deployed = false;
|
|
@@ -37925,7 +38239,7 @@ var init_devAdmin = __esm({
|
|
|
37925
38239
|
const root = resolve17(process.cwd());
|
|
37926
38240
|
const target = safeJoin(root, rel);
|
|
37927
38241
|
const { branch, gitRoot, status: gitStatus } = await devGitInfo(root);
|
|
37928
|
-
if (!target || !existsSync23(target) || !
|
|
38242
|
+
if (!target || !existsSync23(target) || !statSync18(target).isDirectory()) {
|
|
37929
38243
|
res.json({ path: rel, branch, entries: [], error: "not a directory" });
|
|
37930
38244
|
return;
|
|
37931
38245
|
}
|
|
@@ -37938,13 +38252,13 @@ var init_devAdmin = __esm({
|
|
|
37938
38252
|
const entries = [];
|
|
37939
38253
|
for (const name of readdirSync15(target).sort()) {
|
|
37940
38254
|
if (devFilesHidden(name)) continue;
|
|
37941
|
-
const full =
|
|
38255
|
+
const full = join29(target, name);
|
|
37942
38256
|
const entryRel = relative9(root, full).replace(/\\/g, "/");
|
|
37943
38257
|
if (isSecretPath(entryRel)) continue;
|
|
37944
38258
|
let isDir = false;
|
|
37945
38259
|
let size = null;
|
|
37946
38260
|
try {
|
|
37947
|
-
const st =
|
|
38261
|
+
const st = statSync18(full);
|
|
37948
38262
|
isDir = st.isDirectory();
|
|
37949
38263
|
if (!isDir) size = st.size;
|
|
37950
38264
|
} catch {
|
|
@@ -38489,13 +38803,13 @@ var init_devAdmin = __esm({
|
|
|
38489
38803
|
};
|
|
38490
38804
|
handleDevAdminJs = async (_req, res) => {
|
|
38491
38805
|
const { readFileSync: readFileSync30, existsSync: existsSync37 } = await import("node:fs");
|
|
38492
|
-
const { dirname: dirname18, join:
|
|
38806
|
+
const { dirname: dirname18, join: join41, resolve: resolve29 } = await import("node:path");
|
|
38493
38807
|
const { fileURLToPath: fileURLToPath10 } = await import("node:url");
|
|
38494
38808
|
const dir = dirname18(fileURLToPath10(import.meta.url));
|
|
38495
38809
|
const candidates = [
|
|
38496
|
-
|
|
38810
|
+
join41(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
38497
38811
|
// src/../public/js/
|
|
38498
|
-
|
|
38812
|
+
join41(dir, "..", "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
38499
38813
|
// deeper nesting
|
|
38500
38814
|
resolve29(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin.min.js"),
|
|
38501
38815
|
resolve29(process.cwd(), "public", "js", "tina4-dev-admin.min.js")
|
|
@@ -38528,7 +38842,7 @@ var init_devAdmin = __esm({
|
|
|
38528
38842
|
|
|
38529
38843
|
// ../core/src/i18n.ts
|
|
38530
38844
|
import { readFileSync as readFileSync21, readdirSync as readdirSync16, existsSync as existsSync24 } from "node:fs";
|
|
38531
|
-
import { join as
|
|
38845
|
+
import { join as join30, resolve as resolve18 } from "node:path";
|
|
38532
38846
|
var I18n;
|
|
38533
38847
|
var init_i18n = __esm({
|
|
38534
38848
|
"../core/src/i18n.ts"() {
|
|
@@ -38621,7 +38935,7 @@ var init_i18n = __esm({
|
|
|
38621
38935
|
if (this._translations.has(locale)) {
|
|
38622
38936
|
return;
|
|
38623
38937
|
}
|
|
38624
|
-
const filePath =
|
|
38938
|
+
const filePath = join30(this._localeDir, `${locale}.json`);
|
|
38625
38939
|
if (existsSync24(filePath)) {
|
|
38626
38940
|
try {
|
|
38627
38941
|
const raw = readFileSync21(filePath, "utf-8");
|
|
@@ -38634,7 +38948,7 @@ var init_i18n = __esm({
|
|
|
38634
38948
|
}
|
|
38635
38949
|
}
|
|
38636
38950
|
for (const ext of [".yml", ".yaml"]) {
|
|
38637
|
-
const yamlPath =
|
|
38951
|
+
const yamlPath = join30(this._localeDir, `${locale}${ext}`);
|
|
38638
38952
|
if (existsSync24(yamlPath)) {
|
|
38639
38953
|
try {
|
|
38640
38954
|
const raw = readFileSync21(yamlPath, "utf-8");
|
|
@@ -39408,7 +39722,7 @@ function swaggerEnabled() {
|
|
|
39408
39722
|
}
|
|
39409
39723
|
function createSwaggerRoutes(getSpec) {
|
|
39410
39724
|
const serveUi = async (_req, res) => {
|
|
39411
|
-
res.html(SWAGGER_UI_HTML("/swagger/openapi.json"));
|
|
39725
|
+
res.html(SWAGGER_UI_HTML("/swagger/openapi.json", res.cspNonce));
|
|
39412
39726
|
};
|
|
39413
39727
|
return [
|
|
39414
39728
|
{
|
|
@@ -39452,13 +39766,13 @@ var SWAGGER_UI_HTML;
|
|
|
39452
39766
|
var init_ui = __esm({
|
|
39453
39767
|
"../swagger/src/ui.ts"() {
|
|
39454
39768
|
"use strict";
|
|
39455
|
-
SWAGGER_UI_HTML = (specUrl) => `<!DOCTYPE html>
|
|
39769
|
+
SWAGGER_UI_HTML = (specUrl, nonce) => `<!DOCTYPE html>
|
|
39456
39770
|
<html lang="en">
|
|
39457
39771
|
<head>
|
|
39458
39772
|
<meta charset="UTF-8">
|
|
39459
39773
|
<title>Tina4 API Documentation</title>
|
|
39460
39774
|
<link rel="stylesheet" href="${swaggerUiCdn()}/swagger-ui.css">
|
|
39461
|
-
<style>
|
|
39775
|
+
<style nonce="${nonce}">
|
|
39462
39776
|
body { margin: 0; background: #fafafa; }
|
|
39463
39777
|
.topbar { display: none !important; }
|
|
39464
39778
|
</style>
|
|
@@ -39466,7 +39780,7 @@ var init_ui = __esm({
|
|
|
39466
39780
|
<body>
|
|
39467
39781
|
<div id="swagger-ui"></div>
|
|
39468
39782
|
<script src="${swaggerUiCdn()}/swagger-ui-bundle.js"></script>
|
|
39469
|
-
<script>
|
|
39783
|
+
<script nonce="${nonce}">
|
|
39470
39784
|
SwaggerUIBundle({
|
|
39471
39785
|
url: "${specUrl}",
|
|
39472
39786
|
dom_id: '#swagger-ui',
|
|
@@ -39506,7 +39820,7 @@ __export(sso_exports, {
|
|
|
39506
39820
|
SsoError: () => SsoError,
|
|
39507
39821
|
liveSessionIdentity: () => liveSessionIdentity
|
|
39508
39822
|
});
|
|
39509
|
-
import { createHash as createHash9, randomBytes as
|
|
39823
|
+
import { createHash as createHash9, randomBytes as randomBytes8, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
39510
39824
|
function expiresAt(tokens) {
|
|
39511
39825
|
const lifetime = Number(tokens.expires_in ?? 0);
|
|
39512
39826
|
return lifetime > 0 ? Math.floor(Date.now() / 1e3) + lifetime : 0;
|
|
@@ -39634,9 +39948,9 @@ var init_sso = __esm({
|
|
|
39634
39948
|
async login(requestOrSession, returnTo = "/") {
|
|
39635
39949
|
const session = this.session(requestOrSession);
|
|
39636
39950
|
if (!session) throw new SsoError("SSO login requires a Tina4 Session");
|
|
39637
|
-
const state =
|
|
39638
|
-
const nonce =
|
|
39639
|
-
const verifier =
|
|
39951
|
+
const state = randomBytes8(32).toString("base64url");
|
|
39952
|
+
const nonce = randomBytes8(32).toString("base64url");
|
|
39953
|
+
const verifier = randomBytes8(64).toString("base64url");
|
|
39640
39954
|
const challenge = createHash9("sha256").update(verifier).digest("base64url");
|
|
39641
39955
|
session.set(_Sso.PENDING_KEY, { state, nonce, verifier, return_to: _Sso.safeReturn(returnTo), created_at: Math.floor(Date.now() / 1e3) });
|
|
39642
39956
|
const metadata = await this.discover();
|
|
@@ -39864,9 +40178,9 @@ var init_docsAutoDiscovery = __esm({
|
|
|
39864
40178
|
});
|
|
39865
40179
|
|
|
39866
40180
|
// ../core/src/server.ts
|
|
39867
|
-
import { randomBytes as
|
|
39868
|
-
import { resolve as resolve20, dirname as dirname14, join as
|
|
39869
|
-
import { existsSync as existsSync26, readdirSync as readdirSync17, readFileSync as readFileSync23, statSync as
|
|
40181
|
+
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
40182
|
+
import { resolve as resolve20, dirname as dirname14, join as join32, relative as relative10 } from "node:path";
|
|
40183
|
+
import { existsSync as existsSync26, readdirSync as readdirSync17, readFileSync as readFileSync23, statSync as statSync19 } from "node:fs";
|
|
39870
40184
|
import { isatty } from "node:tty";
|
|
39871
40185
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
39872
40186
|
import { execFileSync as execFileSync3, exec } from "node:child_process";
|
|
@@ -40044,7 +40358,8 @@ function isDevMode() {
|
|
|
40044
40358
|
}
|
|
40045
40359
|
async function renderErrorPage(code, data, templatesDir) {
|
|
40046
40360
|
try {
|
|
40047
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
40361
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
40362
|
+
setCspNonceProvider2(currentCspNonce);
|
|
40048
40363
|
const templateFile = `errors/${code}.twig`;
|
|
40049
40364
|
const getCachedFrond = (dir) => {
|
|
40050
40365
|
let instance = frondCache.get(dir);
|
|
@@ -40054,11 +40369,11 @@ async function renderErrorPage(code, data, templatesDir) {
|
|
|
40054
40369
|
}
|
|
40055
40370
|
return instance;
|
|
40056
40371
|
};
|
|
40057
|
-
const userTemplatePath =
|
|
40372
|
+
const userTemplatePath = join32(templatesDir, templateFile);
|
|
40058
40373
|
if (existsSync26(userTemplatePath)) {
|
|
40059
40374
|
return getCachedFrond(templatesDir).render(templateFile, data);
|
|
40060
40375
|
}
|
|
40061
|
-
const builtinTemplatePath =
|
|
40376
|
+
const builtinTemplatePath = join32(BUILTIN_ERROR_TEMPLATES_DIR, templateFile);
|
|
40062
40377
|
if (existsSync26(builtinTemplatePath)) {
|
|
40063
40378
|
return getCachedFrond(BUILTIN_ERROR_TEMPLATES_DIR).render(templateFile, data);
|
|
40064
40379
|
}
|
|
@@ -40078,8 +40393,8 @@ function walkGalleryFiles(dir) {
|
|
|
40078
40393
|
const results = [];
|
|
40079
40394
|
if (!existsSync26(dir)) return results;
|
|
40080
40395
|
for (const f of readdirSync17(dir)) {
|
|
40081
|
-
const full =
|
|
40082
|
-
if (
|
|
40396
|
+
const full = join32(dir, f);
|
|
40397
|
+
if (statSync19(full).isDirectory()) results.push(...walkGalleryFiles(full));
|
|
40083
40398
|
else results.push(full);
|
|
40084
40399
|
}
|
|
40085
40400
|
return results;
|
|
@@ -40091,14 +40406,14 @@ function getGalleryDeployedState() {
|
|
|
40091
40406
|
try {
|
|
40092
40407
|
const entries = readdirSync17(galleryDir).sort();
|
|
40093
40408
|
for (const entry of entries) {
|
|
40094
|
-
const entryPath =
|
|
40095
|
-
const metaFile =
|
|
40096
|
-
if (
|
|
40097
|
-
const srcDir =
|
|
40409
|
+
const entryPath = join32(galleryDir, entry);
|
|
40410
|
+
const metaFile = join32(entryPath, "meta.json");
|
|
40411
|
+
if (statSync19(entryPath).isDirectory() && existsSync26(metaFile)) {
|
|
40412
|
+
const srcDir = join32(entryPath, "src");
|
|
40098
40413
|
if (existsSync26(srcDir)) {
|
|
40099
40414
|
const files = walkGalleryFiles(srcDir);
|
|
40100
40415
|
const projectSrc = resolve20(process.cwd(), "src");
|
|
40101
|
-
state[entry] = files.every((f) => existsSync26(
|
|
40416
|
+
state[entry] = files.every((f) => existsSync26(join32(projectSrc, relative10(srcDir, f))));
|
|
40102
40417
|
} else {
|
|
40103
40418
|
state[entry] = false;
|
|
40104
40419
|
}
|
|
@@ -40160,6 +40475,7 @@ function resolveTemplate(pathname, templatesDir) {
|
|
|
40160
40475
|
}
|
|
40161
40476
|
function renderLandingPage(port = 7148) {
|
|
40162
40477
|
const version = TINA4_VERSION;
|
|
40478
|
+
const nonce = currentCspNonce();
|
|
40163
40479
|
const galleryItems = [
|
|
40164
40480
|
{ id: "rest-api", icon: "🚀", name: "REST API", desc: "A simple JSON API with GET and POST endpoints", accent: "accent-blue", tryUrl: "/api/gallery/hello" },
|
|
40165
40481
|
{ id: "orm", icon: "🗃", name: "ORM", desc: "Product model with CRUD endpoints", accent: "accent-green", tryUrl: "/api/gallery/products" },
|
|
@@ -40174,7 +40490,7 @@ function renderLandingPage(port = 7148) {
|
|
|
40174
40490
|
const isDeployed = deployed[item.id] === true;
|
|
40175
40491
|
const tryBtn = isDeployed ? `<a href="${item.tryUrl}" class="gbtn gbtn-try" target="_blank">Try It</a>` : "";
|
|
40176
40492
|
const viewBtn = isDeployed ? `<a href="${item.tryUrl}" class="gbtn gbtn-view" target="_blank">View</a>` : "";
|
|
40177
|
-
const deployBtn = isDeployed ? `<span class="gbtn gbtn-deployed">Deployed</span>` : `<button class="gbtn gbtn-deploy"
|
|
40493
|
+
const deployBtn = isDeployed ? `<span class="gbtn gbtn-deployed">Deployed</span>` : `<button class="gbtn gbtn-deploy" data-deploy="${item.id}">Deploy</button>`;
|
|
40178
40494
|
return `<div class="gallery-card">
|
|
40179
40495
|
<div class="accent ${item.accent}"></div>
|
|
40180
40496
|
<div class="icon">${item.icon}</div>
|
|
@@ -40189,7 +40505,7 @@ function renderLandingPage(port = 7148) {
|
|
|
40189
40505
|
<meta charset="utf-8">
|
|
40190
40506
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
40191
40507
|
<title>Tina4NodeJs</title>
|
|
40192
|
-
<style>
|
|
40508
|
+
<style nonce="${nonce}">
|
|
40193
40509
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
40194
40510
|
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;display:flex;flex-direction:column;align-items:center;position:relative}
|
|
40195
40511
|
.bg-watermark{position:fixed;bottom:-5%;right:-5%;width:45%;opacity:0.04;pointer-events:none;z-index:0}
|
|
@@ -40228,6 +40544,11 @@ h1{font-size:3rem;font-weight:700;margin-bottom:0.25rem;letter-spacing:-1px}
|
|
|
40228
40544
|
.gbtn-deployed{background:transparent;border:1px solid #22c55e;color:#22c55e;cursor:default;font-size:0.7rem}
|
|
40229
40545
|
@keyframes wiggle{0%{transform:rotate(0deg)}15%{transform:rotate(14deg)}30%{transform:rotate(-10deg)}45%{transform:rotate(8deg)}60%{transform:rotate(-4deg)}75%{transform:rotate(2deg)}100%{transform:rotate(0deg)}}
|
|
40230
40546
|
.star-wiggle{display:inline-block;transform-origin:center}
|
|
40547
|
+
.gallery-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1rem}
|
|
40548
|
+
.tok-comment{color:#64748b}
|
|
40549
|
+
.tok-kw{color:#c084fc}
|
|
40550
|
+
.tok-str{color:#4ade80}
|
|
40551
|
+
.tok-fn{color:#38bdf8}
|
|
40231
40552
|
</style>
|
|
40232
40553
|
</head>
|
|
40233
40554
|
<body>
|
|
@@ -40253,23 +40574,23 @@ h1{font-size:3rem;font-weight:700;margin-bottom:0.25rem;letter-spacing:-1px}
|
|
|
40253
40574
|
<div class="section">
|
|
40254
40575
|
<div class="card">
|
|
40255
40576
|
<h2>Getting Started</h2>
|
|
40256
|
-
<pre class="code-block"><code><span
|
|
40257
|
-
<span
|
|
40577
|
+
<pre class="code-block"><code><span class="tok-comment">// app.ts</span>
|
|
40578
|
+
<span class="tok-kw">import</span> { startServer, Router } <span class="tok-kw">from</span> <span class="tok-str">"tina4-nodejs"</span>;
|
|
40258
40579
|
|
|
40259
|
-
Router.get(<span
|
|
40260
|
-
<span
|
|
40580
|
+
Router.get(<span class="tok-str">"/hello"</span>, <span class="tok-kw">async</span> (<span class="tok-fn">req</span>, <span class="tok-fn">res</span>) => {
|
|
40581
|
+
<span class="tok-kw">return</span> res.json({ message: <span class="tok-str">"Hello World!"</span> });
|
|
40261
40582
|
});
|
|
40262
40583
|
|
|
40263
|
-
startServer({ port: 7148 }); <span
|
|
40584
|
+
startServer({ port: 7148 }); <span class="tok-comment">// starts on port 7148</span></code></pre>
|
|
40264
40585
|
</div>
|
|
40265
40586
|
</div>
|
|
40266
40587
|
<div class="gallery">
|
|
40267
40588
|
<h2 id="gallery">What You Can Build</h2>
|
|
40268
|
-
<div
|
|
40589
|
+
<div class="gallery-cards">
|
|
40269
40590
|
${galleryCards}
|
|
40270
40591
|
</div>
|
|
40271
40592
|
</div>
|
|
40272
|
-
<script>
|
|
40593
|
+
<script nonce="${nonce}">
|
|
40273
40594
|
function deployGallery(name) {
|
|
40274
40595
|
if (!confirm('Deploy the "' + name + '" gallery example into your project? This will copy files into src/.')) return;
|
|
40275
40596
|
fetch('/__dev/api/gallery/deploy', {
|
|
@@ -40304,6 +40625,12 @@ function deployGallery(name) {
|
|
|
40304
40625
|
}
|
|
40305
40626
|
setTimeout(doWiggle,3000);
|
|
40306
40627
|
})();
|
|
40628
|
+
// CSP-clean wiring: no inline onclick handlers \u2014 bind every Deploy button from
|
|
40629
|
+
// its data-deploy attribute (a nonce covers script elements, not event-handler
|
|
40630
|
+
// attributes).
|
|
40631
|
+
document.querySelectorAll('.gbtn-deploy[data-deploy]').forEach(function(btn){
|
|
40632
|
+
btn.addEventListener('click', function(){ deployGallery(btn.dataset.deploy); });
|
|
40633
|
+
});
|
|
40307
40634
|
</script>
|
|
40308
40635
|
</body>
|
|
40309
40636
|
</html>`;
|
|
@@ -40520,7 +40847,7 @@ async function renderDispatchError(err, req2, res, templatesDir) {
|
|
|
40520
40847
|
}
|
|
40521
40848
|
}
|
|
40522
40849
|
}
|
|
40523
|
-
const requestId = Log.getRequestId() ??
|
|
40850
|
+
const requestId = Log.getRequestId() ?? randomBytes9(4).toString("hex");
|
|
40524
40851
|
if (wantsJson(req2)) {
|
|
40525
40852
|
const body = negotiatedErrorBody(500, "Internal Server Error", requestId);
|
|
40526
40853
|
res.raw.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -40591,7 +40918,7 @@ function serveStaticAsset(ctx) {
|
|
|
40591
40918
|
return false;
|
|
40592
40919
|
}
|
|
40593
40920
|
async function serveNotFound(ctx) {
|
|
40594
|
-
const requestId = Log.getRequestId() ??
|
|
40921
|
+
const requestId = Log.getRequestId() ?? randomBytes9(4).toString("hex");
|
|
40595
40922
|
if (wantsJson(ctx.req)) {
|
|
40596
40923
|
const body = negotiatedErrorBody(404, "Not Found", requestId);
|
|
40597
40924
|
ctx.res.raw.writeHead(404, httpReason(404), { "Content-Type": "application/json" });
|
|
@@ -40614,7 +40941,8 @@ async function buildDispatchContext(router, base) {
|
|
|
40614
40941
|
const templatesDir = resolve20(root, "src/templates");
|
|
40615
40942
|
let frondEngine = null;
|
|
40616
40943
|
try {
|
|
40617
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
40944
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
40945
|
+
setCspNonceProvider2(currentCspNonce);
|
|
40618
40946
|
frondEngine = new Frond2(templatesDir);
|
|
40619
40947
|
} catch {
|
|
40620
40948
|
}
|
|
@@ -40724,9 +41052,13 @@ async function dispatchInner(ctx, rawReq, rawRes, requestId) {
|
|
|
40724
41052
|
}
|
|
40725
41053
|
}
|
|
40726
41054
|
async function runDispatch(ctx, rawReq, rawRes) {
|
|
40727
|
-
const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ??
|
|
41055
|
+
const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ?? randomBytes9(4).toString("hex");
|
|
40728
41056
|
if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
|
|
40729
|
-
|
|
41057
|
+
const requestNonce = generateNonce();
|
|
41058
|
+
return Log.runWithRequestId(
|
|
41059
|
+
requestId,
|
|
41060
|
+
() => runWithCspNonce(requestNonce, () => dispatchInner(ctx, rawReq, rawRes, requestId))
|
|
41061
|
+
);
|
|
40730
41062
|
}
|
|
40731
41063
|
function loopbackBindHosts(host) {
|
|
40732
41064
|
const normalized = host.trim().replace(/^\[|\]$/g, "").toLowerCase();
|
|
@@ -40853,7 +41185,8 @@ async function startServer(config) {
|
|
|
40853
41185
|
let frondEngine = null;
|
|
40854
41186
|
setDefaultTemplatesDir(templatesDir);
|
|
40855
41187
|
try {
|
|
40856
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
41188
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
41189
|
+
setCspNonceProvider2(currentCspNonce);
|
|
40857
41190
|
frondEngine = new Frond2(templatesDir);
|
|
40858
41191
|
router.addRoute({
|
|
40859
41192
|
method: "GET",
|
|
@@ -40994,10 +41327,12 @@ async function startServer(config) {
|
|
|
40994
41327
|
}
|
|
40995
41328
|
});
|
|
40996
41329
|
aiServer.on("error", (err) => {
|
|
40997
|
-
if (err
|
|
41330
|
+
if (err?.code === "EADDRINUSE") {
|
|
40998
41331
|
Log.warning(`Test port ${testPort} in use \u2014 skipping`);
|
|
40999
|
-
|
|
41332
|
+
} else {
|
|
41333
|
+
Log.error(`Test port ${testPort} bind failed (${err?.code ?? err}) \u2014 skipping`);
|
|
41000
41334
|
}
|
|
41335
|
+
aiServer = null;
|
|
41001
41336
|
});
|
|
41002
41337
|
aiServer.listen(testPort, host);
|
|
41003
41338
|
}
|
|
@@ -41212,6 +41547,7 @@ var init_server = __esm({
|
|
|
41212
41547
|
init_static();
|
|
41213
41548
|
init_dotenv();
|
|
41214
41549
|
init_errorOverlay();
|
|
41550
|
+
init_csp();
|
|
41215
41551
|
init_health();
|
|
41216
41552
|
init_rateLimiter();
|
|
41217
41553
|
init_logger();
|
|
@@ -41461,7 +41797,7 @@ var init_mqttMessage = __esm({
|
|
|
41461
41797
|
// ../core/src/mqtt.ts
|
|
41462
41798
|
import net2 from "node:net";
|
|
41463
41799
|
import tls from "node:tls";
|
|
41464
|
-
import { randomBytes as
|
|
41800
|
+
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
41465
41801
|
import { existsSync as existsSync27, readFileSync as readFileSync24 } from "node:fs";
|
|
41466
41802
|
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;
|
|
41467
41803
|
var init_mqtt = __esm({
|
|
@@ -41549,7 +41885,7 @@ var init_mqtt = __esm({
|
|
|
41549
41885
|
this.caFile = options.caFile ?? (Env.str("TINA4_MQTT_CA_FILE") || null);
|
|
41550
41886
|
this.tlsVerify = options.tlsVerify ?? Env.bool("TINA4_MQTT_TLS_VERIFY", true);
|
|
41551
41887
|
let cid = options.clientId ?? (Env.str("TINA4_MQTT_CLIENT_ID") || null);
|
|
41552
|
-
if (cid === null || cid === "") cid = "tina4-" +
|
|
41888
|
+
if (cid === null || cid === "") cid = "tina4-" + randomBytes10(8).toString("hex");
|
|
41553
41889
|
this.clientId = cid;
|
|
41554
41890
|
this.keepalive = options.keepalive ?? Env.int("TINA4_MQTT_KEEPALIVE", DEFAULT_KEEPALIVE);
|
|
41555
41891
|
this.cleanSession = options.cleanSession ?? true;
|
|
@@ -42147,8 +42483,8 @@ var init_mqtt = __esm({
|
|
|
42147
42483
|
});
|
|
42148
42484
|
|
|
42149
42485
|
// ../core/src/service.ts
|
|
42150
|
-
import { readdirSync as readdirSync18, statSync as
|
|
42151
|
-
import { join as
|
|
42486
|
+
import { readdirSync as readdirSync18, statSync as statSync20, watchFile, unwatchFile } from "node:fs";
|
|
42487
|
+
import { join as join33, extname as extname8 } from "node:path";
|
|
42152
42488
|
import { pathToFileURL } from "node:url";
|
|
42153
42489
|
function matchCronField(field, value) {
|
|
42154
42490
|
if (field === "*") return true;
|
|
@@ -42322,8 +42658,8 @@ var init_service = __esm({
|
|
|
42322
42658
|
for (const entry of entries) {
|
|
42323
42659
|
const ext = extname8(entry);
|
|
42324
42660
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
42325
|
-
const fullPath =
|
|
42326
|
-
const stat =
|
|
42661
|
+
const fullPath = join33(dir, entry);
|
|
42662
|
+
const stat = statSync20(fullPath);
|
|
42327
42663
|
if (!stat.isFile()) continue;
|
|
42328
42664
|
try {
|
|
42329
42665
|
const fileUrl = pathToFileURL(fullPath).href;
|
|
@@ -42449,7 +42785,7 @@ var init_service = __esm({
|
|
|
42449
42785
|
for (const entry of entries) {
|
|
42450
42786
|
const ext = extname8(entry);
|
|
42451
42787
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
42452
|
-
const fullPath =
|
|
42788
|
+
const fullPath = join33(dir, entry);
|
|
42453
42789
|
if (watchedFiles.has(fullPath)) continue;
|
|
42454
42790
|
watchedFiles.add(fullPath);
|
|
42455
42791
|
watchFile(fullPath, { interval: 1e3 }, async () => {
|
|
@@ -42663,7 +42999,7 @@ var init_ssrf = __esm({
|
|
|
42663
42999
|
import http from "node:http";
|
|
42664
43000
|
import https from "node:https";
|
|
42665
43001
|
import { URL as URL2 } from "node:url";
|
|
42666
|
-
import { randomBytes as
|
|
43002
|
+
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
42667
43003
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
42668
43004
|
import { basename as basename7 } from "node:path";
|
|
42669
43005
|
import { pipeline } from "node:stream/promises";
|
|
@@ -42709,23 +43045,23 @@ function guessContentType(filename) {
|
|
|
42709
43045
|
}
|
|
42710
43046
|
function buildMultipartBody(boundary, fieldName, filename, fileContent, contentType, extraFields) {
|
|
42711
43047
|
const crlf = "\r\n";
|
|
42712
|
-
const
|
|
43048
|
+
const delimiter4 = `--${boundary}`;
|
|
42713
43049
|
const parts = [];
|
|
42714
43050
|
if (extraFields) {
|
|
42715
43051
|
for (const [key, value] of Object.entries(extraFields)) {
|
|
42716
|
-
parts.push(Buffer.from(
|
|
43052
|
+
parts.push(Buffer.from(delimiter4 + crlf, "utf-8"));
|
|
42717
43053
|
parts.push(Buffer.from(`Content-Disposition: form-data; name="${key}"` + crlf + crlf, "utf-8"));
|
|
42718
43054
|
parts.push(Buffer.from(String(value) + crlf, "utf-8"));
|
|
42719
43055
|
}
|
|
42720
43056
|
}
|
|
42721
|
-
parts.push(Buffer.from(
|
|
43057
|
+
parts.push(Buffer.from(delimiter4 + crlf, "utf-8"));
|
|
42722
43058
|
parts.push(
|
|
42723
43059
|
Buffer.from(`Content-Disposition: form-data; name="${fieldName}"; filename="${filename}"` + crlf, "utf-8")
|
|
42724
43060
|
);
|
|
42725
43061
|
parts.push(Buffer.from(`Content-Type: ${contentType}` + crlf + crlf, "utf-8"));
|
|
42726
43062
|
parts.push(fileContent);
|
|
42727
43063
|
parts.push(Buffer.from(crlf, "utf-8"));
|
|
42728
|
-
parts.push(Buffer.from(
|
|
43064
|
+
parts.push(Buffer.from(delimiter4 + "--" + crlf, "utf-8"));
|
|
42729
43065
|
return Buffer.concat(parts);
|
|
42730
43066
|
}
|
|
42731
43067
|
async function* parseLineStream(chunks) {
|
|
@@ -43055,7 +43391,7 @@ var init_api = __esm({
|
|
|
43055
43391
|
return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
|
|
43056
43392
|
}
|
|
43057
43393
|
const partContentType = guessContentType(uploadName);
|
|
43058
|
-
const boundary = "----Tina4Boundary" +
|
|
43394
|
+
const boundary = "----Tina4Boundary" + randomBytes11(16).toString("hex");
|
|
43059
43395
|
const bodyBuffer = buildMultipartBody(boundary, fieldName, uploadName, content, partContentType, extraFields);
|
|
43060
43396
|
const contentType = `multipart/form-data; boundary=${boundary}`;
|
|
43061
43397
|
return this.execute("POST", this.buildUrl(path10), bodyBuffer, contentType, headers);
|
|
@@ -45203,7 +45539,7 @@ __export(ai_exports, {
|
|
|
45203
45539
|
});
|
|
45204
45540
|
import { existsSync as existsSync28, mkdirSync as mkdirSync22, writeFileSync as writeFileSync19, readFileSync as readFileSync26 } from "node:fs";
|
|
45205
45541
|
import { homedir } from "node:os";
|
|
45206
|
-
import { join as
|
|
45542
|
+
import { join as join34, resolve as resolve21, relative as relative11, dirname as dirname15 } from "node:path";
|
|
45207
45543
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
45208
45544
|
import { execSync as execSync2, execFileSync as execFileSync4 } from "node:child_process";
|
|
45209
45545
|
import { createInterface } from "node:readline";
|
|
@@ -45274,8 +45610,8 @@ function downloadSkillsSync(jobs) {
|
|
|
45274
45610
|
function installSkills(root = ".", targets) {
|
|
45275
45611
|
const ref = skillsRef();
|
|
45276
45612
|
const dests = targets ?? [
|
|
45277
|
-
|
|
45278
|
-
|
|
45613
|
+
join34(resolve21(root), ".claude", "skills"),
|
|
45614
|
+
join34(homedir(), ".claude", "skills")
|
|
45279
45615
|
];
|
|
45280
45616
|
const jobs = [];
|
|
45281
45617
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -45293,9 +45629,9 @@ function installSkills(root = ".", targets) {
|
|
|
45293
45629
|
const base = `https://raw.githubusercontent.com/tina4stack/${spec.repo}/${ref}/.claude/skills/${skill}`;
|
|
45294
45630
|
skillMdUrl[skill] = `${base}/SKILL.md`;
|
|
45295
45631
|
for (const dest of dests) {
|
|
45296
|
-
add(`${base}/SKILL.md`,
|
|
45632
|
+
add(`${base}/SKILL.md`, join34(dest, skill, "SKILL.md"));
|
|
45297
45633
|
for (const r of spec.references) {
|
|
45298
|
-
add(`${base}/references/${r}`,
|
|
45634
|
+
add(`${base}/references/${r}`, join34(dest, skill, "references", r));
|
|
45299
45635
|
}
|
|
45300
45636
|
}
|
|
45301
45637
|
}
|
|
@@ -45307,7 +45643,7 @@ function installSkills(root = ".", targets) {
|
|
|
45307
45643
|
return installed;
|
|
45308
45644
|
}
|
|
45309
45645
|
function isInstalled(root, tool) {
|
|
45310
|
-
return existsSync28(
|
|
45646
|
+
return existsSync28(join34(resolve21(root), tool.contextFile));
|
|
45311
45647
|
}
|
|
45312
45648
|
function showMenu(root = ".") {
|
|
45313
45649
|
const r = resolve21(root);
|
|
@@ -45451,9 +45787,9 @@ function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
|
45451
45787
|
}
|
|
45452
45788
|
function installForTool(root, tool, context) {
|
|
45453
45789
|
const created = [];
|
|
45454
|
-
const contextPath =
|
|
45790
|
+
const contextPath = join34(root, tool.contextFile);
|
|
45455
45791
|
if (tool.configDir) {
|
|
45456
|
-
mkdirSync22(
|
|
45792
|
+
mkdirSync22(join34(root, tool.configDir), { recursive: true });
|
|
45457
45793
|
}
|
|
45458
45794
|
const parentDir = dirname15(contextPath);
|
|
45459
45795
|
mkdirSync22(parentDir, { recursive: true });
|
|
@@ -45491,7 +45827,7 @@ function installTina4Ai() {
|
|
|
45491
45827
|
function installClaudeSkills(root) {
|
|
45492
45828
|
const created = [];
|
|
45493
45829
|
for (const skill of installSkills(root)) {
|
|
45494
|
-
created.push(
|
|
45830
|
+
created.push(join34(".claude", "skills", skill));
|
|
45495
45831
|
console.log(` ${GREEN2}\u2713${RESET2} Installed .claude/skills/${skill} (project + global)`);
|
|
45496
45832
|
}
|
|
45497
45833
|
return created;
|
|
@@ -45834,7 +46170,7 @@ function generateClaudeCodeContext() {
|
|
|
45834
46170
|
try {
|
|
45835
46171
|
const thisDir = dirname15(fileURLToPath8(import.meta.url));
|
|
45836
46172
|
const repoRoot = resolve21(thisDir, "..", "..", "..");
|
|
45837
|
-
const claudeMdPath =
|
|
46173
|
+
const claudeMdPath = join34(repoRoot, "CLAUDE.md");
|
|
45838
46174
|
if (existsSync28(claudeMdPath)) {
|
|
45839
46175
|
return readFileSync26(claudeMdPath, "utf-8");
|
|
45840
46176
|
}
|
|
@@ -46037,7 +46373,7 @@ import {
|
|
|
46037
46373
|
createHmac as createHmac4,
|
|
46038
46374
|
createPrivateKey,
|
|
46039
46375
|
createSign as createSign2,
|
|
46040
|
-
randomBytes as
|
|
46376
|
+
randomBytes as randomBytes12
|
|
46041
46377
|
} from "node:crypto";
|
|
46042
46378
|
function encodeBase64Url(value) {
|
|
46043
46379
|
return Buffer.from(value).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
@@ -46117,7 +46453,7 @@ function encryptPayload(payload, subscription) {
|
|
|
46117
46453
|
]);
|
|
46118
46454
|
const prkKey = hmac(authSecret, sharedSecret);
|
|
46119
46455
|
const ikm = hkdfExpand(prkKey, keyInfo, 32);
|
|
46120
|
-
const salt =
|
|
46456
|
+
const salt = randomBytes12(16);
|
|
46121
46457
|
const contentPrk = hmac(salt, ikm);
|
|
46122
46458
|
const cek = hkdfExpand(contentPrk, Buffer.from("Content-Encoding: aes128gcm\0", "ascii"), 16);
|
|
46123
46459
|
const nonce = hkdfExpand(contentPrk, Buffer.from("Content-Encoding: nonce\0", "ascii"), 12);
|
|
@@ -48874,6 +49210,7 @@ __export(src_exports3, {
|
|
|
48874
49210
|
cacheStats: () => cacheStats,
|
|
48875
49211
|
checkPassword: () => checkPassword,
|
|
48876
49212
|
clearCache: () => clearCache,
|
|
49213
|
+
clearCurrentNonce: () => clearCurrentNonce,
|
|
48877
49214
|
computeAcceptKey: () => computeAcceptKey,
|
|
48878
49215
|
container: () => container,
|
|
48879
49216
|
cors: () => cors,
|
|
@@ -48885,6 +49222,8 @@ __export(src_exports3, {
|
|
|
48885
49222
|
createMessenger: () => createMessenger,
|
|
48886
49223
|
createRequest: () => createRequest,
|
|
48887
49224
|
createResponse: () => createResponse,
|
|
49225
|
+
cspNonce: () => cspNonce,
|
|
49226
|
+
currentCspNonce: () => currentCspNonce,
|
|
48888
49227
|
decodeRequest: () => decodeRequest,
|
|
48889
49228
|
defaultContext: () => defaultContext,
|
|
48890
49229
|
defaultRouter: () => defaultRouter,
|
|
@@ -48910,6 +49249,7 @@ __export(src_exports3, {
|
|
|
48910
49249
|
feedbackWhitelist: () => feedbackWhitelist,
|
|
48911
49250
|
fts5Supported: () => fts5Supported,
|
|
48912
49251
|
generateContext: () => generateContext,
|
|
49252
|
+
generateNonce: () => generateNonce,
|
|
48913
49253
|
generateVapidKeys: () => generateVapidKeys,
|
|
48914
49254
|
get: () => get,
|
|
48915
49255
|
getDefaultDevServer: () => getDefaultDevServer,
|
|
@@ -48932,6 +49272,7 @@ __export(src_exports3, {
|
|
|
48932
49272
|
httpReason: () => httpReason,
|
|
48933
49273
|
inContainer: () => inContainer,
|
|
48934
49274
|
injectFeedbackWidget: () => injectFeedbackWidget,
|
|
49275
|
+
injectNonceIntoCsp: () => injectNonceIntoCsp,
|
|
48935
49276
|
installAll: () => installAll,
|
|
48936
49277
|
installSelected: () => installSelected,
|
|
48937
49278
|
isBannerSuppressed: () => isBannerSuppressed,
|
|
@@ -48986,6 +49327,7 @@ __export(src_exports3, {
|
|
|
48986
49327
|
resetTrustedProxyCache: () => resetTrustedProxyCache,
|
|
48987
49328
|
resolveAlgorithm: () => resolveAlgorithm,
|
|
48988
49329
|
resolveClientIp: () => resolveClientIp,
|
|
49330
|
+
resolveCspHeader: () => resolveCspHeader,
|
|
48989
49331
|
resolveFrameworkVersion: () => resolveFrameworkVersion,
|
|
48990
49332
|
resolvePortAndHost: () => resolvePortAndHost,
|
|
48991
49333
|
resolveStringMiddleware: () => resolveStringMiddleware,
|
|
@@ -48993,12 +49335,14 @@ __export(src_exports3, {
|
|
|
48993
49335
|
responseCache: () => responseCache,
|
|
48994
49336
|
runAll: () => runAll,
|
|
48995
49337
|
runRouteMiddlewares: () => runRouteMiddlewares,
|
|
49338
|
+
runWithCspNonce: () => runWithCspNonce,
|
|
48996
49339
|
runtimeDir: () => runtimeDir,
|
|
48997
49340
|
saveUpload: () => saveUpload,
|
|
48998
49341
|
schemaFromParams: () => schemaFromParams,
|
|
48999
49342
|
selectablePids: () => selectablePids,
|
|
49000
49343
|
serveWebSocketRoute: () => serveWebSocketRoute,
|
|
49001
49344
|
sessionCookieName: () => sessionCookieName,
|
|
49345
|
+
setCurrentNonce: () => setCurrentNonce,
|
|
49002
49346
|
setDefaultTemplatesDir: () => setDefaultTemplatesDir,
|
|
49003
49347
|
setFrond: () => setFrond,
|
|
49004
49348
|
showMenu: () => showMenu,
|
|
@@ -49065,6 +49409,7 @@ var init_src3 = __esm({
|
|
|
49065
49409
|
init_wsdl();
|
|
49066
49410
|
init_htmlElement();
|
|
49067
49411
|
init_errorOverlay();
|
|
49412
|
+
init_csp();
|
|
49068
49413
|
init_ai();
|
|
49069
49414
|
init_sso();
|
|
49070
49415
|
init_push();
|
|
@@ -49273,6 +49618,15 @@ export default async function (req: Tina4Request, res: Tina4Response): Promise<v
|
|
|
49273
49618
|
<p>The Intelligent Native Application 4ramework</p>
|
|
49274
49619
|
</body>
|
|
49275
49620
|
</html>
|
|
49621
|
+
`
|
|
49622
|
+
);
|
|
49623
|
+
writeFileSync(
|
|
49624
|
+
join(targetDir, "public/style.css"),
|
|
49625
|
+
`body { font-family: -apple-system, sans-serif; max-width: 600px; margin: 80px auto; padding: 0 20px; color: #333; }
|
|
49626
|
+
h1 { font-size: 2.5em; margin-bottom: 0.2em; }
|
|
49627
|
+
p { color: #666; font-size: 1.1em; }
|
|
49628
|
+
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 0.9em; }
|
|
49629
|
+
a { color: #2563eb; }
|
|
49276
49630
|
`
|
|
49277
49631
|
);
|
|
49278
49632
|
writeFileSync(
|
|
@@ -49281,13 +49635,7 @@ export default async function (req: Tina4Request, res: Tina4Response): Promise<v
|
|
|
49281
49635
|
<html>
|
|
49282
49636
|
<head>
|
|
49283
49637
|
<title>Tina4</title>
|
|
49284
|
-
<style>
|
|
49285
|
-
body { font-family: -apple-system, sans-serif; max-width: 600px; margin: 80px auto; padding: 0 20px; color: #333; }
|
|
49286
|
-
h1 { font-size: 2.5em; margin-bottom: 0.2em; }
|
|
49287
|
-
p { color: #666; font-size: 1.1em; }
|
|
49288
|
-
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 0.9em; }
|
|
49289
|
-
a { color: #2563eb; }
|
|
49290
|
-
</style>
|
|
49638
|
+
<link rel="stylesheet" href="/style.css">
|
|
49291
49639
|
</head>
|
|
49292
49640
|
<body>
|
|
49293
49641
|
<h1>tina4</h1>
|
|
@@ -49358,6 +49706,7 @@ async function serveProject(options) {
|
|
|
49358
49706
|
|
|
49359
49707
|
// src/commands/migrate.ts
|
|
49360
49708
|
init_dotenv();
|
|
49709
|
+
init_util();
|
|
49361
49710
|
import { existsSync as existsSync30 } from "node:fs";
|
|
49362
49711
|
import { resolve as resolve23 } from "node:path";
|
|
49363
49712
|
async function runMigrations(migrationDir) {
|
|
@@ -49377,12 +49726,7 @@ async function runMigrations(migrationDir) {
|
|
|
49377
49726
|
console.error(" Error: @tina4/orm is required to run migrations.");
|
|
49378
49727
|
process.exit(1);
|
|
49379
49728
|
}
|
|
49380
|
-
|
|
49381
|
-
await initDatabase2();
|
|
49382
|
-
} catch (err) {
|
|
49383
|
-
console.error(` Error initialising database: ${err instanceof Error ? err.message : String(err)}`);
|
|
49384
|
-
process.exit(1);
|
|
49385
|
-
}
|
|
49729
|
+
await initDatabaseOrExit(initDatabase2);
|
|
49386
49730
|
const result = await migrate2(void 0, { migrationsDir: dir });
|
|
49387
49731
|
if (result.applied.length === 0 && result.failed.length === 0) {
|
|
49388
49732
|
console.log(" Nothing to migrate \u2014 all migrations already applied.");
|
|
@@ -49421,6 +49765,7 @@ async function createMigration2(args = []) {
|
|
|
49421
49765
|
|
|
49422
49766
|
// src/commands/migrateStatus.ts
|
|
49423
49767
|
init_dotenv();
|
|
49768
|
+
init_util();
|
|
49424
49769
|
import { resolve as resolve24 } from "node:path";
|
|
49425
49770
|
async function migrateStatus(migrationDir) {
|
|
49426
49771
|
loadEnv();
|
|
@@ -49437,12 +49782,7 @@ async function migrateStatus(migrationDir) {
|
|
|
49437
49782
|
console.error(" Error: @tina4/orm is required to check migration status.");
|
|
49438
49783
|
process.exit(1);
|
|
49439
49784
|
}
|
|
49440
|
-
|
|
49441
|
-
await initDatabase2();
|
|
49442
|
-
} catch (err) {
|
|
49443
|
-
console.error(` Error initialising database: ${err instanceof Error ? err.message : String(err)}`);
|
|
49444
|
-
process.exit(1);
|
|
49445
|
-
}
|
|
49785
|
+
await initDatabaseOrExit(initDatabase2);
|
|
49446
49786
|
ensureMigrationTable2();
|
|
49447
49787
|
const result = await statusFn(void 0, { migrationsDir: dir });
|
|
49448
49788
|
console.log("");
|
|
@@ -49469,6 +49809,7 @@ async function migrateStatus(migrationDir) {
|
|
|
49469
49809
|
|
|
49470
49810
|
// src/commands/migrateRollback.ts
|
|
49471
49811
|
init_dotenv();
|
|
49812
|
+
init_util();
|
|
49472
49813
|
import { resolve as resolve25 } from "node:path";
|
|
49473
49814
|
async function migrateRollback(migrationDir) {
|
|
49474
49815
|
loadEnv();
|
|
@@ -49487,12 +49828,7 @@ async function migrateRollback(migrationDir) {
|
|
|
49487
49828
|
console.error(" Error: @tina4/orm is required to rollback migrations.");
|
|
49488
49829
|
process.exit(1);
|
|
49489
49830
|
}
|
|
49490
|
-
|
|
49491
|
-
await initDatabase2();
|
|
49492
|
-
} catch (err) {
|
|
49493
|
-
console.error(` Error initialising database: ${err instanceof Error ? err.message : String(err)}`);
|
|
49494
|
-
process.exit(1);
|
|
49495
|
-
}
|
|
49831
|
+
await initDatabaseOrExit(initDatabase2);
|
|
49496
49832
|
await ensureMigrationTable2();
|
|
49497
49833
|
const lastBatch = await getLastBatchMigrations2();
|
|
49498
49834
|
if (lastBatch.length === 0) {
|
|
@@ -49562,8 +49898,8 @@ async function listRoutes() {
|
|
|
49562
49898
|
}
|
|
49563
49899
|
|
|
49564
49900
|
// src/commands/test.ts
|
|
49565
|
-
import { existsSync as existsSync32, readdirSync as readdirSync19, readFileSync as readFileSync27, statSync as
|
|
49566
|
-
import { resolve as resolve27, join as
|
|
49901
|
+
import { existsSync as existsSync32, readdirSync as readdirSync19, readFileSync as readFileSync27, statSync as statSync21 } from "node:fs";
|
|
49902
|
+
import { resolve as resolve27, join as join35 } from "node:path";
|
|
49567
49903
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
49568
49904
|
import { execSync as execSync3 } from "node:child_process";
|
|
49569
49905
|
function walkSource(dir) {
|
|
@@ -49575,10 +49911,10 @@ function walkSource(dir) {
|
|
|
49575
49911
|
return out;
|
|
49576
49912
|
}
|
|
49577
49913
|
for (const name of entries) {
|
|
49578
|
-
const full =
|
|
49914
|
+
const full = join35(dir, name);
|
|
49579
49915
|
let st;
|
|
49580
49916
|
try {
|
|
49581
|
-
st =
|
|
49917
|
+
st = statSync21(full);
|
|
49582
49918
|
} catch {
|
|
49583
49919
|
continue;
|
|
49584
49920
|
}
|
|
@@ -49646,7 +49982,7 @@ async function runTests(testPath) {
|
|
|
49646
49982
|
break;
|
|
49647
49983
|
}
|
|
49648
49984
|
try {
|
|
49649
|
-
const files = readdirSync19(fullPath).filter((f) => f.endsWith(".ts")).map((f) =>
|
|
49985
|
+
const files = readdirSync19(fullPath).filter((f) => f.endsWith(".ts")).map((f) => join35(fullPath, f));
|
|
49650
49986
|
testFiles.push(...files);
|
|
49651
49987
|
} catch {
|
|
49652
49988
|
}
|
|
@@ -49675,9 +50011,10 @@ async function runTests(testPath) {
|
|
|
49675
50011
|
}
|
|
49676
50012
|
|
|
49677
50013
|
// src/commands/lint.ts
|
|
49678
|
-
|
|
50014
|
+
init_util();
|
|
50015
|
+
import { existsSync as existsSync33, readdirSync as readdirSync20, readFileSync as readFileSync28, statSync as statSync22, writeFileSync as writeFileSync20 } from "node:fs";
|
|
49679
50016
|
import { createRequire as createRequire9 } from "node:module";
|
|
49680
|
-
import {
|
|
50017
|
+
import { dirname as dirname16, join as join36, relative as relative12 } from "node:path";
|
|
49681
50018
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
49682
50019
|
var LINT_EXTENSIONS = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
|
|
49683
50020
|
var JS_EXTENSIONS = [".js", ".mjs", ".cjs"];
|
|
@@ -49695,10 +50032,10 @@ function walkSource2(dir, out) {
|
|
|
49695
50032
|
}
|
|
49696
50033
|
for (const name of entries) {
|
|
49697
50034
|
if (SKIP_DIRS3.has(name)) continue;
|
|
49698
|
-
const full =
|
|
50035
|
+
const full = join36(dir, name);
|
|
49699
50036
|
let st;
|
|
49700
50037
|
try {
|
|
49701
|
-
st =
|
|
50038
|
+
st = statSync22(full);
|
|
49702
50039
|
} catch {
|
|
49703
50040
|
continue;
|
|
49704
50041
|
}
|
|
@@ -49711,15 +50048,15 @@ function walkSource2(dir, out) {
|
|
|
49711
50048
|
}
|
|
49712
50049
|
function collectAppFiles(cwd) {
|
|
49713
50050
|
const files = [];
|
|
49714
|
-
const srcDir =
|
|
50051
|
+
const srcDir = join36(cwd, "src");
|
|
49715
50052
|
try {
|
|
49716
|
-
if (
|
|
50053
|
+
if (statSync22(srcDir).isDirectory()) walkSource2(srcDir, files);
|
|
49717
50054
|
} catch {
|
|
49718
50055
|
}
|
|
49719
50056
|
for (const entry of ["app.ts", "app.mts", "app.js", "app.mjs"]) {
|
|
49720
|
-
const p =
|
|
50057
|
+
const p = join36(cwd, entry);
|
|
49721
50058
|
try {
|
|
49722
|
-
if (
|
|
50059
|
+
if (statSync22(p).isFile()) files.push(p);
|
|
49723
50060
|
} catch {
|
|
49724
50061
|
}
|
|
49725
50062
|
}
|
|
@@ -49740,15 +50077,15 @@ export default [js.configs.recommended, ...tseslint.configs.recommended];
|
|
|
49740
50077
|
`;
|
|
49741
50078
|
function findEslintConfig(cwd) {
|
|
49742
50079
|
for (const name of ESLINT_FLAT_CONFIGS) {
|
|
49743
|
-
const p =
|
|
50080
|
+
const p = join36(cwd, name);
|
|
49744
50081
|
if (existsSync33(p)) return p;
|
|
49745
50082
|
}
|
|
49746
50083
|
return null;
|
|
49747
50084
|
}
|
|
49748
50085
|
function resolvePackageBin(cwd, pkg, binRelative) {
|
|
49749
|
-
const direct =
|
|
50086
|
+
const direct = join36(cwd, "node_modules", pkg, binRelative);
|
|
49750
50087
|
if (existsSync33(direct)) return direct;
|
|
49751
|
-
const require3 = createRequire9(
|
|
50088
|
+
const require3 = createRequire9(join36(cwd, "package.json"));
|
|
49752
50089
|
let entry;
|
|
49753
50090
|
try {
|
|
49754
50091
|
entry = require3.resolve(pkg);
|
|
@@ -49757,11 +50094,11 @@ function resolvePackageBin(cwd, pkg, binRelative) {
|
|
|
49757
50094
|
}
|
|
49758
50095
|
let dir = dirname16(entry);
|
|
49759
50096
|
for (let i = 0; i < 8; i++) {
|
|
49760
|
-
const pkgJson =
|
|
50097
|
+
const pkgJson = join36(dir, "package.json");
|
|
49761
50098
|
if (existsSync33(pkgJson)) {
|
|
49762
50099
|
try {
|
|
49763
50100
|
if (JSON.parse(readFileSync28(pkgJson, "utf-8")).name === pkg) {
|
|
49764
|
-
const bin =
|
|
50101
|
+
const bin = join36(dir, binRelative);
|
|
49765
50102
|
return existsSync33(bin) ? bin : null;
|
|
49766
50103
|
}
|
|
49767
50104
|
} catch {
|
|
@@ -49777,22 +50114,12 @@ function resolveEslintBin(cwd) {
|
|
|
49777
50114
|
return resolvePackageBin(cwd, "eslint", "bin/eslint.js");
|
|
49778
50115
|
}
|
|
49779
50116
|
function hasPackage(cwd, pkg) {
|
|
49780
|
-
return existsSync33(
|
|
50117
|
+
return existsSync33(join36(cwd, "node_modules", ...pkg.split("/"), "package.json"));
|
|
49781
50118
|
}
|
|
49782
50119
|
function resolveNpm() {
|
|
49783
50120
|
const windows = process.platform === "win32";
|
|
49784
50121
|
const names = windows ? ["npm.cmd", "npm.exe", "npm"] : ["npm"];
|
|
49785
|
-
|
|
49786
|
-
if (!dir) continue;
|
|
49787
|
-
for (const name of names) {
|
|
49788
|
-
const candidate = join35(dir, name);
|
|
49789
|
-
try {
|
|
49790
|
-
if (statSync21(candidate).isFile()) return candidate;
|
|
49791
|
-
} catch {
|
|
49792
|
-
}
|
|
49793
|
-
}
|
|
49794
|
-
}
|
|
49795
|
-
return null;
|
|
50122
|
+
return findOnPath(names);
|
|
49796
50123
|
}
|
|
49797
50124
|
function bootstrapEslint(cwd, noInstall) {
|
|
49798
50125
|
let bin = resolveEslintBin(cwd);
|
|
@@ -49810,7 +50137,7 @@ function bootstrapEslint(cwd, noInstall) {
|
|
|
49810
50137
|
else console.log(" \xB7 could not install eslint \u2014 using the zero-dependency baseline.");
|
|
49811
50138
|
}
|
|
49812
50139
|
if (bin && !config && hasPackage(cwd, "@eslint/js") && hasPackage(cwd, "typescript-eslint")) {
|
|
49813
|
-
const scaffold =
|
|
50140
|
+
const scaffold = join36(cwd, ESLINT_SCAFFOLD_FILE);
|
|
49814
50141
|
try {
|
|
49815
50142
|
writeFileSync20(scaffold, ESLINT_SCAFFOLD, "utf-8");
|
|
49816
50143
|
config = scaffold;
|
|
@@ -49879,7 +50206,7 @@ function runLint(args) {
|
|
|
49879
50206
|
if (fix) {
|
|
49880
50207
|
console.log(" \xB7 --fix needs eslint \u2014 the baseline check has no autofix.");
|
|
49881
50208
|
}
|
|
49882
|
-
if (existsSync33(
|
|
50209
|
+
if (existsSync33(join36(cwd, "tsconfig.json")) && runTypescript(cwd, files)) return;
|
|
49883
50210
|
runJavaScript(cwd, files);
|
|
49884
50211
|
}
|
|
49885
50212
|
|
|
@@ -49888,7 +50215,7 @@ init_generate();
|
|
|
49888
50215
|
|
|
49889
50216
|
// src/commands/seed.ts
|
|
49890
50217
|
import { existsSync as existsSync34, readdirSync as readdirSync21 } from "node:fs";
|
|
49891
|
-
import { resolve as resolve28, join as
|
|
50218
|
+
import { resolve as resolve28, join as join37 } from "node:path";
|
|
49892
50219
|
import { execSync as execSync4 } from "node:child_process";
|
|
49893
50220
|
async function runSeeds(seedPath) {
|
|
49894
50221
|
const cwd = process.cwd();
|
|
@@ -49915,7 +50242,7 @@ async function runSeeds(seedPath) {
|
|
|
49915
50242
|
}
|
|
49916
50243
|
let seedFiles;
|
|
49917
50244
|
try {
|
|
49918
|
-
seedFiles = readdirSync21(seedDir).filter((f) => f.endsWith(".ts")).sort().map((f) =>
|
|
50245
|
+
seedFiles = readdirSync21(seedDir).filter((f) => f.endsWith(".ts")).sort().map((f) => join37(seedDir, f));
|
|
49919
50246
|
} catch {
|
|
49920
50247
|
console.log(" Could not read src/seeds/ directory.");
|
|
49921
50248
|
return;
|
|
@@ -49946,39 +50273,16 @@ async function runSeeds(seedPath) {
|
|
|
49946
50273
|
// src/commands/queue.ts
|
|
49947
50274
|
init_dotenv();
|
|
49948
50275
|
init_queue();
|
|
49949
|
-
|
|
49950
|
-
import {
|
|
50276
|
+
init_util();
|
|
50277
|
+
import { readdirSync as readdirSync22, statSync as statSync23 } from "node:fs";
|
|
50278
|
+
import { extname as extname9, join as join38 } from "node:path";
|
|
49951
50279
|
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
49952
50280
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["once", "json"]);
|
|
49953
|
-
|
|
49954
|
-
const flags = {};
|
|
49955
|
-
const positional = [];
|
|
49956
|
-
let i = 0;
|
|
49957
|
-
while (i < args.length) {
|
|
49958
|
-
const arg = args[i];
|
|
49959
|
-
if (arg.startsWith("--")) {
|
|
49960
|
-
const key = arg.slice(2);
|
|
49961
|
-
if (BOOLEAN_FLAGS.has(key)) {
|
|
49962
|
-
flags[key] = true;
|
|
49963
|
-
i += 1;
|
|
49964
|
-
} else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
49965
|
-
flags[key] = args[i + 1];
|
|
49966
|
-
i += 2;
|
|
49967
|
-
} else {
|
|
49968
|
-
flags[key] = true;
|
|
49969
|
-
i += 1;
|
|
49970
|
-
}
|
|
49971
|
-
} else {
|
|
49972
|
-
positional.push(arg);
|
|
49973
|
-
i += 1;
|
|
49974
|
-
}
|
|
49975
|
-
}
|
|
49976
|
-
return { flags, positional };
|
|
49977
|
-
}
|
|
50281
|
+
var parseFlags2 = (args) => parseFlags(args, BOOLEAN_FLAGS);
|
|
49978
50282
|
async function resolveQueueHandler(servicesDir, topic) {
|
|
49979
50283
|
let entries;
|
|
49980
50284
|
try {
|
|
49981
|
-
if (!
|
|
50285
|
+
if (!statSync23(servicesDir).isDirectory()) return null;
|
|
49982
50286
|
entries = readdirSync22(servicesDir);
|
|
49983
50287
|
} catch {
|
|
49984
50288
|
return null;
|
|
@@ -49987,9 +50291,9 @@ async function resolveQueueHandler(servicesDir, topic) {
|
|
|
49987
50291
|
if (entry.startsWith("_")) continue;
|
|
49988
50292
|
const ext = extname9(entry);
|
|
49989
50293
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
49990
|
-
const fullPath =
|
|
50294
|
+
const fullPath = join38(servicesDir, entry);
|
|
49991
50295
|
try {
|
|
49992
|
-
if (!
|
|
50296
|
+
if (!statSync23(fullPath).isFile()) continue;
|
|
49993
50297
|
const mod = await import(pathToFileURL3(fullPath).href);
|
|
49994
50298
|
const config = mod.default ?? mod;
|
|
49995
50299
|
if (config && typeof config === "object" && config.topic === topic && typeof config.handle === "function") {
|
|
@@ -50006,7 +50310,7 @@ function firstJob(yielded) {
|
|
|
50006
50310
|
}
|
|
50007
50311
|
async function queueWork(args) {
|
|
50008
50312
|
loadEnv();
|
|
50009
|
-
const { flags, positional } =
|
|
50313
|
+
const { flags, positional } = parseFlags2(args);
|
|
50010
50314
|
const topic = positional[0] ?? "default";
|
|
50011
50315
|
const once2 = Boolean(flags.once);
|
|
50012
50316
|
let pollSeconds;
|
|
@@ -50058,7 +50362,7 @@ async function queueWork(args) {
|
|
|
50058
50362
|
}
|
|
50059
50363
|
async function queueStats(args) {
|
|
50060
50364
|
loadEnv();
|
|
50061
|
-
const { flags, positional } =
|
|
50365
|
+
const { flags, positional } = parseFlags2(args);
|
|
50062
50366
|
const topic = positional[0] ?? "default";
|
|
50063
50367
|
const queue = new Queue({ topic });
|
|
50064
50368
|
const stats = {
|
|
@@ -50089,7 +50393,7 @@ async function queueStats(args) {
|
|
|
50089
50393
|
}
|
|
50090
50394
|
async function queueRetry(args) {
|
|
50091
50395
|
loadEnv();
|
|
50092
|
-
const { positional } =
|
|
50396
|
+
const { positional } = parseFlags2(args);
|
|
50093
50397
|
const topic = positional[0] ?? "default";
|
|
50094
50398
|
const queue = new Queue({ topic });
|
|
50095
50399
|
const dead = queue.deadLetters(0);
|
|
@@ -50105,7 +50409,7 @@ async function queueRetry(args) {
|
|
|
50105
50409
|
}
|
|
50106
50410
|
async function queueClear(args) {
|
|
50107
50411
|
loadEnv();
|
|
50108
|
-
const { positional } =
|
|
50412
|
+
const { positional } = parseFlags2(args);
|
|
50109
50413
|
const status2 = positional[0] ?? "completed";
|
|
50110
50414
|
const topic = positional[1] ?? "default";
|
|
50111
50415
|
const queue = new Queue({ topic });
|
|
@@ -50139,28 +50443,12 @@ async function queueCommand(args = []) {
|
|
|
50139
50443
|
}
|
|
50140
50444
|
|
|
50141
50445
|
// src/commands/build.ts
|
|
50142
|
-
|
|
50143
|
-
import {
|
|
50446
|
+
init_util();
|
|
50447
|
+
import { accessSync as accessSync2, constants as constants4, existsSync as existsSync35, statSync as statSync24 } from "node:fs";
|
|
50448
|
+
import { basename as basename9, delimiter as delimiter3, join as join39 } from "node:path";
|
|
50144
50449
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
50145
|
-
function
|
|
50146
|
-
|
|
50147
|
-
let i = 0;
|
|
50148
|
-
while (i < args.length) {
|
|
50149
|
-
const arg = args[i];
|
|
50150
|
-
if (arg.startsWith("--")) {
|
|
50151
|
-
const key = arg.slice(2);
|
|
50152
|
-
if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
50153
|
-
flags[key] = args[i + 1];
|
|
50154
|
-
i += 2;
|
|
50155
|
-
} else {
|
|
50156
|
-
flags[key] = true;
|
|
50157
|
-
i += 1;
|
|
50158
|
-
}
|
|
50159
|
-
} else {
|
|
50160
|
-
i += 1;
|
|
50161
|
-
}
|
|
50162
|
-
}
|
|
50163
|
-
return flags;
|
|
50450
|
+
function parseFlags3(args) {
|
|
50451
|
+
return parseFlags(args).flags;
|
|
50164
50452
|
}
|
|
50165
50453
|
function whichDocker() {
|
|
50166
50454
|
const pathValue = process.env.PATH || process.env.Path || "";
|
|
@@ -50169,10 +50457,10 @@ function whichDocker() {
|
|
|
50169
50457
|
for (const dir of pathValue.split(delimiter3)) {
|
|
50170
50458
|
if (!dir) continue;
|
|
50171
50459
|
for (const ext of exts) {
|
|
50172
|
-
const candidate =
|
|
50460
|
+
const candidate = join39(dir, `docker${ext}`);
|
|
50173
50461
|
try {
|
|
50174
50462
|
accessSync2(candidate, constants4.X_OK);
|
|
50175
|
-
if (
|
|
50463
|
+
if (statSync24(candidate).isFile()) return candidate;
|
|
50176
50464
|
} catch {
|
|
50177
50465
|
}
|
|
50178
50466
|
}
|
|
@@ -50180,14 +50468,14 @@ function whichDocker() {
|
|
|
50180
50468
|
return null;
|
|
50181
50469
|
}
|
|
50182
50470
|
function buildImage(args) {
|
|
50183
|
-
const flags =
|
|
50471
|
+
const flags = parseFlags3(args);
|
|
50184
50472
|
let tag = typeof flags.tag === "string" ? flags.tag : "";
|
|
50185
50473
|
if (!tag) {
|
|
50186
50474
|
const dirName = basename9(process.cwd()).toLowerCase();
|
|
50187
50475
|
tag = `${dirName || "tina4app"}:latest`;
|
|
50188
50476
|
}
|
|
50189
50477
|
const dockerfile = typeof flags.file === "string" && flags.file ? flags.file : "Dockerfile";
|
|
50190
|
-
if (!existsSync35(dockerfile) || !
|
|
50478
|
+
if (!existsSync35(dockerfile) || !statSync24(dockerfile).isFile()) {
|
|
50191
50479
|
console.log(` \u2717 No ${dockerfile} found.`);
|
|
50192
50480
|
console.log(" A Tina4 app deploys as a container. Scaffold a Dockerfile first:");
|
|
50193
50481
|
console.log(" tina4 deploy docker (or: tina4nodejs init)");
|
|
@@ -50214,14 +50502,15 @@ function buildImage(args) {
|
|
|
50214
50502
|
}
|
|
50215
50503
|
|
|
50216
50504
|
// src/bin.ts
|
|
50505
|
+
init_util();
|
|
50217
50506
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
50218
|
-
import { existsSync as existsSync36, readFileSync as readFileSync29
|
|
50219
|
-
import {
|
|
50507
|
+
import { existsSync as existsSync36, readFileSync as readFileSync29 } from "node:fs";
|
|
50508
|
+
import { dirname as dirname17, join as join40 } from "node:path";
|
|
50220
50509
|
import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
50221
50510
|
function readCliVersion() {
|
|
50222
50511
|
let dir = dirname17(fileURLToPath9(import.meta.url));
|
|
50223
50512
|
for (let i = 0; i < 6; i++) {
|
|
50224
|
-
const pkgPath =
|
|
50513
|
+
const pkgPath = join40(dir, "package.json");
|
|
50225
50514
|
if (existsSync36(pkgPath)) {
|
|
50226
50515
|
try {
|
|
50227
50516
|
const pkg = JSON.parse(readFileSync29(pkgPath, "utf-8"));
|
|
@@ -50526,17 +50815,7 @@ var CLIENT_INSTALL_HINT = " Install it: curl -fsSL https://tina4.com/install.s
|
|
|
50526
50815
|
function findClient() {
|
|
50527
50816
|
const windows = process.platform === "win32";
|
|
50528
50817
|
const names = windows ? [`${CLIENT_BINARY}.exe`, `${CLIENT_BINARY}.cmd`, `${CLIENT_BINARY}.bat`] : [CLIENT_BINARY];
|
|
50529
|
-
|
|
50530
|
-
if (!dir) continue;
|
|
50531
|
-
for (const name of names) {
|
|
50532
|
-
const candidate = join39(dir, name);
|
|
50533
|
-
try {
|
|
50534
|
-
if (statSync24(candidate).isFile()) return candidate;
|
|
50535
|
-
} catch {
|
|
50536
|
-
}
|
|
50537
|
-
}
|
|
50538
|
-
}
|
|
50539
|
-
return null;
|
|
50818
|
+
return findOnPath(names);
|
|
50540
50819
|
}
|
|
50541
50820
|
function delegateToClient(command, args) {
|
|
50542
50821
|
if (process.env[DELEGATION_GUARD_ENV] === command) {
|