@malloy-publisher/server 0.0.218 → 0.0.220
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/dist/app/api-doc.yaml +262 -25
- package/dist/app/assets/{EnvironmentPage-DxOiCxcd.js → EnvironmentPage-BqLiaatL.js} +1 -1
- package/dist/app/assets/{HomePage-i8qAtD6x.js → HomePage-G_xpt9XE.js} +1 -1
- package/dist/app/assets/LightMode-s1PDRIsF.js +1 -0
- package/dist/app/assets/MainPage-C6TfpC92.js +2 -0
- package/dist/app/assets/{MaterializationsPage-6OEVM543.js → MaterializationsPage-BMO1afhm.js} +1 -1
- package/dist/app/assets/ModelPage-zHOJwMnU.js +1 -0
- package/dist/app/assets/{PackagePage-BVmoVsPQ.js → PackagePage-GCaWmELQ.js} +1 -1
- package/dist/app/assets/{RouteError-DFX521_t.js → RouteError-CIC588k4.js} +1 -1
- package/dist/app/assets/ThemeEditorPage-BdbIfIGO.js +1 -0
- package/dist/app/assets/{WorkbookPage-CzucDJ-T.js → WorkbookPage-C8ucK_H8.js} +1 -1
- package/dist/app/assets/{core-B95VQkAV.es-Cbn-yKG-.js → core-DFvqRVqM.es-NtfPVC7h.js} +1 -1
- package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
- package/dist/app/assets/index-8E2uLeV9.js +2541 -0
- package/dist/app/assets/index-B3NcDPbp.js +18 -0
- package/dist/app/assets/index-BxDMCn3s.js +527 -0
- package/dist/app/assets/index-aYNf0kTZ.js +1 -0
- package/dist/app/assets/index-aYtt-ovi.js +1761 -0
- package/dist/app/assets/index-w_0OQJgZ.js +23 -0
- package/dist/app/index.html +1 -1
- package/dist/runtime/publisher.js +6 -1
- package/dist/server.mjs +637 -263
- package/package.json +12 -12
- package/src/config.theme.spec.ts +178 -0
- package/src/config.ts +179 -0
- package/src/controller/materialization.controller.spec.ts +4 -4
- package/src/controller/materialization.controller.ts +2 -2
- package/src/controller/theme.controller.ts +83 -0
- package/src/dto/connection.dto.ts +6 -0
- package/src/materialization_metrics.ts +1 -1
- package/src/runtime/publisher.js +6 -1
- package/src/server.ts +58 -1
- package/src/service/build_plan.spec.ts +17 -17
- package/src/service/build_plan.ts +25 -10
- package/src/service/connection.spec.ts +70 -0
- package/src/service/connection.ts +102 -11
- package/src/service/connection_config.spec.ts +138 -0
- package/src/service/connection_config.ts +81 -2
- package/src/service/connection_fingerprint.spec.ts +102 -0
- package/src/service/manifest_loader.spec.ts +5 -5
- package/src/service/manifest_loader.ts +4 -4
- package/src/service/materialization_service.spec.ts +47 -37
- package/src/service/materialization_service.ts +59 -50
- package/src/service/materialization_test_fixtures.ts +5 -5
- package/src/service/model.ts +1 -1
- package/src/service/package.ts +4 -4
- package/src/service/proxy.spec.ts +82 -35
- package/src/service/proxy.ts +55 -40
- package/src/service/theme_store.ts +199 -0
- package/src/storage/DatabaseInterface.ts +1 -1
- package/src/storage/StorageManager.ts +17 -0
- package/src/storage/duckdb/schema.ts +27 -6
- package/src/theme_key_parity.spec.ts +57 -0
- package/tests/integration/materialization/manifest_binding.integration.spec.ts +1 -1
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +5 -5
- package/tests/unit/controller/theme.controller.spec.ts +141 -0
- package/tests/unit/service/theme_store.spec.ts +139 -0
- package/dist/app/assets/MainPage-B58d5pmX.js +0 -2
- package/dist/app/assets/ModelPage-8d5l7YkU.js +0 -1
- package/dist/app/assets/index--xJ1pzL-.js +0 -455
- package/dist/app/assets/index-Bxkza-hz.js +0 -40
- package/dist/app/assets/index-DxRKma36.js +0 -1760
- package/dist/app/assets/index.umd-o-yUIEtS.js +0 -2467
package/dist/server.mjs
CHANGED
|
@@ -161487,11 +161487,11 @@ var require_agent = __commonJS((exports, module) => {
|
|
|
161487
161487
|
};
|
|
161488
161488
|
})();
|
|
161489
161489
|
var WINDOWS_PIPE_REGEX = /^[/\\][/\\]\.[/\\]pipe[/\\].+/;
|
|
161490
|
-
function createAgent(
|
|
161491
|
-
if (process.platform === "win32" && !WINDOWS_PIPE_REGEX.test(
|
|
161492
|
-
return
|
|
161490
|
+
function createAgent(path3) {
|
|
161491
|
+
if (process.platform === "win32" && !WINDOWS_PIPE_REGEX.test(path3)) {
|
|
161492
|
+
return path3 === "pageant" ? new PageantAgent : new CygwinAgent(path3);
|
|
161493
161493
|
}
|
|
161494
|
-
return new OpenSSHAgent(
|
|
161494
|
+
return new OpenSSHAgent(path3);
|
|
161495
161495
|
}
|
|
161496
161496
|
var AgentProtocol = (() => {
|
|
161497
161497
|
const SSH_AGENTC_REQUEST_IDENTITIES = 11;
|
|
@@ -166093,17 +166093,17 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166093
166093
|
if (!this.server)
|
|
166094
166094
|
sendOrBuffer(this, CLIENT_VERSION_BUFFER);
|
|
166095
166095
|
}
|
|
166096
|
-
createReadStream(
|
|
166096
|
+
createReadStream(path3, options) {
|
|
166097
166097
|
if (this.server)
|
|
166098
166098
|
throw new Error("Client-only method called in server mode");
|
|
166099
|
-
return new ReadStream(this,
|
|
166099
|
+
return new ReadStream(this, path3, options);
|
|
166100
166100
|
}
|
|
166101
|
-
createWriteStream(
|
|
166101
|
+
createWriteStream(path3, options) {
|
|
166102
166102
|
if (this.server)
|
|
166103
166103
|
throw new Error("Client-only method called in server mode");
|
|
166104
|
-
return new WriteStream(this,
|
|
166104
|
+
return new WriteStream(this, path3, options);
|
|
166105
166105
|
}
|
|
166106
|
-
open(
|
|
166106
|
+
open(path3, flags_, attrs, cb) {
|
|
166107
166107
|
if (this.server)
|
|
166108
166108
|
throw new Error("Client-only method called in server mode");
|
|
166109
166109
|
if (typeof attrs === "function") {
|
|
@@ -166122,7 +166122,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166122
166122
|
attrsFlags = attrs.flags;
|
|
166123
166123
|
attrsLen = attrs.nb;
|
|
166124
166124
|
}
|
|
166125
|
-
const pathLen = Buffer.byteLength(
|
|
166125
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166126
166126
|
let p = 9;
|
|
166127
166127
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + 4 + attrsLen);
|
|
166128
166128
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166130,7 +166130,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166130
166130
|
const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
|
|
166131
166131
|
writeUInt32BE(buf, reqid, 5);
|
|
166132
166132
|
writeUInt32BE(buf, pathLen, p);
|
|
166133
|
-
buf.utf8Write(
|
|
166133
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166134
166134
|
writeUInt32BE(buf, flags, p += pathLen);
|
|
166135
166135
|
writeUInt32BE(buf, attrsFlags, p += 4);
|
|
166136
166136
|
if (attrsLen) {
|
|
@@ -166250,7 +166250,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166250
166250
|
throw new Error("Client-only method called in server mode");
|
|
166251
166251
|
fastXfer(fs2, this, localPath, remotePath, opts, cb);
|
|
166252
166252
|
}
|
|
166253
|
-
readFile(
|
|
166253
|
+
readFile(path3, options, callback_) {
|
|
166254
166254
|
if (this.server)
|
|
166255
166255
|
throw new Error("Client-only method called in server mode");
|
|
166256
166256
|
let callback;
|
|
@@ -166323,13 +166323,13 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166323
166323
|
return callback && callback(er, buffer);
|
|
166324
166324
|
});
|
|
166325
166325
|
};
|
|
166326
|
-
this.open(
|
|
166326
|
+
this.open(path3, flag, 438, (er, handle_) => {
|
|
166327
166327
|
if (er)
|
|
166328
166328
|
return callback && callback(er);
|
|
166329
166329
|
handle = handle_;
|
|
166330
166330
|
const tryStat = (er2, st) => {
|
|
166331
166331
|
if (er2) {
|
|
166332
|
-
this.stat(
|
|
166332
|
+
this.stat(path3, (er_, st_) => {
|
|
166333
166333
|
if (er_) {
|
|
166334
166334
|
return this.close(handle, () => {
|
|
166335
166335
|
callback && callback(er2);
|
|
@@ -166350,7 +166350,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166350
166350
|
this.fstat(handle, tryStat);
|
|
166351
166351
|
});
|
|
166352
166352
|
}
|
|
166353
|
-
writeFile(
|
|
166353
|
+
writeFile(path3, data, options, callback_) {
|
|
166354
166354
|
if (this.server)
|
|
166355
166355
|
throw new Error("Client-only method called in server mode");
|
|
166356
166356
|
let callback;
|
|
@@ -166369,7 +166369,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166369
166369
|
if (options.encoding && !Buffer.isEncoding(options.encoding))
|
|
166370
166370
|
throw new Error(`Unknown encoding: ${options.encoding}`);
|
|
166371
166371
|
const flag = options.flag || "w";
|
|
166372
|
-
this.open(
|
|
166372
|
+
this.open(path3, flag, options.mode, (openErr, handle) => {
|
|
166373
166373
|
if (openErr) {
|
|
166374
166374
|
callback && callback(openErr);
|
|
166375
166375
|
} else {
|
|
@@ -166378,7 +166378,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166378
166378
|
if (position === null) {
|
|
166379
166379
|
const tryStat = (er, st) => {
|
|
166380
166380
|
if (er) {
|
|
166381
|
-
this.stat(
|
|
166381
|
+
this.stat(path3, (er_, st_) => {
|
|
166382
166382
|
if (er_) {
|
|
166383
166383
|
return this.close(handle, () => {
|
|
166384
166384
|
callback && callback(er);
|
|
@@ -166397,7 +166397,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166397
166397
|
}
|
|
166398
166398
|
});
|
|
166399
166399
|
}
|
|
166400
|
-
appendFile(
|
|
166400
|
+
appendFile(path3, data, options, callback_) {
|
|
166401
166401
|
if (this.server)
|
|
166402
166402
|
throw new Error("Client-only method called in server mode");
|
|
166403
166403
|
let callback;
|
|
@@ -166415,12 +166415,12 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166415
166415
|
throw new TypeError("Bad arguments");
|
|
166416
166416
|
if (!options.flag)
|
|
166417
166417
|
options = Object.assign({ flag: "a" }, options);
|
|
166418
|
-
this.writeFile(
|
|
166418
|
+
this.writeFile(path3, data, options, callback);
|
|
166419
166419
|
}
|
|
166420
|
-
exists(
|
|
166420
|
+
exists(path3, cb) {
|
|
166421
166421
|
if (this.server)
|
|
166422
166422
|
throw new Error("Client-only method called in server mode");
|
|
166423
|
-
this.stat(
|
|
166423
|
+
this.stat(path3, (err) => {
|
|
166424
166424
|
cb && cb(err ? false : true);
|
|
166425
166425
|
});
|
|
166426
166426
|
}
|
|
@@ -166459,7 +166459,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166459
166459
|
const isBuffered = sendOrBuffer(this, buf);
|
|
166460
166460
|
this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} RENAME`);
|
|
166461
166461
|
}
|
|
166462
|
-
mkdir(
|
|
166462
|
+
mkdir(path3, attrs, cb) {
|
|
166463
166463
|
if (this.server)
|
|
166464
166464
|
throw new Error("Client-only method called in server mode");
|
|
166465
166465
|
let flags = 0;
|
|
@@ -166473,7 +166473,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166473
166473
|
flags = attrs.flags;
|
|
166474
166474
|
attrsLen = attrs.nb;
|
|
166475
166475
|
}
|
|
166476
|
-
const pathLen = Buffer.byteLength(
|
|
166476
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166477
166477
|
let p = 9;
|
|
166478
166478
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + attrsLen);
|
|
166479
166479
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166481,7 +166481,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166481
166481
|
const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
|
|
166482
166482
|
writeUInt32BE(buf, reqid, 5);
|
|
166483
166483
|
writeUInt32BE(buf, pathLen, p);
|
|
166484
|
-
buf.utf8Write(
|
|
166484
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166485
166485
|
writeUInt32BE(buf, flags, p += pathLen);
|
|
166486
166486
|
if (attrsLen) {
|
|
166487
166487
|
p += 4;
|
|
@@ -166495,10 +166495,10 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166495
166495
|
const isBuffered = sendOrBuffer(this, buf);
|
|
166496
166496
|
this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} MKDIR`);
|
|
166497
166497
|
}
|
|
166498
|
-
rmdir(
|
|
166498
|
+
rmdir(path3, cb) {
|
|
166499
166499
|
if (this.server)
|
|
166500
166500
|
throw new Error("Client-only method called in server mode");
|
|
166501
|
-
const pathLen = Buffer.byteLength(
|
|
166501
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166502
166502
|
let p = 9;
|
|
166503
166503
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
|
|
166504
166504
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166506,7 +166506,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166506
166506
|
const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
|
|
166507
166507
|
writeUInt32BE(buf, reqid, 5);
|
|
166508
166508
|
writeUInt32BE(buf, pathLen, p);
|
|
166509
|
-
buf.utf8Write(
|
|
166509
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166510
166510
|
this._requests[reqid] = { cb };
|
|
166511
166511
|
const isBuffered = sendOrBuffer(this, buf);
|
|
166512
166512
|
this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} RMDIR`);
|
|
@@ -166590,10 +166590,10 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166590
166590
|
const isBuffered = sendOrBuffer(this, buf);
|
|
166591
166591
|
this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} FSTAT`);
|
|
166592
166592
|
}
|
|
166593
|
-
stat(
|
|
166593
|
+
stat(path3, cb) {
|
|
166594
166594
|
if (this.server)
|
|
166595
166595
|
throw new Error("Client-only method called in server mode");
|
|
166596
|
-
const pathLen = Buffer.byteLength(
|
|
166596
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166597
166597
|
let p = 9;
|
|
166598
166598
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
|
|
166599
166599
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166601,15 +166601,15 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166601
166601
|
const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
|
|
166602
166602
|
writeUInt32BE(buf, reqid, 5);
|
|
166603
166603
|
writeUInt32BE(buf, pathLen, p);
|
|
166604
|
-
buf.utf8Write(
|
|
166604
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166605
166605
|
this._requests[reqid] = { cb };
|
|
166606
166606
|
const isBuffered = sendOrBuffer(this, buf);
|
|
166607
166607
|
this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} STAT`);
|
|
166608
166608
|
}
|
|
166609
|
-
lstat(
|
|
166609
|
+
lstat(path3, cb) {
|
|
166610
166610
|
if (this.server)
|
|
166611
166611
|
throw new Error("Client-only method called in server mode");
|
|
166612
|
-
const pathLen = Buffer.byteLength(
|
|
166612
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166613
166613
|
let p = 9;
|
|
166614
166614
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
|
|
166615
166615
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166617,15 +166617,15 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166617
166617
|
const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
|
|
166618
166618
|
writeUInt32BE(buf, reqid, 5);
|
|
166619
166619
|
writeUInt32BE(buf, pathLen, p);
|
|
166620
|
-
buf.utf8Write(
|
|
166620
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166621
166621
|
this._requests[reqid] = { cb };
|
|
166622
166622
|
const isBuffered = sendOrBuffer(this, buf);
|
|
166623
166623
|
this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} LSTAT`);
|
|
166624
166624
|
}
|
|
166625
|
-
opendir(
|
|
166625
|
+
opendir(path3, cb) {
|
|
166626
166626
|
if (this.server)
|
|
166627
166627
|
throw new Error("Client-only method called in server mode");
|
|
166628
|
-
const pathLen = Buffer.byteLength(
|
|
166628
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166629
166629
|
let p = 9;
|
|
166630
166630
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
|
|
166631
166631
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166633,12 +166633,12 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166633
166633
|
const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
|
|
166634
166634
|
writeUInt32BE(buf, reqid, 5);
|
|
166635
166635
|
writeUInt32BE(buf, pathLen, p);
|
|
166636
|
-
buf.utf8Write(
|
|
166636
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166637
166637
|
this._requests[reqid] = { cb };
|
|
166638
166638
|
const isBuffered = sendOrBuffer(this, buf);
|
|
166639
166639
|
this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} OPENDIR`);
|
|
166640
166640
|
}
|
|
166641
|
-
setstat(
|
|
166641
|
+
setstat(path3, attrs, cb) {
|
|
166642
166642
|
if (this.server)
|
|
166643
166643
|
throw new Error("Client-only method called in server mode");
|
|
166644
166644
|
let flags = 0;
|
|
@@ -166650,7 +166650,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166650
166650
|
} else if (typeof attrs === "function") {
|
|
166651
166651
|
cb = attrs;
|
|
166652
166652
|
}
|
|
166653
|
-
const pathLen = Buffer.byteLength(
|
|
166653
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166654
166654
|
let p = 9;
|
|
166655
166655
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + attrsLen);
|
|
166656
166656
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166658,7 +166658,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166658
166658
|
const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
|
|
166659
166659
|
writeUInt32BE(buf, reqid, 5);
|
|
166660
166660
|
writeUInt32BE(buf, pathLen, p);
|
|
166661
|
-
buf.utf8Write(
|
|
166661
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166662
166662
|
writeUInt32BE(buf, flags, p += pathLen);
|
|
166663
166663
|
if (attrsLen) {
|
|
166664
166664
|
p += 4;
|
|
@@ -166714,8 +166714,8 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166714
166714
|
mtime: toUnixTimestamp(mtime)
|
|
166715
166715
|
}, cb);
|
|
166716
166716
|
}
|
|
166717
|
-
utimes(
|
|
166718
|
-
return this.setstat(
|
|
166717
|
+
utimes(path3, atime, mtime, cb) {
|
|
166718
|
+
return this.setstat(path3, {
|
|
166719
166719
|
atime: toUnixTimestamp(atime),
|
|
166720
166720
|
mtime: toUnixTimestamp(mtime)
|
|
166721
166721
|
}, cb);
|
|
@@ -166726,8 +166726,8 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166726
166726
|
gid
|
|
166727
166727
|
}, cb);
|
|
166728
166728
|
}
|
|
166729
|
-
chown(
|
|
166730
|
-
return this.setstat(
|
|
166729
|
+
chown(path3, uid, gid, cb) {
|
|
166730
|
+
return this.setstat(path3, {
|
|
166731
166731
|
uid,
|
|
166732
166732
|
gid
|
|
166733
166733
|
}, cb);
|
|
@@ -166737,15 +166737,15 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166737
166737
|
mode
|
|
166738
166738
|
}, cb);
|
|
166739
166739
|
}
|
|
166740
|
-
chmod(
|
|
166741
|
-
return this.setstat(
|
|
166740
|
+
chmod(path3, mode, cb) {
|
|
166741
|
+
return this.setstat(path3, {
|
|
166742
166742
|
mode
|
|
166743
166743
|
}, cb);
|
|
166744
166744
|
}
|
|
166745
|
-
readlink(
|
|
166745
|
+
readlink(path3, cb) {
|
|
166746
166746
|
if (this.server)
|
|
166747
166747
|
throw new Error("Client-only method called in server mode");
|
|
166748
|
-
const pathLen = Buffer.byteLength(
|
|
166748
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166749
166749
|
let p = 9;
|
|
166750
166750
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
|
|
166751
166751
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166753,7 +166753,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166753
166753
|
const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
|
|
166754
166754
|
writeUInt32BE(buf, reqid, 5);
|
|
166755
166755
|
writeUInt32BE(buf, pathLen, p);
|
|
166756
|
-
buf.utf8Write(
|
|
166756
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166757
166757
|
this._requests[reqid] = {
|
|
166758
166758
|
cb: (err, names) => {
|
|
166759
166759
|
if (typeof cb !== "function")
|
|
@@ -166794,10 +166794,10 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166794
166794
|
const isBuffered = sendOrBuffer(this, buf);
|
|
166795
166795
|
this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} SYMLINK`);
|
|
166796
166796
|
}
|
|
166797
|
-
realpath(
|
|
166797
|
+
realpath(path3, cb) {
|
|
166798
166798
|
if (this.server)
|
|
166799
166799
|
throw new Error("Client-only method called in server mode");
|
|
166800
|
-
const pathLen = Buffer.byteLength(
|
|
166800
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166801
166801
|
let p = 9;
|
|
166802
166802
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
|
|
166803
166803
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166805,7 +166805,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166805
166805
|
const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
|
|
166806
166806
|
writeUInt32BE(buf, reqid, 5);
|
|
166807
166807
|
writeUInt32BE(buf, pathLen, p);
|
|
166808
|
-
buf.utf8Write(
|
|
166808
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166809
166809
|
this._requests[reqid] = {
|
|
166810
166810
|
cb: (err, names) => {
|
|
166811
166811
|
if (typeof cb !== "function")
|
|
@@ -166847,13 +166847,13 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166847
166847
|
this._debug(`SFTP: Outbound: ${which} posix-rename@openssh.com`);
|
|
166848
166848
|
}
|
|
166849
166849
|
}
|
|
166850
|
-
ext_openssh_statvfs(
|
|
166850
|
+
ext_openssh_statvfs(path3, cb) {
|
|
166851
166851
|
if (this.server)
|
|
166852
166852
|
throw new Error("Client-only method called in server mode");
|
|
166853
166853
|
const ext = this._extensions["statvfs@openssh.com"];
|
|
166854
166854
|
if (!ext || ext !== "2")
|
|
166855
166855
|
throw new Error("Server does not support this extended request");
|
|
166856
|
-
const pathLen = Buffer.byteLength(
|
|
166856
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166857
166857
|
let p = 9;
|
|
166858
166858
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 19 + 4 + pathLen);
|
|
166859
166859
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166863,7 +166863,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166863
166863
|
writeUInt32BE(buf, 19, p);
|
|
166864
166864
|
buf.utf8Write("statvfs@openssh.com", p += 4, 19);
|
|
166865
166865
|
writeUInt32BE(buf, pathLen, p += 19);
|
|
166866
|
-
buf.utf8Write(
|
|
166866
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166867
166867
|
this._requests[reqid] = { extended: "statvfs@openssh.com", cb };
|
|
166868
166868
|
const isBuffered = sendOrBuffer(this, buf);
|
|
166869
166869
|
if (this._debug) {
|
|
@@ -166947,7 +166947,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166947
166947
|
const isBuffered = sendOrBuffer(this, buf);
|
|
166948
166948
|
this._debug && this._debug(`SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} fsync@openssh.com`);
|
|
166949
166949
|
}
|
|
166950
|
-
ext_openssh_lsetstat(
|
|
166950
|
+
ext_openssh_lsetstat(path3, attrs, cb) {
|
|
166951
166951
|
if (this.server)
|
|
166952
166952
|
throw new Error("Client-only method called in server mode");
|
|
166953
166953
|
const ext = this._extensions["lsetstat@openssh.com"];
|
|
@@ -166962,7 +166962,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166962
166962
|
} else if (typeof attrs === "function") {
|
|
166963
166963
|
cb = attrs;
|
|
166964
166964
|
}
|
|
166965
|
-
const pathLen = Buffer.byteLength(
|
|
166965
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166966
166966
|
let p = 9;
|
|
166967
166967
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 20 + 4 + pathLen + 4 + attrsLen);
|
|
166968
166968
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -166972,7 +166972,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166972
166972
|
writeUInt32BE(buf, 20, p);
|
|
166973
166973
|
buf.utf8Write("lsetstat@openssh.com", p += 4, 20);
|
|
166974
166974
|
writeUInt32BE(buf, pathLen, p += 20);
|
|
166975
|
-
buf.utf8Write(
|
|
166975
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
166976
166976
|
writeUInt32BE(buf, flags, p += pathLen);
|
|
166977
166977
|
if (attrsLen) {
|
|
166978
166978
|
p += 4;
|
|
@@ -166989,13 +166989,13 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
166989
166989
|
this._debug(`SFTP: Outbound: ${status} lsetstat@openssh.com`);
|
|
166990
166990
|
}
|
|
166991
166991
|
}
|
|
166992
|
-
ext_openssh_expandPath(
|
|
166992
|
+
ext_openssh_expandPath(path3, cb) {
|
|
166993
166993
|
if (this.server)
|
|
166994
166994
|
throw new Error("Client-only method called in server mode");
|
|
166995
166995
|
const ext = this._extensions["expand-path@openssh.com"];
|
|
166996
166996
|
if (ext !== "1")
|
|
166997
166997
|
throw new Error("Server does not support this extended request");
|
|
166998
|
-
const pathLen = Buffer.byteLength(
|
|
166998
|
+
const pathLen = Buffer.byteLength(path3);
|
|
166999
166999
|
let p = 9;
|
|
167000
167000
|
const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 23 + 4 + pathLen);
|
|
167001
167001
|
writeUInt32BE(buf, buf.length - 4, 0);
|
|
@@ -167005,7 +167005,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
167005
167005
|
writeUInt32BE(buf, 23, p);
|
|
167006
167006
|
buf.utf8Write("expand-path@openssh.com", p += 4, 23);
|
|
167007
167007
|
writeUInt32BE(buf, pathLen, p += 20);
|
|
167008
|
-
buf.utf8Write(
|
|
167008
|
+
buf.utf8Write(path3, p += 4, pathLen);
|
|
167009
167009
|
this._requests[reqid] = {
|
|
167010
167010
|
cb: (err, names) => {
|
|
167011
167011
|
if (typeof cb !== "function")
|
|
@@ -168222,12 +168222,12 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
168222
168222
|
[REQUEST.LSTAT]: (sftp, payload) => {
|
|
168223
168223
|
bufferParser.init(payload, 1);
|
|
168224
168224
|
const reqID = bufferParser.readUInt32BE();
|
|
168225
|
-
const
|
|
168225
|
+
const path3 = bufferParser.readString(true);
|
|
168226
168226
|
bufferParser.clear();
|
|
168227
|
-
if (
|
|
168227
|
+
if (path3 === undefined)
|
|
168228
168228
|
return doFatalSFTPError(sftp, "Malformed LSTAT packet");
|
|
168229
168229
|
sftp._debug && sftp._debug(`SFTP: Inbound: Received LSTAT (id:${reqID})`);
|
|
168230
|
-
if (!sftp.emit("LSTAT", reqID,
|
|
168230
|
+
if (!sftp.emit("LSTAT", reqID, path3)) {
|
|
168231
168231
|
sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
|
|
168232
168232
|
}
|
|
168233
168233
|
},
|
|
@@ -168246,13 +168246,13 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
168246
168246
|
[REQUEST.SETSTAT]: (sftp, payload) => {
|
|
168247
168247
|
bufferParser.init(payload, 1);
|
|
168248
168248
|
const reqID = bufferParser.readUInt32BE();
|
|
168249
|
-
const
|
|
168249
|
+
const path3 = bufferParser.readString(true);
|
|
168250
168250
|
const attrs = readAttrs(sftp._biOpt);
|
|
168251
168251
|
bufferParser.clear();
|
|
168252
168252
|
if (attrs === undefined)
|
|
168253
168253
|
return doFatalSFTPError(sftp, "Malformed SETSTAT packet");
|
|
168254
168254
|
sftp._debug && sftp._debug(`SFTP: Inbound: Received SETSTAT (id:${reqID})`);
|
|
168255
|
-
if (!sftp.emit("SETSTAT", reqID,
|
|
168255
|
+
if (!sftp.emit("SETSTAT", reqID, path3, attrs)) {
|
|
168256
168256
|
sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
|
|
168257
168257
|
}
|
|
168258
168258
|
},
|
|
@@ -168272,12 +168272,12 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
168272
168272
|
[REQUEST.OPENDIR]: (sftp, payload) => {
|
|
168273
168273
|
bufferParser.init(payload, 1);
|
|
168274
168274
|
const reqID = bufferParser.readUInt32BE();
|
|
168275
|
-
const
|
|
168275
|
+
const path3 = bufferParser.readString(true);
|
|
168276
168276
|
bufferParser.clear();
|
|
168277
|
-
if (
|
|
168277
|
+
if (path3 === undefined)
|
|
168278
168278
|
return doFatalSFTPError(sftp, "Malformed OPENDIR packet");
|
|
168279
168279
|
sftp._debug && sftp._debug(`SFTP: Inbound: Received OPENDIR (id:${reqID})`);
|
|
168280
|
-
if (!sftp.emit("OPENDIR", reqID,
|
|
168280
|
+
if (!sftp.emit("OPENDIR", reqID, path3)) {
|
|
168281
168281
|
sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
|
|
168282
168282
|
}
|
|
168283
168283
|
},
|
|
@@ -168296,61 +168296,61 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
168296
168296
|
[REQUEST.REMOVE]: (sftp, payload) => {
|
|
168297
168297
|
bufferParser.init(payload, 1);
|
|
168298
168298
|
const reqID = bufferParser.readUInt32BE();
|
|
168299
|
-
const
|
|
168299
|
+
const path3 = bufferParser.readString(true);
|
|
168300
168300
|
bufferParser.clear();
|
|
168301
|
-
if (
|
|
168301
|
+
if (path3 === undefined)
|
|
168302
168302
|
return doFatalSFTPError(sftp, "Malformed REMOVE packet");
|
|
168303
168303
|
sftp._debug && sftp._debug(`SFTP: Inbound: Received REMOVE (id:${reqID})`);
|
|
168304
|
-
if (!sftp.emit("REMOVE", reqID,
|
|
168304
|
+
if (!sftp.emit("REMOVE", reqID, path3)) {
|
|
168305
168305
|
sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
|
|
168306
168306
|
}
|
|
168307
168307
|
},
|
|
168308
168308
|
[REQUEST.MKDIR]: (sftp, payload) => {
|
|
168309
168309
|
bufferParser.init(payload, 1);
|
|
168310
168310
|
const reqID = bufferParser.readUInt32BE();
|
|
168311
|
-
const
|
|
168311
|
+
const path3 = bufferParser.readString(true);
|
|
168312
168312
|
const attrs = readAttrs(sftp._biOpt);
|
|
168313
168313
|
bufferParser.clear();
|
|
168314
168314
|
if (attrs === undefined)
|
|
168315
168315
|
return doFatalSFTPError(sftp, "Malformed MKDIR packet");
|
|
168316
168316
|
sftp._debug && sftp._debug(`SFTP: Inbound: Received MKDIR (id:${reqID})`);
|
|
168317
|
-
if (!sftp.emit("MKDIR", reqID,
|
|
168317
|
+
if (!sftp.emit("MKDIR", reqID, path3, attrs)) {
|
|
168318
168318
|
sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
|
|
168319
168319
|
}
|
|
168320
168320
|
},
|
|
168321
168321
|
[REQUEST.RMDIR]: (sftp, payload) => {
|
|
168322
168322
|
bufferParser.init(payload, 1);
|
|
168323
168323
|
const reqID = bufferParser.readUInt32BE();
|
|
168324
|
-
const
|
|
168324
|
+
const path3 = bufferParser.readString(true);
|
|
168325
168325
|
bufferParser.clear();
|
|
168326
|
-
if (
|
|
168326
|
+
if (path3 === undefined)
|
|
168327
168327
|
return doFatalSFTPError(sftp, "Malformed RMDIR packet");
|
|
168328
168328
|
sftp._debug && sftp._debug(`SFTP: Inbound: Received RMDIR (id:${reqID})`);
|
|
168329
|
-
if (!sftp.emit("RMDIR", reqID,
|
|
168329
|
+
if (!sftp.emit("RMDIR", reqID, path3)) {
|
|
168330
168330
|
sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
|
|
168331
168331
|
}
|
|
168332
168332
|
},
|
|
168333
168333
|
[REQUEST.REALPATH]: (sftp, payload) => {
|
|
168334
168334
|
bufferParser.init(payload, 1);
|
|
168335
168335
|
const reqID = bufferParser.readUInt32BE();
|
|
168336
|
-
const
|
|
168336
|
+
const path3 = bufferParser.readString(true);
|
|
168337
168337
|
bufferParser.clear();
|
|
168338
|
-
if (
|
|
168338
|
+
if (path3 === undefined)
|
|
168339
168339
|
return doFatalSFTPError(sftp, "Malformed REALPATH packet");
|
|
168340
168340
|
sftp._debug && sftp._debug(`SFTP: Inbound: Received REALPATH (id:${reqID})`);
|
|
168341
|
-
if (!sftp.emit("REALPATH", reqID,
|
|
168341
|
+
if (!sftp.emit("REALPATH", reqID, path3)) {
|
|
168342
168342
|
sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
|
|
168343
168343
|
}
|
|
168344
168344
|
},
|
|
168345
168345
|
[REQUEST.STAT]: (sftp, payload) => {
|
|
168346
168346
|
bufferParser.init(payload, 1);
|
|
168347
168347
|
const reqID = bufferParser.readUInt32BE();
|
|
168348
|
-
const
|
|
168348
|
+
const path3 = bufferParser.readString(true);
|
|
168349
168349
|
bufferParser.clear();
|
|
168350
|
-
if (
|
|
168350
|
+
if (path3 === undefined)
|
|
168351
168351
|
return doFatalSFTPError(sftp, "Malformed STAT packet");
|
|
168352
168352
|
sftp._debug && sftp._debug(`SFTP: Inbound: Received STAT (id:${reqID})`);
|
|
168353
|
-
if (!sftp.emit("STAT", reqID,
|
|
168353
|
+
if (!sftp.emit("STAT", reqID, path3)) {
|
|
168354
168354
|
sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
|
|
168355
168355
|
}
|
|
168356
168356
|
},
|
|
@@ -168370,12 +168370,12 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
168370
168370
|
[REQUEST.READLINK]: (sftp, payload) => {
|
|
168371
168371
|
bufferParser.init(payload, 1);
|
|
168372
168372
|
const reqID = bufferParser.readUInt32BE();
|
|
168373
|
-
const
|
|
168373
|
+
const path3 = bufferParser.readString(true);
|
|
168374
168374
|
bufferParser.clear();
|
|
168375
|
-
if (
|
|
168375
|
+
if (path3 === undefined)
|
|
168376
168376
|
return doFatalSFTPError(sftp, "Malformed READLINK packet");
|
|
168377
168377
|
sftp._debug && sftp._debug(`SFTP: Inbound: Received READLINK (id:${reqID})`);
|
|
168378
|
-
if (!sftp.emit("READLINK", reqID,
|
|
168378
|
+
if (!sftp.emit("READLINK", reqID, path3)) {
|
|
168379
168379
|
sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
|
|
168380
168380
|
}
|
|
168381
168381
|
},
|
|
@@ -168444,7 +168444,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
168444
168444
|
function roundUpToMultipleOf8(n) {
|
|
168445
168445
|
return n + 7 & ~7;
|
|
168446
168446
|
}
|
|
168447
|
-
function ReadStream(sftp,
|
|
168447
|
+
function ReadStream(sftp, path3, options) {
|
|
168448
168448
|
if (options === undefined)
|
|
168449
168449
|
options = {};
|
|
168450
168450
|
else if (typeof options === "string")
|
|
@@ -168458,7 +168458,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
168458
168458
|
options.emitClose = false;
|
|
168459
168459
|
options.autoDestroy = false;
|
|
168460
168460
|
ReadableStream3.call(this, options);
|
|
168461
|
-
this.path =
|
|
168461
|
+
this.path = path3;
|
|
168462
168462
|
this.flags = options.flags === undefined ? "r" : options.flags;
|
|
168463
168463
|
this.mode = options.mode === undefined ? 438 : options.mode;
|
|
168464
168464
|
this.start = options.start;
|
|
@@ -168578,7 +168578,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
168578
168578
|
},
|
|
168579
168579
|
configurable: true
|
|
168580
168580
|
});
|
|
168581
|
-
function WriteStream(sftp,
|
|
168581
|
+
function WriteStream(sftp, path3, options) {
|
|
168582
168582
|
if (options === undefined)
|
|
168583
168583
|
options = {};
|
|
168584
168584
|
else if (typeof options === "string")
|
|
@@ -168590,7 +168590,7 @@ var require_SFTP = __commonJS((exports, module) => {
|
|
|
168590
168590
|
options.emitClose = false;
|
|
168591
168591
|
options.autoDestroy = false;
|
|
168592
168592
|
WritableStream.call(this, options);
|
|
168593
|
-
this.path =
|
|
168593
|
+
this.path = path3;
|
|
168594
168594
|
this.flags = options.flags === undefined ? "w" : options.flags;
|
|
168595
168595
|
this.mode = options.mode === undefined ? 438 : options.mode;
|
|
168596
168596
|
this.start = options.start;
|
|
@@ -242929,6 +242929,15 @@ function resolvePublisherConfigPath(serverRoot) {
|
|
|
242929
242929
|
}
|
|
242930
242930
|
return null;
|
|
242931
242931
|
}
|
|
242932
|
+
var PER_MODE_COLOR_KEYS = [
|
|
242933
|
+
"background",
|
|
242934
|
+
"tableHeader",
|
|
242935
|
+
"tableHeaderBackground",
|
|
242936
|
+
"tableBody",
|
|
242937
|
+
"tile",
|
|
242938
|
+
"tileTitle",
|
|
242939
|
+
"mapColor"
|
|
242940
|
+
];
|
|
242932
242941
|
var DEFAULT_HIGH_WATER_FRACTION = 0.8;
|
|
242933
242942
|
var DEFAULT_LOW_WATER_FRACTION = 0.7;
|
|
242934
242943
|
var DEFAULT_CHECK_INTERVAL_MS = 5000;
|
|
@@ -243118,11 +243127,98 @@ var getPublisherConfig = (serverRoot) => {
|
|
|
243118
243127
|
if (processedConfig && typeof processedConfig === "object" && "frozenConfig" in processedConfig) {
|
|
243119
243128
|
frozenConfig = Boolean(processedConfig.frozenConfig);
|
|
243120
243129
|
}
|
|
243130
|
+
const instanceTheme = sanitizeTheme(processedConfig && typeof processedConfig === "object" && "theme" in processedConfig ? processedConfig.theme : undefined, "publisher.config.json");
|
|
243121
243131
|
return {
|
|
243122
243132
|
frozenConfig,
|
|
243133
|
+
...instanceTheme ? { theme: instanceTheme } : {},
|
|
243123
243134
|
environments
|
|
243124
243135
|
};
|
|
243125
243136
|
};
|
|
243137
|
+
function sanitizeTheme(raw, context) {
|
|
243138
|
+
if (raw === undefined || raw === null)
|
|
243139
|
+
return;
|
|
243140
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
243141
|
+
logger.warn(`Invalid "theme" in ${context}: expected an object. Ignoring.`);
|
|
243142
|
+
return;
|
|
243143
|
+
}
|
|
243144
|
+
const obj = raw;
|
|
243145
|
+
const theme = {};
|
|
243146
|
+
if ("defaultMode" in obj) {
|
|
243147
|
+
const mode = obj.defaultMode;
|
|
243148
|
+
if (mode === "light" || mode === "dark" || mode === "auto") {
|
|
243149
|
+
theme.defaultMode = mode;
|
|
243150
|
+
} else {
|
|
243151
|
+
logger.warn(`Invalid "theme.defaultMode" in ${context}: expected "light" | "dark" | "auto" (got ${JSON.stringify(mode)}). Ignoring field.`);
|
|
243152
|
+
}
|
|
243153
|
+
}
|
|
243154
|
+
if ("allowUserToggle" in obj) {
|
|
243155
|
+
const value = obj.allowUserToggle;
|
|
243156
|
+
if (typeof value === "boolean") {
|
|
243157
|
+
theme.allowUserToggle = value;
|
|
243158
|
+
} else {
|
|
243159
|
+
logger.warn(`Invalid "theme.allowUserToggle" in ${context}: expected a boolean (got ${JSON.stringify(value)}). Ignoring field.`);
|
|
243160
|
+
}
|
|
243161
|
+
}
|
|
243162
|
+
if ("palette" in obj && obj.palette && typeof obj.palette === "object") {
|
|
243163
|
+
const palette = obj.palette;
|
|
243164
|
+
const sanitized = {};
|
|
243165
|
+
if (Array.isArray(palette.series)) {
|
|
243166
|
+
sanitized.series = palette.series.filter((c) => typeof c === "string");
|
|
243167
|
+
}
|
|
243168
|
+
for (const key of PER_MODE_COLOR_KEYS) {
|
|
243169
|
+
const raw2 = palette[key];
|
|
243170
|
+
if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2))
|
|
243171
|
+
continue;
|
|
243172
|
+
const r = raw2;
|
|
243173
|
+
const out = {};
|
|
243174
|
+
if (typeof r.light === "string")
|
|
243175
|
+
out.light = r.light;
|
|
243176
|
+
if (typeof r.dark === "string")
|
|
243177
|
+
out.dark = r.dark;
|
|
243178
|
+
if (Object.keys(out).length > 0) {
|
|
243179
|
+
sanitized[key] = out;
|
|
243180
|
+
}
|
|
243181
|
+
}
|
|
243182
|
+
if (Object.keys(sanitized).length > 0)
|
|
243183
|
+
theme.palette = sanitized;
|
|
243184
|
+
}
|
|
243185
|
+
if ("font" in obj && obj.font && typeof obj.font === "object") {
|
|
243186
|
+
const font = obj.font;
|
|
243187
|
+
const sanitized = {};
|
|
243188
|
+
if (typeof font.family === "string")
|
|
243189
|
+
sanitized.family = font.family;
|
|
243190
|
+
if (typeof font.size === "number" && Number.isFinite(font.size)) {
|
|
243191
|
+
sanitized.size = font.size;
|
|
243192
|
+
}
|
|
243193
|
+
if (Object.keys(sanitized).length > 0)
|
|
243194
|
+
theme.font = sanitized;
|
|
243195
|
+
}
|
|
243196
|
+
return Object.keys(theme).length > 0 ? theme : undefined;
|
|
243197
|
+
}
|
|
243198
|
+
function mergeThemes(base, override) {
|
|
243199
|
+
if (!base)
|
|
243200
|
+
return override;
|
|
243201
|
+
if (!override)
|
|
243202
|
+
return base;
|
|
243203
|
+
const merged = { ...base, ...override };
|
|
243204
|
+
if (base.palette || override.palette) {
|
|
243205
|
+
merged.palette = {
|
|
243206
|
+
...base.palette ?? {},
|
|
243207
|
+
...override.palette ?? {}
|
|
243208
|
+
};
|
|
243209
|
+
for (const key of PER_MODE_COLOR_KEYS) {
|
|
243210
|
+
const b = base.palette?.[key];
|
|
243211
|
+
const o = override.palette?.[key];
|
|
243212
|
+
if (b || o) {
|
|
243213
|
+
merged.palette[key] = { ...b ?? {}, ...o ?? {} };
|
|
243214
|
+
}
|
|
243215
|
+
}
|
|
243216
|
+
}
|
|
243217
|
+
if (base.font || override.font) {
|
|
243218
|
+
merged.font = { ...base.font ?? {}, ...override.font ?? {} };
|
|
243219
|
+
}
|
|
243220
|
+
return merged;
|
|
243221
|
+
}
|
|
243126
243222
|
var isPublisherConfigFrozen = (serverRoot) => {
|
|
243127
243223
|
try {
|
|
243128
243224
|
const publisherConfig = getPublisherConfig(serverRoot);
|
|
@@ -243198,17 +243294,29 @@ var getProcessedPublisherConfig = (serverRoot) => {
|
|
|
243198
243294
|
logger.warn(`Environment "${environment.name}" has no valid packages. Skipping entry.`);
|
|
243199
243295
|
continue;
|
|
243200
243296
|
}
|
|
243297
|
+
const envTheme = sanitizeTheme(environment.theme, `environment "${environment.name}"`);
|
|
243298
|
+
const resolvedTheme = mergeThemes(rawConfig.theme, envTheme);
|
|
243201
243299
|
validEnvironments.push({
|
|
243202
243300
|
name: environment.name,
|
|
243203
243301
|
packages: validPackages,
|
|
243204
|
-
connections: convertConnectionsToApiConnections(environment.connections || [])
|
|
243302
|
+
connections: convertConnectionsToApiConnections(environment.connections || []),
|
|
243303
|
+
...resolvedTheme ? { theme: resolvedTheme } : {}
|
|
243205
243304
|
});
|
|
243206
243305
|
}
|
|
243207
243306
|
return {
|
|
243208
243307
|
frozenConfig: rawConfig.frozenConfig ?? false,
|
|
243308
|
+
...rawConfig.theme ? { theme: rawConfig.theme } : {},
|
|
243209
243309
|
environments: validEnvironments
|
|
243210
243310
|
};
|
|
243211
243311
|
};
|
|
243312
|
+
var getInstanceTheme = (serverRoot) => {
|
|
243313
|
+
try {
|
|
243314
|
+
return getPublisherConfig(serverRoot).theme;
|
|
243315
|
+
} catch (error) {
|
|
243316
|
+
logger.error(`Error reading instance theme from ${PUBLISHER_CONFIG_NAME}`, { error });
|
|
243317
|
+
return;
|
|
243318
|
+
}
|
|
243319
|
+
};
|
|
243212
243320
|
|
|
243213
243321
|
// src/controller/connection.controller.ts
|
|
243214
243322
|
init_errors();
|
|
@@ -246665,7 +246773,138 @@ function safeJoinUnderRoot(root, ...segments) {
|
|
|
246665
246773
|
|
|
246666
246774
|
// src/service/connection_config.ts
|
|
246667
246775
|
import { createPrivateKey } from "crypto";
|
|
246776
|
+
import { existsSync } from "fs";
|
|
246668
246777
|
import path3 from "path";
|
|
246778
|
+
|
|
246779
|
+
// src/service/proxy.ts
|
|
246780
|
+
init_logger();
|
|
246781
|
+
var import_ssh2 = __toESM(require_lib10(), 1);
|
|
246782
|
+
import net from "net";
|
|
246783
|
+
var SSH_CONNECT_TIMEOUT_MS = 15000;
|
|
246784
|
+
var SSH_KEEPALIVE_INTERVAL_MS = 15000;
|
|
246785
|
+
function parseHostKeys(raw) {
|
|
246786
|
+
const keys = new Set;
|
|
246787
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
246788
|
+
const trimmed = line.trim();
|
|
246789
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
246790
|
+
continue;
|
|
246791
|
+
const tokens = trimmed.split(/\s+/);
|
|
246792
|
+
const blob = tokens.find((t) => /^AAAA[A-Za-z0-9+/]+={0,2}$/.test(t));
|
|
246793
|
+
const key = blob ?? tokens[tokens.length - 1];
|
|
246794
|
+
if (key)
|
|
246795
|
+
keys.add(key);
|
|
246796
|
+
}
|
|
246797
|
+
return keys;
|
|
246798
|
+
}
|
|
246799
|
+
async function openProxy(proxy, target) {
|
|
246800
|
+
if (proxy.type === "ssh") {
|
|
246801
|
+
if (!proxy.ssh) {
|
|
246802
|
+
throw new Error("ConnectionProxy type is 'ssh' but the 'ssh' config object is missing.");
|
|
246803
|
+
}
|
|
246804
|
+
return openSshProxy(proxy.ssh, target);
|
|
246805
|
+
}
|
|
246806
|
+
throw new Error(`Proxy type '${proxy.type}' is not supported yet. Only 'ssh' is implemented.`);
|
|
246807
|
+
}
|
|
246808
|
+
function openSshProxy(ssh, target) {
|
|
246809
|
+
return new Promise((resolve2, reject) => {
|
|
246810
|
+
const client = new import_ssh2.Client;
|
|
246811
|
+
let localPort = 0;
|
|
246812
|
+
let server;
|
|
246813
|
+
let settled = false;
|
|
246814
|
+
const sockets = new Set;
|
|
246815
|
+
function fail(err) {
|
|
246816
|
+
if (settled)
|
|
246817
|
+
return;
|
|
246818
|
+
settled = true;
|
|
246819
|
+
server?.close();
|
|
246820
|
+
client.end();
|
|
246821
|
+
reject(err);
|
|
246822
|
+
}
|
|
246823
|
+
const connectConfig = {
|
|
246824
|
+
host: ssh.host,
|
|
246825
|
+
port: ssh.port ?? 22,
|
|
246826
|
+
username: ssh.username,
|
|
246827
|
+
privateKey: ssh.privateKey,
|
|
246828
|
+
...ssh.privateKeyPass ? { passphrase: ssh.privateKeyPass } : {},
|
|
246829
|
+
readyTimeout: SSH_CONNECT_TIMEOUT_MS,
|
|
246830
|
+
keepaliveInterval: SSH_KEEPALIVE_INTERVAL_MS,
|
|
246831
|
+
keepaliveCountMax: 3,
|
|
246832
|
+
hostVerifier: (key) => {
|
|
246833
|
+
const presented = key.toString("base64");
|
|
246834
|
+
if (ssh.hostKey) {
|
|
246835
|
+
const pinned = parseHostKeys(ssh.hostKey);
|
|
246836
|
+
if (pinned.has(presented)) {
|
|
246837
|
+
return true;
|
|
246838
|
+
}
|
|
246839
|
+
fail(new Error(`SSH host-key verification failed for ${ssh.host}: the presented key ` + `(${presented.slice(0, 24)}…) is not among the ${pinned.size} pinned ` + `host key(s).`));
|
|
246840
|
+
return false;
|
|
246841
|
+
}
|
|
246842
|
+
logger.warn(`Connecting to SSH bastion ${ssh.host} without host-key verification (no hostKey pinned).`);
|
|
246843
|
+
return true;
|
|
246844
|
+
}
|
|
246845
|
+
};
|
|
246846
|
+
client.on("error", (err) => fail(err));
|
|
246847
|
+
client.on("ready", () => {
|
|
246848
|
+
server = net.createServer((socket) => {
|
|
246849
|
+
sockets.add(socket);
|
|
246850
|
+
socket.on("close", () => sockets.delete(socket));
|
|
246851
|
+
const earlyData = [];
|
|
246852
|
+
socket.on("data", (chunk) => earlyData.push(chunk));
|
|
246853
|
+
client.forwardOut("127.0.0.1", localPort, target.host, target.port, (err, channel) => {
|
|
246854
|
+
if (err) {
|
|
246855
|
+
socket.destroy(err);
|
|
246856
|
+
return;
|
|
246857
|
+
}
|
|
246858
|
+
if (socket.destroyed) {
|
|
246859
|
+
channel.destroy();
|
|
246860
|
+
return;
|
|
246861
|
+
}
|
|
246862
|
+
for (const chunk of earlyData) {
|
|
246863
|
+
channel.write(chunk);
|
|
246864
|
+
}
|
|
246865
|
+
earlyData.length = 0;
|
|
246866
|
+
socket.removeAllListeners("data");
|
|
246867
|
+
socket.pipe(channel);
|
|
246868
|
+
channel.pipe(socket);
|
|
246869
|
+
socket.on("error", () => channel.destroy());
|
|
246870
|
+
channel.on("error", () => socket.destroy());
|
|
246871
|
+
socket.on("close", () => channel.destroy());
|
|
246872
|
+
channel.on("close", () => socket.destroy());
|
|
246873
|
+
});
|
|
246874
|
+
});
|
|
246875
|
+
server.listen(0, "127.0.0.1", () => {
|
|
246876
|
+
const addr = server.address();
|
|
246877
|
+
if (!addr || typeof addr === "string") {
|
|
246878
|
+
fail(new Error("Failed to obtain local listen port for SSH proxy."));
|
|
246879
|
+
return;
|
|
246880
|
+
}
|
|
246881
|
+
localPort = addr.port;
|
|
246882
|
+
if (settled)
|
|
246883
|
+
return;
|
|
246884
|
+
settled = true;
|
|
246885
|
+
resolve2({
|
|
246886
|
+
host: "127.0.0.1",
|
|
246887
|
+
port: localPort,
|
|
246888
|
+
close() {
|
|
246889
|
+
return new Promise((res) => {
|
|
246890
|
+
for (const s of sockets)
|
|
246891
|
+
s.destroy();
|
|
246892
|
+
server.close(() => {
|
|
246893
|
+
client.end();
|
|
246894
|
+
res();
|
|
246895
|
+
});
|
|
246896
|
+
});
|
|
246897
|
+
}
|
|
246898
|
+
});
|
|
246899
|
+
});
|
|
246900
|
+
server.on("error", (err) => fail(err));
|
|
246901
|
+
});
|
|
246902
|
+
client.connect(connectConfig);
|
|
246903
|
+
});
|
|
246904
|
+
}
|
|
246905
|
+
|
|
246906
|
+
// src/service/connection_config.ts
|
|
246907
|
+
var PROXIED_SSLMODES = ["disable", "no-verify", "verify-ca"];
|
|
246669
246908
|
var PUBLISHER_DUCKDB_API_FIELDS = new Set(["attachedDatabases"]);
|
|
246670
246909
|
function normalizeSnowflakePrivateKey(privateKey) {
|
|
246671
246910
|
let privateKeyContent = privateKey.trim();
|
|
@@ -246850,6 +247089,29 @@ function validateConnectionShape(connection) {
|
|
|
246850
247089
|
if (!connection.postgresConnection?.host || !connection.postgresConnection?.port) {
|
|
246851
247090
|
throw new Error(`Connection proxy on '${connection.name}' requires explicit host and port on the ` + `postgres connection; the connectionString form is not supported with a proxy.`);
|
|
246852
247091
|
}
|
|
247092
|
+
const hostKey = connection.proxy.ssh?.hostKey;
|
|
247093
|
+
if (hostKey && parseHostKeys(hostKey).size === 0) {
|
|
247094
|
+
throw new Error(`Connection proxy on '${connection.name}' has a hostKey with no usable host-key line ` + `(only blanks/comments). Provide an OpenSSH known_hosts line or base64 blob, or omit ` + `hostKey to connect unpinned.`);
|
|
247095
|
+
}
|
|
247096
|
+
const sslmode = connection.postgresConnection?.sslmode;
|
|
247097
|
+
if (sslmode != null) {
|
|
247098
|
+
if (!PROXIED_SSLMODES.includes(sslmode)) {
|
|
247099
|
+
throw new Error(`Connection proxy on '${connection.name}' has unsupported sslmode '${sslmode}' ` + `(expected ${PROXIED_SSLMODES.join(" | ")}).`);
|
|
247100
|
+
}
|
|
247101
|
+
if (sslmode === "verify-ca") {
|
|
247102
|
+
const caBundle = process.env.NODE_EXTRA_CA_CERTS;
|
|
247103
|
+
if (!caBundle || !existsSync(caBundle)) {
|
|
247104
|
+
throw new Error(`Connection proxy on '${connection.name}' uses sslmode 'verify-ca' but no readable ` + `CA bundle is available (NODE_EXTRA_CA_CERTS is unset or points at a missing file). ` + `Add the CA bundle to the image or use sslmode 'no-verify'.`);
|
|
247105
|
+
}
|
|
247106
|
+
}
|
|
247107
|
+
}
|
|
247108
|
+
const dbName = connection.postgresConnection?.databaseName;
|
|
247109
|
+
if (dbName && decodeURI(encodeURIComponent(dbName)) !== dbName) {
|
|
247110
|
+
throw new Error(`Connection proxy on '${connection.name}' has a database name with characters that can't ` + `be carried over a proxied connection (${JSON.stringify(dbName)}). Use a database name ` + `without URI-reserved characters (; , / ? : @ & = + $ #).`);
|
|
247111
|
+
}
|
|
247112
|
+
}
|
|
247113
|
+
if (!connection.proxy && connection.postgresConnection?.sslmode) {
|
|
247114
|
+
throw new Error(`Connection '${connection.name}' sets postgresConnection.sslmode but has no proxy; sslmode is ` + `only supported for proxied connections (direct connections use the deployment PGSSLMODE).`);
|
|
246853
247115
|
}
|
|
246854
247116
|
switch (connection.type) {
|
|
246855
247117
|
case "postgres":
|
|
@@ -247126,126 +247388,6 @@ function validateAndBuildTrinoCoreConfig(trinoConfig) {
|
|
|
247126
247388
|
throw new Error(`Invalid Trino connection: expected "http://server:port" or "https://server:port".`);
|
|
247127
247389
|
}
|
|
247128
247390
|
|
|
247129
|
-
// src/service/proxy.ts
|
|
247130
|
-
var import_ssh2 = __toESM(require_lib10(), 1);
|
|
247131
|
-
import net from "net";
|
|
247132
|
-
var SSH_CONNECT_TIMEOUT_MS = 15000;
|
|
247133
|
-
var SSH_KEEPALIVE_INTERVAL_MS = 15000;
|
|
247134
|
-
function extractHostKeyBase64(raw) {
|
|
247135
|
-
const tokens = raw.trim().split(/\s+/);
|
|
247136
|
-
const blob = tokens.find((t) => /^AAAA[A-Za-z0-9+/]+={0,2}$/.test(t));
|
|
247137
|
-
return blob ?? tokens[tokens.length - 1] ?? "";
|
|
247138
|
-
}
|
|
247139
|
-
async function openProxy(proxy, target) {
|
|
247140
|
-
if (proxy.type === "ssh") {
|
|
247141
|
-
if (!proxy.ssh) {
|
|
247142
|
-
throw new Error("ConnectionProxy type is 'ssh' but the 'ssh' config object is missing.");
|
|
247143
|
-
}
|
|
247144
|
-
return openSshProxy(proxy.ssh, target);
|
|
247145
|
-
}
|
|
247146
|
-
throw new Error(`Proxy type '${proxy.type}' is not supported yet. Only 'ssh' is implemented.`);
|
|
247147
|
-
}
|
|
247148
|
-
function openSshProxy(ssh, target) {
|
|
247149
|
-
return new Promise((resolve2, reject) => {
|
|
247150
|
-
const client = new import_ssh2.Client;
|
|
247151
|
-
let localPort = 0;
|
|
247152
|
-
let server;
|
|
247153
|
-
let settled = false;
|
|
247154
|
-
const sockets = new Set;
|
|
247155
|
-
function fail(err) {
|
|
247156
|
-
if (settled)
|
|
247157
|
-
return;
|
|
247158
|
-
settled = true;
|
|
247159
|
-
server?.close();
|
|
247160
|
-
client.end();
|
|
247161
|
-
reject(err);
|
|
247162
|
-
}
|
|
247163
|
-
const connectConfig = {
|
|
247164
|
-
host: ssh.host,
|
|
247165
|
-
port: ssh.port ?? 22,
|
|
247166
|
-
username: ssh.username,
|
|
247167
|
-
privateKey: ssh.privateKey,
|
|
247168
|
-
...ssh.privateKeyPass ? { passphrase: ssh.privateKeyPass } : {},
|
|
247169
|
-
readyTimeout: SSH_CONNECT_TIMEOUT_MS,
|
|
247170
|
-
keepaliveInterval: SSH_KEEPALIVE_INTERVAL_MS,
|
|
247171
|
-
keepaliveCountMax: 3,
|
|
247172
|
-
hostVerifier: (key) => {
|
|
247173
|
-
const presented = key.toString("base64");
|
|
247174
|
-
if (ssh.hostKey) {
|
|
247175
|
-
const expected = extractHostKeyBase64(ssh.hostKey);
|
|
247176
|
-
if (presented !== expected) {
|
|
247177
|
-
fail(new Error(`SSH host-key mismatch for ${ssh.host}: expected ${expected.slice(0, 24)}… but got ${presented.slice(0, 24)}….`));
|
|
247178
|
-
return false;
|
|
247179
|
-
}
|
|
247180
|
-
return true;
|
|
247181
|
-
}
|
|
247182
|
-
if (process.env.PUBLISHER_SSH_ALLOW_UNKNOWN_HOSTKEY === "true") {
|
|
247183
|
-
return true;
|
|
247184
|
-
}
|
|
247185
|
-
fail(new Error(`SSH connection to ${ssh.host} refused: no hostKey provided and ` + `PUBLISHER_SSH_ALLOW_UNKNOWN_HOSTKEY is not 'true'. ` + `Set hostKey to the bastion's host public key (an OpenSSH ` + `known_hosts line or its base64 blob, e.g. from ssh-keyscan).`));
|
|
247186
|
-
return false;
|
|
247187
|
-
}
|
|
247188
|
-
};
|
|
247189
|
-
client.on("error", (err) => fail(err));
|
|
247190
|
-
client.on("ready", () => {
|
|
247191
|
-
server = net.createServer((socket) => {
|
|
247192
|
-
sockets.add(socket);
|
|
247193
|
-
socket.on("close", () => sockets.delete(socket));
|
|
247194
|
-
const earlyData = [];
|
|
247195
|
-
socket.on("data", (chunk) => earlyData.push(chunk));
|
|
247196
|
-
client.forwardOut("127.0.0.1", localPort, target.host, target.port, (err, channel) => {
|
|
247197
|
-
if (err) {
|
|
247198
|
-
socket.destroy(err);
|
|
247199
|
-
return;
|
|
247200
|
-
}
|
|
247201
|
-
if (socket.destroyed) {
|
|
247202
|
-
channel.destroy();
|
|
247203
|
-
return;
|
|
247204
|
-
}
|
|
247205
|
-
for (const chunk of earlyData) {
|
|
247206
|
-
channel.write(chunk);
|
|
247207
|
-
}
|
|
247208
|
-
earlyData.length = 0;
|
|
247209
|
-
socket.removeAllListeners("data");
|
|
247210
|
-
socket.pipe(channel);
|
|
247211
|
-
channel.pipe(socket);
|
|
247212
|
-
socket.on("error", () => channel.destroy());
|
|
247213
|
-
channel.on("error", () => socket.destroy());
|
|
247214
|
-
socket.on("close", () => channel.destroy());
|
|
247215
|
-
channel.on("close", () => socket.destroy());
|
|
247216
|
-
});
|
|
247217
|
-
});
|
|
247218
|
-
server.listen(0, "127.0.0.1", () => {
|
|
247219
|
-
const addr = server.address();
|
|
247220
|
-
if (!addr || typeof addr === "string") {
|
|
247221
|
-
fail(new Error("Failed to obtain local listen port for SSH proxy."));
|
|
247222
|
-
return;
|
|
247223
|
-
}
|
|
247224
|
-
localPort = addr.port;
|
|
247225
|
-
if (settled)
|
|
247226
|
-
return;
|
|
247227
|
-
settled = true;
|
|
247228
|
-
resolve2({
|
|
247229
|
-
host: "127.0.0.1",
|
|
247230
|
-
port: localPort,
|
|
247231
|
-
close() {
|
|
247232
|
-
return new Promise((res) => {
|
|
247233
|
-
for (const s of sockets)
|
|
247234
|
-
s.destroy();
|
|
247235
|
-
server.close(() => {
|
|
247236
|
-
client.end();
|
|
247237
|
-
res();
|
|
247238
|
-
});
|
|
247239
|
-
});
|
|
247240
|
-
}
|
|
247241
|
-
});
|
|
247242
|
-
});
|
|
247243
|
-
server.on("error", (err) => fail(err));
|
|
247244
|
-
});
|
|
247245
|
-
client.connect(connectConfig);
|
|
247246
|
-
});
|
|
247247
|
-
}
|
|
247248
|
-
|
|
247249
247391
|
// src/service/connection.ts
|
|
247250
247392
|
async function installAndLoadExtension(connection, extensionName, fromCommunity = false) {
|
|
247251
247393
|
try {
|
|
@@ -247798,19 +247940,37 @@ function buildSnowflakePrivateKeyConnection(metadata) {
|
|
|
247798
247940
|
poolOptions: buildPoolOptions({ poolMin: 1, poolMax: 20 })
|
|
247799
247941
|
});
|
|
247800
247942
|
}
|
|
247943
|
+
function buildProxiedSslQuery(name, sslmode) {
|
|
247944
|
+
const caBundle = process.env.NODE_EXTRA_CA_CERTS;
|
|
247945
|
+
const mode = sslmode ?? "no-verify";
|
|
247946
|
+
switch (mode) {
|
|
247947
|
+
case "disable":
|
|
247948
|
+
return "?sslmode=disable";
|
|
247949
|
+
case "no-verify":
|
|
247950
|
+
return "?sslmode=no-verify";
|
|
247951
|
+
case "verify-ca":
|
|
247952
|
+
if (!caBundle) {
|
|
247953
|
+
throw new Error(`Connection proxy on '${name}' uses sslmode 'verify-ca' but no trusted CA bundle is available (NODE_EXTRA_CA_CERTS is unset). Add the CA bundle to the image or use sslmode 'no-verify'.`);
|
|
247954
|
+
}
|
|
247955
|
+
return `?uselibpqcompat=true&sslmode=verify-ca&sslrootcert=${encodeURIComponent(caBundle)}`;
|
|
247956
|
+
default:
|
|
247957
|
+
throw new Error(`Connection proxy on '${name}' has unsupported sslmode '${mode}' (expected disable | no-verify | verify-ca).`);
|
|
247958
|
+
}
|
|
247959
|
+
}
|
|
247801
247960
|
function buildProxiedPostgresConnection(metadata, endpoint) {
|
|
247802
247961
|
const name = metadata.apiConnection.name;
|
|
247803
247962
|
const pg = metadata.apiConnection.postgresConnection;
|
|
247804
247963
|
if (!pg) {
|
|
247805
247964
|
throw new Error(`Proxied connection '${name}' has type 'postgres' but no postgresConnection config.`);
|
|
247806
247965
|
}
|
|
247966
|
+
const enc = encodeURIComponent;
|
|
247967
|
+
const auth = pg.userName ? `${enc(pg.userName)}${pg.password ? `:${enc(pg.password)}` : ""}@` : "";
|
|
247968
|
+
const db = pg.databaseName ? `/${enc(pg.databaseName)}` : "";
|
|
247969
|
+
const ssl = buildProxiedSslQuery(name, pg.sslmode);
|
|
247970
|
+
const connectionString = `postgresql://${auth}${endpoint.host}:${endpoint.port}${db}${ssl}`;
|
|
247807
247971
|
return new PooledPostgresConnection({
|
|
247808
247972
|
name,
|
|
247809
|
-
|
|
247810
|
-
port: endpoint.port,
|
|
247811
|
-
username: pg.userName,
|
|
247812
|
-
password: pg.password,
|
|
247813
|
-
databaseName: pg.databaseName,
|
|
247973
|
+
connectionString,
|
|
247814
247974
|
poolMin: 1,
|
|
247815
247975
|
poolMax: 20
|
|
247816
247976
|
});
|
|
@@ -247824,6 +247984,13 @@ function buildAzureDuckDBConnection(metadata, entry) {
|
|
|
247824
247984
|
function getMetadataForLookup(metadata, name) {
|
|
247825
247985
|
return name ? metadata.get(name) : undefined;
|
|
247826
247986
|
}
|
|
247987
|
+
function applyConnectionFingerprint(connection, metadata) {
|
|
247988
|
+
const fingerprint = metadata?.apiConnection.fingerprint;
|
|
247989
|
+
if (fingerprint) {
|
|
247990
|
+
connection.getDigest = () => fingerprint;
|
|
247991
|
+
}
|
|
247992
|
+
return connection;
|
|
247993
|
+
}
|
|
247827
247994
|
function isDuckDBConnection(connection) {
|
|
247828
247995
|
return connection instanceof DuckDBConnection;
|
|
247829
247996
|
}
|
|
@@ -247849,9 +248016,8 @@ function buildEnvironmentMalloyConfig(connections = [], environmentPath = "", is
|
|
|
247849
248016
|
}
|
|
247850
248017
|
await attachPromise;
|
|
247851
248018
|
}
|
|
247852
|
-
malloyConfig.wrapConnections((base) =>
|
|
247853
|
-
|
|
247854
|
-
const metadata = getMetadataForLookup(assembled.metadata, name);
|
|
248019
|
+
malloyConfig.wrapConnections((base) => {
|
|
248020
|
+
const resolveConnection = async (name, metadata) => {
|
|
247855
248021
|
if (metadata?.isDuckLake) {
|
|
247856
248022
|
let connectionPromise = duckLakeCache.get(name);
|
|
247857
248023
|
if (!connectionPromise) {
|
|
@@ -247923,8 +248089,14 @@ function buildEnvironmentMalloyConfig(connections = [], environmentPath = "", is
|
|
|
247923
248089
|
await attachOnce(connection, metadata);
|
|
247924
248090
|
}
|
|
247925
248091
|
return connection;
|
|
247926
|
-
}
|
|
247927
|
-
|
|
248092
|
+
};
|
|
248093
|
+
return {
|
|
248094
|
+
lookupConnection: async (name) => {
|
|
248095
|
+
const metadata = getMetadataForLookup(assembled.metadata, name);
|
|
248096
|
+
return applyConnectionFingerprint(await resolveConnection(name, metadata), metadata);
|
|
248097
|
+
}
|
|
248098
|
+
};
|
|
248099
|
+
});
|
|
247928
248100
|
return {
|
|
247929
248101
|
malloyConfig,
|
|
247930
248102
|
apiConnections: assembled.apiConnections,
|
|
@@ -256465,13 +256637,10 @@ class DuckDBRepository {
|
|
|
256465
256637
|
init_logger();
|
|
256466
256638
|
async function initializeSchema(db, force = false) {
|
|
256467
256639
|
const initialized = await db.isInitialized();
|
|
256468
|
-
if (initialized && !force) {
|
|
256469
|
-
return;
|
|
256470
|
-
}
|
|
256471
256640
|
if (force) {
|
|
256472
256641
|
logger.info("Reinitializing database schema dropping and recreating all tables");
|
|
256473
256642
|
await dropAllTables(db);
|
|
256474
|
-
} else {
|
|
256643
|
+
} else if (!initialized) {
|
|
256475
256644
|
await dropLegacyProjectSchema(db);
|
|
256476
256645
|
logger.info("Creating database schema for the first time...");
|
|
256477
256646
|
}
|
|
@@ -256530,6 +256699,14 @@ async function initializeSchema(db, force = false) {
|
|
|
256530
256699
|
FOREIGN KEY (environment_id) REFERENCES environments(id)
|
|
256531
256700
|
)
|
|
256532
256701
|
`);
|
|
256702
|
+
await db.run(`
|
|
256703
|
+
CREATE TABLE IF NOT EXISTS themes (
|
|
256704
|
+
id VARCHAR PRIMARY KEY,
|
|
256705
|
+
payload JSON NOT NULL,
|
|
256706
|
+
created_at TIMESTAMP NOT NULL,
|
|
256707
|
+
updated_at TIMESTAMP NOT NULL
|
|
256708
|
+
)
|
|
256709
|
+
`);
|
|
256533
256710
|
await db.run("CREATE INDEX IF NOT EXISTS idx_packages_environment_id ON packages(environment_id)");
|
|
256534
256711
|
await db.run("CREATE INDEX IF NOT EXISTS idx_connections_environment_id ON connections(environment_id)");
|
|
256535
256712
|
await db.run("CREATE INDEX IF NOT EXISTS idx_materializations_environment_package ON materializations(environment_id, package_name)");
|
|
@@ -256562,7 +256739,8 @@ async function dropAllTables(db) {
|
|
|
256562
256739
|
"materializations",
|
|
256563
256740
|
"packages",
|
|
256564
256741
|
"connections",
|
|
256565
|
-
"environments"
|
|
256742
|
+
"environments",
|
|
256743
|
+
"themes"
|
|
256566
256744
|
];
|
|
256567
256745
|
logger.info("Dropping tables:", tables.join(", "));
|
|
256568
256746
|
for (const table of tables) {
|
|
@@ -256608,6 +256786,15 @@ class StorageManager {
|
|
|
256608
256786
|
this.connection = connection;
|
|
256609
256787
|
this.repository = new DuckDBRepository(connection);
|
|
256610
256788
|
}
|
|
256789
|
+
getDuckDbConnection() {
|
|
256790
|
+
if (!this.connection) {
|
|
256791
|
+
throw new Error("Storage not initialized");
|
|
256792
|
+
}
|
|
256793
|
+
if (!(this.connection instanceof DuckDBConnection2)) {
|
|
256794
|
+
throw new Error("Underlying storage connection is not DuckDB");
|
|
256795
|
+
}
|
|
256796
|
+
return this.connection;
|
|
256797
|
+
}
|
|
256611
256798
|
getRepository() {
|
|
256612
256799
|
if (!this.repository) {
|
|
256613
256800
|
throw new Error("Storage not initialized. Call initialize() first.");
|
|
@@ -256754,16 +256941,16 @@ async function fetchManifestEntries(uri) {
|
|
|
256754
256941
|
throw new Error(`Failed to parse build manifest at ${uri}: ${err instanceof Error ? err.message : String(err)}`);
|
|
256755
256942
|
}
|
|
256756
256943
|
const entries = {};
|
|
256757
|
-
for (const [
|
|
256944
|
+
for (const [sourceEntityId, entry] of Object.entries(parsed.entries ?? {})) {
|
|
256758
256945
|
const physicalTableName = entry?.physicalTableName;
|
|
256759
256946
|
if (!physicalTableName) {
|
|
256760
256947
|
logger.warn("Manifest entry has no physicalTableName; skipping", {
|
|
256761
256948
|
uri,
|
|
256762
|
-
|
|
256949
|
+
sourceEntityId
|
|
256763
256950
|
});
|
|
256764
256951
|
continue;
|
|
256765
256952
|
}
|
|
256766
|
-
entries[
|
|
256953
|
+
entries[sourceEntityId] = { tableName: physicalTableName };
|
|
256767
256954
|
}
|
|
256768
256955
|
return entries;
|
|
256769
256956
|
}
|
|
@@ -258255,7 +258442,7 @@ function* iterGraphSources(graph, sources) {
|
|
|
258255
258442
|
}
|
|
258256
258443
|
}
|
|
258257
258444
|
}
|
|
258258
|
-
function
|
|
258445
|
+
function computeSourceEntityId(source, connectionDigests) {
|
|
258259
258446
|
return source.makeBuildId(connectionDigests[source.connectionName], source.getSQL());
|
|
258260
258447
|
}
|
|
258261
258448
|
async function resolvePackageConnections(pkg, names) {
|
|
@@ -258343,7 +258530,7 @@ function deriveBuildPlan(graphs, sources, connectionDigests, sourceNames, source
|
|
|
258343
258530
|
sourceID: source.sourceID,
|
|
258344
258531
|
connectionName: source.connectionName,
|
|
258345
258532
|
dialect: source.dialectName,
|
|
258346
|
-
|
|
258533
|
+
sourceEntityId: computeSourceEntityId(source, connectionDigests),
|
|
258347
258534
|
sql: source.getSQL(),
|
|
258348
258535
|
columns: deriveColumns(source),
|
|
258349
258536
|
annotationFields: deriveAnnotationFields(source),
|
|
@@ -260983,7 +261170,7 @@ class MaterializationController {
|
|
|
260983
261170
|
}
|
|
260984
261171
|
const instruction = raw;
|
|
260985
261172
|
const required = [
|
|
260986
|
-
"
|
|
261173
|
+
"sourceEntityId",
|
|
260987
261174
|
"materializedTableId",
|
|
260988
261175
|
"physicalTableName",
|
|
260989
261176
|
"realization"
|
|
@@ -260997,7 +261184,7 @@ class MaterializationController {
|
|
|
260997
261184
|
throw new BadRequestError("Build instruction 'realization' must be COPY or SNAPSHOT");
|
|
260998
261185
|
}
|
|
260999
261186
|
return {
|
|
261000
|
-
|
|
261187
|
+
sourceEntityId: instruction.sourceEntityId,
|
|
261001
261188
|
sourceID: typeof instruction.sourceID === "string" ? instruction.sourceID : undefined,
|
|
261002
261189
|
materializedTableId: instruction.materializedTableId,
|
|
261003
261190
|
physicalTableName: instruction.physicalTableName,
|
|
@@ -261018,6 +261205,40 @@ class MaterializationController {
|
|
|
261018
261205
|
}
|
|
261019
261206
|
}
|
|
261020
261207
|
|
|
261208
|
+
// src/controller/theme.controller.ts
|
|
261209
|
+
init_errors();
|
|
261210
|
+
class ThemeController {
|
|
261211
|
+
themeStore;
|
|
261212
|
+
serverRoot;
|
|
261213
|
+
constructor(themeStore, serverRoot) {
|
|
261214
|
+
this.themeStore = themeStore;
|
|
261215
|
+
this.serverRoot = serverRoot;
|
|
261216
|
+
}
|
|
261217
|
+
getTheme = async () => {
|
|
261218
|
+
const theme = await this.themeStore.get();
|
|
261219
|
+
return theme ?? {};
|
|
261220
|
+
};
|
|
261221
|
+
putTheme = async (body) => {
|
|
261222
|
+
if (isPublisherConfigFrozen(this.serverRoot)) {
|
|
261223
|
+
throw new FrozenConfigError('Cannot edit theme: publisher.config.json has "frozenConfig": true.');
|
|
261224
|
+
}
|
|
261225
|
+
const isEmptyObject2 = typeof body === "object" && body !== null && !Array.isArray(body) && Object.keys(body).length === 0;
|
|
261226
|
+
const sanitized = sanitizeTheme(body, "PUT /api/v0/theme");
|
|
261227
|
+
if (!sanitized && !isEmptyObject2) {
|
|
261228
|
+
throw new BadRequestError("Theme payload was malformed (expected an object).");
|
|
261229
|
+
}
|
|
261230
|
+
const saved = await this.themeStore.set(sanitized ?? {});
|
|
261231
|
+
return saved;
|
|
261232
|
+
};
|
|
261233
|
+
resetTheme = async () => {
|
|
261234
|
+
if (isPublisherConfigFrozen(this.serverRoot)) {
|
|
261235
|
+
throw new FrozenConfigError('Cannot reset theme: publisher.config.json has "frozenConfig": true.');
|
|
261236
|
+
}
|
|
261237
|
+
const reseeded = await this.themeStore.reset();
|
|
261238
|
+
return reseeded ?? {};
|
|
261239
|
+
};
|
|
261240
|
+
}
|
|
261241
|
+
|
|
261021
261242
|
// ../../node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
|
|
261022
261243
|
var DEFAULT_REQUEST_TIMEOUT_MSEC = 60000;
|
|
261023
261244
|
|
|
@@ -267581,9 +267802,9 @@ async function resolveEnvironmentId(repository, environmentName) {
|
|
|
267581
267802
|
}
|
|
267582
267803
|
|
|
267583
267804
|
// src/service/materialization_service.ts
|
|
267584
|
-
var
|
|
267585
|
-
function stagingSuffix(
|
|
267586
|
-
return `_${
|
|
267805
|
+
var STAGING_ID_LEN = 12;
|
|
267806
|
+
function stagingSuffix(sourceEntityId) {
|
|
267807
|
+
return `_${sourceEntityId.replace(/-/g, "").substring(0, STAGING_ID_LEN)}`;
|
|
267587
267808
|
}
|
|
267588
267809
|
function selfAssignTableName(persistSource) {
|
|
267589
267810
|
return deriveAnnotationFields(persistSource).name || persistSource.name;
|
|
@@ -267745,18 +267966,18 @@ class MaterializationService {
|
|
|
267745
267966
|
for (const persistSource of iterGraphSources(graph, compiled.sources)) {
|
|
267746
267967
|
if (include && !include.has(persistSource.name))
|
|
267747
267968
|
continue;
|
|
267748
|
-
const
|
|
267749
|
-
if (seen.has(
|
|
267969
|
+
const sourceEntityId = computeSourceEntityId(persistSource, compiled.connectionDigests);
|
|
267970
|
+
if (seen.has(sourceEntityId))
|
|
267750
267971
|
continue;
|
|
267751
|
-
seen.add(
|
|
267752
|
-
const prior = priorEntries[
|
|
267972
|
+
seen.add(sourceEntityId);
|
|
267973
|
+
const prior = priorEntries[sourceEntityId];
|
|
267753
267974
|
if (prior && prior.physicalTableName) {
|
|
267754
|
-
carried[
|
|
267975
|
+
carried[sourceEntityId] = prior;
|
|
267755
267976
|
continue;
|
|
267756
267977
|
}
|
|
267757
267978
|
instructions.push({
|
|
267758
|
-
|
|
267759
|
-
materializedTableId: `local-${
|
|
267979
|
+
sourceEntityId,
|
|
267980
|
+
materializedTableId: `local-${sourceEntityId.substring(0, STAGING_ID_LEN)}`,
|
|
267760
267981
|
physicalTableName: selfAssignTableName(persistSource),
|
|
267761
267982
|
realization: "COPY"
|
|
267762
267983
|
});
|
|
@@ -267777,9 +267998,11 @@ class MaterializationService {
|
|
|
267777
267998
|
}
|
|
267778
267999
|
async autoLoadManifest(environment, packageName, entries) {
|
|
267779
268000
|
const manifestEntries = {};
|
|
267780
|
-
for (const [
|
|
268001
|
+
for (const [sourceEntityId, entry] of Object.entries(entries)) {
|
|
267781
268002
|
if (entry.physicalTableName) {
|
|
267782
|
-
manifestEntries[
|
|
268003
|
+
manifestEntries[sourceEntityId] = {
|
|
268004
|
+
tableName: entry.physicalTableName
|
|
268005
|
+
};
|
|
267783
268006
|
}
|
|
267784
268007
|
}
|
|
267785
268008
|
try {
|
|
@@ -267801,13 +268024,13 @@ class MaterializationService {
|
|
|
267801
268024
|
if (!plan) {
|
|
267802
268025
|
throw new BadRequestError("Package has no persist sources; buildInstructions cannot be applied");
|
|
267803
268026
|
}
|
|
267804
|
-
const
|
|
268027
|
+
const plannedSourceEntityIds = new Set;
|
|
267805
268028
|
for (const source of Object.values(plan.sources)) {
|
|
267806
|
-
|
|
268029
|
+
plannedSourceEntityIds.add(source.sourceEntityId);
|
|
267807
268030
|
}
|
|
267808
268031
|
for (const instruction of instructions) {
|
|
267809
|
-
if (!
|
|
267810
|
-
throw new BadRequestError(`Instruction references unknown
|
|
268032
|
+
if (!plannedSourceEntityIds.has(instruction.sourceEntityId)) {
|
|
268033
|
+
throw new BadRequestError(`Instruction references unknown sourceEntityId '${instruction.sourceEntityId}'`);
|
|
267811
268034
|
}
|
|
267812
268035
|
if (instruction.realization === "SNAPSHOT") {
|
|
267813
268036
|
throw new BadRequestError("realization=SNAPSHOT is not supported (COPY only)");
|
|
@@ -267817,20 +268040,22 @@ class MaterializationService {
|
|
|
267817
268040
|
async executeInstructedBuild(compiled, instructions, seedEntries, signal) {
|
|
267818
268041
|
const { graphs, sources, connectionDigests, connections } = compiled;
|
|
267819
268042
|
const bySourceID = new Map;
|
|
267820
|
-
const
|
|
268043
|
+
const bySourceEntityId = new Map;
|
|
267821
268044
|
for (const instruction of instructions) {
|
|
267822
268045
|
if (instruction.sourceID) {
|
|
267823
268046
|
bySourceID.set(instruction.sourceID, instruction);
|
|
267824
268047
|
}
|
|
267825
|
-
|
|
268048
|
+
bySourceEntityId.set(instruction.sourceEntityId, instruction);
|
|
267826
268049
|
}
|
|
267827
268050
|
const manifest = new Manifest;
|
|
267828
268051
|
const entries = {};
|
|
267829
|
-
for (const [
|
|
268052
|
+
for (const [sourceEntityId, entry] of Object.entries(seedEntries)) {
|
|
267830
268053
|
if (entry.physicalTableName) {
|
|
267831
|
-
manifest.update(
|
|
268054
|
+
manifest.update(sourceEntityId, {
|
|
268055
|
+
tableName: entry.physicalTableName
|
|
268056
|
+
});
|
|
267832
268057
|
}
|
|
267833
|
-
entries[
|
|
268058
|
+
entries[sourceEntityId] = entry;
|
|
267834
268059
|
}
|
|
267835
268060
|
for (const graph of graphs) {
|
|
267836
268061
|
const connection = connections.get(graph.connectionName);
|
|
@@ -267840,25 +268065,25 @@ class MaterializationService {
|
|
|
267840
268065
|
for (const persistSource of iterGraphSources(graph, sources)) {
|
|
267841
268066
|
if (signal.aborted)
|
|
267842
268067
|
throw new Error("Build cancelled");
|
|
267843
|
-
const
|
|
267844
|
-
const instruction = bySourceID.get(persistSource.sourceID) ??
|
|
268068
|
+
const sourceEntityId = computeSourceEntityId(persistSource, connectionDigests);
|
|
268069
|
+
const instruction = bySourceID.get(persistSource.sourceID) ?? bySourceEntityId.get(sourceEntityId);
|
|
267845
268070
|
if (!instruction)
|
|
267846
268071
|
continue;
|
|
267847
268072
|
const entry = await this.buildOneSource(persistSource, instruction, connection, connectionDigests, manifest);
|
|
267848
|
-
entries[
|
|
268073
|
+
entries[sourceEntityId] = entry;
|
|
267849
268074
|
}
|
|
267850
268075
|
}
|
|
267851
268076
|
return entries;
|
|
267852
268077
|
}
|
|
267853
268078
|
async buildOneSource(persistSource, instruction, connection, connectionDigests, manifest) {
|
|
267854
|
-
const
|
|
268079
|
+
const sourceEntityId = instruction.sourceEntityId;
|
|
267855
268080
|
const physicalTableName = instruction.physicalTableName;
|
|
267856
268081
|
const buildSQL = persistSource.getSQL({
|
|
267857
268082
|
buildManifest: manifest.buildManifest,
|
|
267858
268083
|
connectionDigests
|
|
267859
268084
|
});
|
|
267860
268085
|
const bareName = bareTableName(physicalTableName);
|
|
267861
|
-
const stagingTableName = `${physicalTableName}${stagingSuffix(
|
|
268086
|
+
const stagingTableName = `${physicalTableName}${stagingSuffix(sourceEntityId)}`;
|
|
267862
268087
|
const dialect = persistSource.dialectName;
|
|
267863
268088
|
const quotedStaging = quoteTablePath(stagingTableName, dialect);
|
|
267864
268089
|
const quotedPhysical = quoteTablePath(physicalTableName, dialect);
|
|
@@ -267881,7 +268106,7 @@ class MaterializationService {
|
|
|
267881
268106
|
}
|
|
267882
268107
|
throw err;
|
|
267883
268108
|
}
|
|
267884
|
-
manifest.update(
|
|
268109
|
+
manifest.update(sourceEntityId, { tableName: physicalTableName });
|
|
267885
268110
|
const durationMs = Math.round(performance.now() - startTime);
|
|
267886
268111
|
recordSourceBuildDuration(durationMs);
|
|
267887
268112
|
logger.info(`Built materialized source ${persistSource.name}`, {
|
|
@@ -267889,7 +268114,7 @@ class MaterializationService {
|
|
|
267889
268114
|
durationMs
|
|
267890
268115
|
});
|
|
267891
268116
|
return {
|
|
267892
|
-
|
|
268117
|
+
sourceEntityId,
|
|
267893
268118
|
sourceName: persistSource.name,
|
|
267894
268119
|
materializedTableId: instruction.materializedTableId,
|
|
267895
268120
|
physicalTableName,
|
|
@@ -267946,7 +268171,7 @@ class MaterializationService {
|
|
|
267946
268171
|
if (!connectionName || !physicalTableName) {
|
|
267947
268172
|
logger.warn("Skipping manifest entry with no connection/table", {
|
|
267948
268173
|
materializationId: m.id,
|
|
267949
|
-
|
|
268174
|
+
sourceEntityId: entry.sourceEntityId
|
|
267950
268175
|
});
|
|
267951
268176
|
continue;
|
|
267952
268177
|
}
|
|
@@ -267958,7 +268183,7 @@ class MaterializationService {
|
|
|
267958
268183
|
}
|
|
267959
268184
|
const dialect = connection.dialectName;
|
|
267960
268185
|
await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(physicalTableName, dialect)}`);
|
|
267961
|
-
await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(`${physicalTableName}${stagingSuffix(entry.
|
|
268186
|
+
await connection.runSQL(`DROP TABLE IF EXISTS ${quoteTablePath(`${physicalTableName}${stagingSuffix(entry.sourceEntityId)}`, dialect)}`);
|
|
267962
268187
|
recordDropTables("success");
|
|
267963
268188
|
logger.info("Dropped materialized table on delete", {
|
|
267964
268189
|
materializationId: m.id,
|
|
@@ -268125,6 +268350,122 @@ class PackageMemoryGovernor {
|
|
|
268125
268350
|
}
|
|
268126
268351
|
}
|
|
268127
268352
|
|
|
268353
|
+
// src/service/theme_store.ts
|
|
268354
|
+
init_logger();
|
|
268355
|
+
var THEME_ROW_ID = "default";
|
|
268356
|
+
|
|
268357
|
+
class ThemeStore {
|
|
268358
|
+
storageManager;
|
|
268359
|
+
serverRoot;
|
|
268360
|
+
mutex = new Mutex;
|
|
268361
|
+
cached;
|
|
268362
|
+
cacheLoaded = false;
|
|
268363
|
+
constructor(storageManager, serverRoot) {
|
|
268364
|
+
this.storageManager = storageManager;
|
|
268365
|
+
this.serverRoot = serverRoot;
|
|
268366
|
+
}
|
|
268367
|
+
get db() {
|
|
268368
|
+
return this.storageManager.getDuckDbConnection();
|
|
268369
|
+
}
|
|
268370
|
+
async initialize() {
|
|
268371
|
+
await this.mutex.runExclusive(async () => {
|
|
268372
|
+
await this.loadLocked();
|
|
268373
|
+
});
|
|
268374
|
+
}
|
|
268375
|
+
async get() {
|
|
268376
|
+
if (!this.cacheLoaded) {
|
|
268377
|
+
await this.initialize();
|
|
268378
|
+
}
|
|
268379
|
+
return this.cached;
|
|
268380
|
+
}
|
|
268381
|
+
async set(theme) {
|
|
268382
|
+
return this.mutex.runExclusive(async () => {
|
|
268383
|
+
await this.persistLocked(theme);
|
|
268384
|
+
this.cached = theme;
|
|
268385
|
+
this.cacheLoaded = true;
|
|
268386
|
+
return theme;
|
|
268387
|
+
});
|
|
268388
|
+
}
|
|
268389
|
+
async reset() {
|
|
268390
|
+
return this.mutex.runExclusive(async () => {
|
|
268391
|
+
await this.db.run("DELETE FROM themes WHERE id = ?", [THEME_ROW_ID]);
|
|
268392
|
+
this.cached = undefined;
|
|
268393
|
+
this.cacheLoaded = false;
|
|
268394
|
+
await this.loadLocked();
|
|
268395
|
+
return this.cached;
|
|
268396
|
+
});
|
|
268397
|
+
}
|
|
268398
|
+
async loadLocked() {
|
|
268399
|
+
if (this.cacheLoaded)
|
|
268400
|
+
return;
|
|
268401
|
+
const row = await this.db.get("SELECT payload FROM themes WHERE id = ?", [THEME_ROW_ID]);
|
|
268402
|
+
if (row?.payload) {
|
|
268403
|
+
let parsed;
|
|
268404
|
+
try {
|
|
268405
|
+
parsed = JSON.parse(row.payload);
|
|
268406
|
+
} catch (error) {
|
|
268407
|
+
logger.warn("ThemeStore: stored theme JSON is unparseable; treating as empty", { error });
|
|
268408
|
+
this.cacheLoaded = true;
|
|
268409
|
+
return;
|
|
268410
|
+
}
|
|
268411
|
+
const sanitized = sanitizeTheme(parsed, "ThemeStore.load");
|
|
268412
|
+
const dropped = listSanitizerDrops(parsed, sanitized);
|
|
268413
|
+
if (dropped.length > 0) {
|
|
268414
|
+
logger.info("ThemeStore: dropped stale fields from stored theme during load", { dropped });
|
|
268415
|
+
}
|
|
268416
|
+
this.cached = sanitized;
|
|
268417
|
+
} else {
|
|
268418
|
+
const seed = getInstanceTheme(this.serverRoot);
|
|
268419
|
+
if (seed) {
|
|
268420
|
+
this.cached = seed;
|
|
268421
|
+
await this.persistLocked(seed);
|
|
268422
|
+
}
|
|
268423
|
+
}
|
|
268424
|
+
this.cacheLoaded = true;
|
|
268425
|
+
}
|
|
268426
|
+
async persistLocked(theme) {
|
|
268427
|
+
const now = new Date().toISOString();
|
|
268428
|
+
const payload = JSON.stringify(theme);
|
|
268429
|
+
await this.db.run(`INSERT INTO themes (id, payload, created_at, updated_at)
|
|
268430
|
+
VALUES (?, ?, ?, ?)
|
|
268431
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
268432
|
+
payload = EXCLUDED.payload,
|
|
268433
|
+
updated_at = EXCLUDED.updated_at`, [THEME_ROW_ID, payload, now, now]);
|
|
268434
|
+
}
|
|
268435
|
+
}
|
|
268436
|
+
function listSanitizerDrops(raw, sanitized) {
|
|
268437
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
268438
|
+
return [];
|
|
268439
|
+
const r = raw;
|
|
268440
|
+
const drops = [];
|
|
268441
|
+
const topKeys = ["defaultMode", "allowUserToggle"];
|
|
268442
|
+
for (const k of topKeys) {
|
|
268443
|
+
if (r[k] !== undefined && sanitized?.[k] === undefined)
|
|
268444
|
+
drops.push(k);
|
|
268445
|
+
}
|
|
268446
|
+
if (r.palette && typeof r.palette === "object" && !Array.isArray(r.palette)) {
|
|
268447
|
+
const rp = r.palette;
|
|
268448
|
+
const sp = sanitized?.palette ?? {};
|
|
268449
|
+
for (const k of Object.keys(rp)) {
|
|
268450
|
+
if (rp[k] !== undefined && sp[k] === undefined)
|
|
268451
|
+
drops.push(`palette.${k}`);
|
|
268452
|
+
}
|
|
268453
|
+
} else if (r.palette !== undefined && (!sanitized || sanitized.palette === undefined)) {
|
|
268454
|
+
drops.push("palette");
|
|
268455
|
+
}
|
|
268456
|
+
if (r.font && typeof r.font === "object" && !Array.isArray(r.font)) {
|
|
268457
|
+
const rf = r.font;
|
|
268458
|
+
const sf = sanitized?.font ?? {};
|
|
268459
|
+
for (const k of Object.keys(rf)) {
|
|
268460
|
+
if (rf[k] !== undefined && sf[k] === undefined)
|
|
268461
|
+
drops.push(`font.${k}`);
|
|
268462
|
+
}
|
|
268463
|
+
} else if (r.font !== undefined && (!sanitized || sanitized.font === undefined)) {
|
|
268464
|
+
drops.push("font");
|
|
268465
|
+
}
|
|
268466
|
+
return drops;
|
|
268467
|
+
}
|
|
268468
|
+
|
|
268128
268469
|
// src/server.ts
|
|
268129
268470
|
function parseArgs() {
|
|
268130
268471
|
const args = process.argv.slice(2);
|
|
@@ -268219,6 +268560,8 @@ var queryController = new QueryController(environmentStore);
|
|
|
268219
268560
|
var compileController = new CompileController(environmentStore);
|
|
268220
268561
|
var materializationService = new MaterializationService(environmentStore);
|
|
268221
268562
|
var materializationController = new MaterializationController(materializationService);
|
|
268563
|
+
var themeStore = new ThemeStore(environmentStore.storageManager, SERVER_ROOT);
|
|
268564
|
+
var themeController = new ThemeController(themeStore, SERVER_ROOT);
|
|
268222
268565
|
var mcpApp = import_express2.default();
|
|
268223
268566
|
registerHealthEndpoints(mcpApp);
|
|
268224
268567
|
mcpApp.use(MCP_ENDPOINT, import_express2.default.json());
|
|
@@ -268492,13 +268835,44 @@ app.get(`${API_PREFIX2}/environments/:environmentName/packages/:packageName/page
|
|
|
268492
268835
|
app.get(`${API_PREFIX2}/status`, async (_req, res) => {
|
|
268493
268836
|
try {
|
|
268494
268837
|
const status = await environmentStore.getStatus();
|
|
268495
|
-
|
|
268838
|
+
const theme = environmentStore.storageManager.isInitialized() ? await themeStore.get() : undefined;
|
|
268839
|
+
res.status(200).json({ ...status, theme: theme ?? {} });
|
|
268496
268840
|
} catch (error) {
|
|
268497
268841
|
logger.error("Error getting status", { error });
|
|
268498
268842
|
const { json, status } = internalErrorToHttpError(error);
|
|
268499
268843
|
res.status(status).json(json);
|
|
268500
268844
|
}
|
|
268501
268845
|
});
|
|
268846
|
+
app.get(`${API_PREFIX2}/theme`, async (_req, res) => {
|
|
268847
|
+
try {
|
|
268848
|
+
if (!environmentStore.storageManager.isInitialized()) {
|
|
268849
|
+
throw new ServiceUnavailableError("Theme storage is still initializing. Retry shortly.");
|
|
268850
|
+
}
|
|
268851
|
+
res.status(200).json(await themeController.getTheme());
|
|
268852
|
+
} catch (error) {
|
|
268853
|
+
logger.error("Error getting theme", { error });
|
|
268854
|
+
const { json, status } = internalErrorToHttpError(error);
|
|
268855
|
+
res.status(status).json(json);
|
|
268856
|
+
}
|
|
268857
|
+
});
|
|
268858
|
+
app.put(`${API_PREFIX2}/theme`, async (req, res) => {
|
|
268859
|
+
try {
|
|
268860
|
+
res.status(200).json(await themeController.putTheme(req.body));
|
|
268861
|
+
} catch (error) {
|
|
268862
|
+
logger.error("Error saving theme", { error });
|
|
268863
|
+
const { json, status } = internalErrorToHttpError(error);
|
|
268864
|
+
res.status(status).json(json);
|
|
268865
|
+
}
|
|
268866
|
+
});
|
|
268867
|
+
app.delete(`${API_PREFIX2}/theme`, async (_req, res) => {
|
|
268868
|
+
try {
|
|
268869
|
+
res.status(200).json(await themeController.resetTheme());
|
|
268870
|
+
} catch (error) {
|
|
268871
|
+
logger.error("Error resetting theme", { error });
|
|
268872
|
+
const { json, status } = internalErrorToHttpError(error);
|
|
268873
|
+
res.status(status).json(json);
|
|
268874
|
+
}
|
|
268875
|
+
});
|
|
268502
268876
|
app.get(`${API_PREFIX2}/watch-mode/status`, watchModeController.getWatchStatus);
|
|
268503
268877
|
app.post(`${API_PREFIX2}/watch-mode/start`, watchModeController.startWatching);
|
|
268504
268878
|
app.post(`${API_PREFIX2}/watch-mode/stop`, watchModeController.stopWatchMode);
|