@malloy-publisher/server 0.0.204 → 0.0.205
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/build.ts +10 -1
- package/dist/app/api-doc.yaml +133 -4
- package/dist/app/assets/{EnvironmentPage-CX06cjOF.js → EnvironmentPage-CAge6UHD.js} +1 -1
- package/dist/app/assets/HomePage-DhTe8qpa.js +1 -0
- package/dist/app/assets/{MainPage-nUJ9YatG.js → MainPage-CeTxxGex.js} +2 -2
- package/dist/app/assets/MaterializationsPage-CpDHB70t.js +1 -0
- package/dist/app/assets/ModelPage-D9sSMb75.js +1 -0
- package/dist/app/assets/{PackagePage-BaEVdEAG.js → PackagePage-LRqQWrFY.js} +1 -1
- package/dist/app/assets/{RouteError-BShQjZio.js → RouteError-xT6kuCNw.js} +1 -1
- package/dist/app/assets/{WorkbookPage-CBn6ZjJW.js → WorkbookPage-DsIh9svZ.js} +1 -1
- package/dist/app/assets/{core-DECXYL4E.es-OaRfXwuQ.js → core-C2sQrwVu.es-Bjem0hym.js} +1 -1
- package/dist/app/assets/{index-BLfPC1gy.js → index-BdOZDcce.js} +1 -1
- package/dist/app/assets/{index-Dy3YhAZQ.js → index-DHHAcY5o.js} +1 -1
- package/dist/app/assets/{index-DqiJ0bWp.js → index-RX3QOTde.js} +121 -121
- package/dist/app/assets/{index.umd-DAN9K8yC.js → index.umd-D2WH3D-f.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/runtime/publisher.js +318 -0
- package/dist/server.mjs +567 -194
- package/package.json +5 -4
- package/scripts/bake-duckdb-extensions.js +104 -0
- package/src/controller/watch-mode.controller.ts +176 -46
- package/src/errors.spec.ts +21 -0
- package/src/mcp/error_messages.spec.ts +35 -0
- package/src/mcp/error_messages.ts +14 -1
- package/src/mcp/handler_utils.ts +12 -0
- package/src/runtime/publisher.js +318 -0
- package/src/server.ts +479 -2
- package/src/service/authorize_integration.spec.ts +96 -2
- package/src/service/compile_authorize.spec.ts +85 -0
- package/src/service/environment.ts +63 -5
- package/src/service/environment_store.ts +142 -11
- package/src/service/model.ts +44 -0
- package/src/service/package.ts +17 -6
- package/src/storage/duckdb/DuckDBConnection.ts +70 -124
- package/tests/fixtures/authorize-compile/model.malloy +9 -0
- package/tests/fixtures/authorize-compile/publisher.json +4 -0
- package/tests/fixtures/html-pages-nopublic/model.malloy +1 -0
- package/tests/fixtures/html-pages-nopublic/publisher.json +5 -0
- package/tests/fixtures/html-pages-test/data.csv +3 -0
- package/tests/fixtures/html-pages-test/public/assets/app.css +3 -0
- package/tests/fixtures/html-pages-test/public/data.json +1 -0
- package/tests/fixtures/html-pages-test/public/index.html +9 -0
- package/tests/fixtures/html-pages-test/public/sub/page2.html +9 -0
- package/tests/fixtures/html-pages-test/publisher.json +5 -0
- package/tests/fixtures/html-pages-test/report.malloy +1 -0
- package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +92 -0
- package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +138 -0
- package/tests/integration/html_pages/html_pages.integration.spec.ts +378 -0
- package/tests/integration/watch-mode/watch_mode.integration.spec.ts +421 -0
- package/tests/unit/duckdb/attached_databases.test.ts +111 -0
- package/tests/unit/duckdb/duckdb_connection.test.ts +181 -0
- package/tests/unit/duckdb/repositories.test.ts +208 -0
- package/dist/app/assets/HomePage-CNFt_eUU.js +0 -1
- package/dist/app/assets/MaterializationsPage-B5goxVXW.js +0 -1
- package/dist/app/assets/ModelPage-Ba7Xh4lL.js +0 -1
package/dist/server.mjs
CHANGED
|
@@ -200925,8 +200925,8 @@ var require_uri_all = __commonJS((exports, module) => {
|
|
|
200925
200925
|
wsComponents.secure = undefined;
|
|
200926
200926
|
}
|
|
200927
200927
|
if (wsComponents.resourceName) {
|
|
200928
|
-
var _wsComponents$resourc = wsComponents.resourceName.split("?"), _wsComponents$resourc2 = slicedToArray(_wsComponents$resourc, 2),
|
|
200929
|
-
wsComponents.path =
|
|
200928
|
+
var _wsComponents$resourc = wsComponents.resourceName.split("?"), _wsComponents$resourc2 = slicedToArray(_wsComponents$resourc, 2), path11 = _wsComponents$resourc2[0], query = _wsComponents$resourc2[1];
|
|
200929
|
+
wsComponents.path = path11 && path11 !== "/" ? path11 : undefined;
|
|
200930
200930
|
wsComponents.query = query;
|
|
200931
200931
|
wsComponents.resourceName = undefined;
|
|
200932
200932
|
}
|
|
@@ -201319,12 +201319,12 @@ var require_util12 = __commonJS((exports, module) => {
|
|
|
201319
201319
|
return "'" + escapeQuotes(str) + "'";
|
|
201320
201320
|
}
|
|
201321
201321
|
function getPathExpr(currentPath, expr, jsonPointers, isNumber2) {
|
|
201322
|
-
var
|
|
201323
|
-
return joinPaths(currentPath,
|
|
201322
|
+
var path11 = jsonPointers ? "'/' + " + expr + (isNumber2 ? "" : ".replace(/~/g, '~0').replace(/\\//g, '~1')") : isNumber2 ? "'[' + " + expr + " + ']'" : "'[\\'' + " + expr + " + '\\']'";
|
|
201323
|
+
return joinPaths(currentPath, path11);
|
|
201324
201324
|
}
|
|
201325
201325
|
function getPath(currentPath, prop, jsonPointers) {
|
|
201326
|
-
var
|
|
201327
|
-
return joinPaths(currentPath,
|
|
201326
|
+
var path11 = jsonPointers ? toQuotedString("/" + escapeJsonPointer(prop)) : toQuotedString(getProperty(prop));
|
|
201327
|
+
return joinPaths(currentPath, path11);
|
|
201328
201328
|
}
|
|
201329
201329
|
var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/;
|
|
201330
201330
|
var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
|
|
@@ -207378,11 +207378,11 @@ var require_ast = __commonJS((exports, module) => {
|
|
|
207378
207378
|
helperExpression: function helperExpression(node) {
|
|
207379
207379
|
return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
|
|
207380
207380
|
},
|
|
207381
|
-
scopedId: function scopedId(
|
|
207382
|
-
return /^\.|this\b/.test(
|
|
207381
|
+
scopedId: function scopedId(path11) {
|
|
207382
|
+
return /^\.|this\b/.test(path11.original);
|
|
207383
207383
|
},
|
|
207384
|
-
simpleId: function simpleId(
|
|
207385
|
-
return
|
|
207384
|
+
simpleId: function simpleId(path11) {
|
|
207385
|
+
return path11.parts.length === 1 && !AST.helpers.scopedId(path11) && !path11.depth;
|
|
207386
207386
|
}
|
|
207387
207387
|
}
|
|
207388
207388
|
};
|
|
@@ -208442,12 +208442,12 @@ var require_helpers3 = __commonJS((exports) => {
|
|
|
208442
208442
|
loc
|
|
208443
208443
|
};
|
|
208444
208444
|
}
|
|
208445
|
-
function prepareMustache(
|
|
208445
|
+
function prepareMustache(path11, params, hash, open2, strip, locInfo) {
|
|
208446
208446
|
var escapeFlag = open2.charAt(3) || open2.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
|
|
208447
208447
|
var decorator = /\*/.test(open2);
|
|
208448
208448
|
return {
|
|
208449
208449
|
type: decorator ? "Decorator" : "MustacheStatement",
|
|
208450
|
-
path:
|
|
208450
|
+
path: path11,
|
|
208451
208451
|
params,
|
|
208452
208452
|
hash,
|
|
208453
208453
|
escaped,
|
|
@@ -208711,9 +208711,9 @@ var require_compiler = __commonJS((exports) => {
|
|
|
208711
208711
|
},
|
|
208712
208712
|
DecoratorBlock: function DecoratorBlock(decorator) {
|
|
208713
208713
|
var program = decorator.program && this.compileProgram(decorator.program);
|
|
208714
|
-
var params = this.setupFullMustacheParams(decorator, program, undefined),
|
|
208714
|
+
var params = this.setupFullMustacheParams(decorator, program, undefined), path11 = decorator.path;
|
|
208715
208715
|
this.useDecorators = true;
|
|
208716
|
-
this.opcode("registerDecorator", params.length,
|
|
208716
|
+
this.opcode("registerDecorator", params.length, path11.original);
|
|
208717
208717
|
},
|
|
208718
208718
|
PartialStatement: function PartialStatement(partial) {
|
|
208719
208719
|
this.usePartial = true;
|
|
@@ -208776,46 +208776,46 @@ var require_compiler = __commonJS((exports) => {
|
|
|
208776
208776
|
}
|
|
208777
208777
|
},
|
|
208778
208778
|
ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
|
|
208779
|
-
var
|
|
208780
|
-
this.opcode("getContext",
|
|
208779
|
+
var path11 = sexpr.path, name = path11.parts[0], isBlock = program != null || inverse != null;
|
|
208780
|
+
this.opcode("getContext", path11.depth);
|
|
208781
208781
|
this.opcode("pushProgram", program);
|
|
208782
208782
|
this.opcode("pushProgram", inverse);
|
|
208783
|
-
|
|
208784
|
-
this.accept(
|
|
208783
|
+
path11.strict = true;
|
|
208784
|
+
this.accept(path11);
|
|
208785
208785
|
this.opcode("invokeAmbiguous", name, isBlock);
|
|
208786
208786
|
},
|
|
208787
208787
|
simpleSexpr: function simpleSexpr(sexpr) {
|
|
208788
|
-
var
|
|
208789
|
-
|
|
208790
|
-
this.accept(
|
|
208788
|
+
var path11 = sexpr.path;
|
|
208789
|
+
path11.strict = true;
|
|
208790
|
+
this.accept(path11);
|
|
208791
208791
|
this.opcode("resolvePossibleLambda");
|
|
208792
208792
|
},
|
|
208793
208793
|
helperSexpr: function helperSexpr(sexpr, program, inverse) {
|
|
208794
|
-
var params = this.setupFullMustacheParams(sexpr, program, inverse),
|
|
208794
|
+
var params = this.setupFullMustacheParams(sexpr, program, inverse), path11 = sexpr.path, name = path11.parts[0];
|
|
208795
208795
|
if (this.options.knownHelpers[name]) {
|
|
208796
208796
|
this.opcode("invokeKnownHelper", params.length, name);
|
|
208797
208797
|
} else if (this.options.knownHelpersOnly) {
|
|
208798
208798
|
throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
|
|
208799
208799
|
} else {
|
|
208800
|
-
|
|
208801
|
-
|
|
208802
|
-
this.accept(
|
|
208803
|
-
this.opcode("invokeHelper", params.length,
|
|
208800
|
+
path11.strict = true;
|
|
208801
|
+
path11.falsy = true;
|
|
208802
|
+
this.accept(path11);
|
|
208803
|
+
this.opcode("invokeHelper", params.length, path11.original, _ast2["default"].helpers.simpleId(path11));
|
|
208804
208804
|
}
|
|
208805
208805
|
},
|
|
208806
|
-
PathExpression: function PathExpression(
|
|
208807
|
-
this.addDepth(
|
|
208808
|
-
this.opcode("getContext",
|
|
208809
|
-
var name =
|
|
208806
|
+
PathExpression: function PathExpression(path11) {
|
|
208807
|
+
this.addDepth(path11.depth);
|
|
208808
|
+
this.opcode("getContext", path11.depth);
|
|
208809
|
+
var name = path11.parts[0], scoped = _ast2["default"].helpers.scopedId(path11), blockParamId = !path11.depth && !scoped && this.blockParamIndex(name);
|
|
208810
208810
|
if (blockParamId) {
|
|
208811
|
-
this.opcode("lookupBlockParam", blockParamId,
|
|
208811
|
+
this.opcode("lookupBlockParam", blockParamId, path11.parts);
|
|
208812
208812
|
} else if (!name) {
|
|
208813
208813
|
this.opcode("pushContext");
|
|
208814
|
-
} else if (
|
|
208814
|
+
} else if (path11.data) {
|
|
208815
208815
|
this.options.data = true;
|
|
208816
|
-
this.opcode("lookupData",
|
|
208816
|
+
this.opcode("lookupData", path11.depth, path11.parts, path11.strict);
|
|
208817
208817
|
} else {
|
|
208818
|
-
this.opcode("lookupOnContext",
|
|
208818
|
+
this.opcode("lookupOnContext", path11.parts, path11.falsy, path11.strict, scoped);
|
|
208819
208819
|
}
|
|
208820
208820
|
},
|
|
208821
208821
|
StringLiteral: function StringLiteral(string2) {
|
|
@@ -209159,16 +209159,16 @@ var require_util13 = __commonJS((exports) => {
|
|
|
209159
209159
|
}
|
|
209160
209160
|
exports.urlGenerate = urlGenerate;
|
|
209161
209161
|
function normalize2(aPath) {
|
|
209162
|
-
var
|
|
209162
|
+
var path11 = aPath;
|
|
209163
209163
|
var url2 = urlParse(aPath);
|
|
209164
209164
|
if (url2) {
|
|
209165
209165
|
if (!url2.path) {
|
|
209166
209166
|
return aPath;
|
|
209167
209167
|
}
|
|
209168
|
-
|
|
209168
|
+
path11 = url2.path;
|
|
209169
209169
|
}
|
|
209170
|
-
var isAbsolute4 = exports.isAbsolute(
|
|
209171
|
-
var parts =
|
|
209170
|
+
var isAbsolute4 = exports.isAbsolute(path11);
|
|
209171
|
+
var parts = path11.split(/\/+/);
|
|
209172
209172
|
for (var part, up = 0, i = parts.length - 1;i >= 0; i--) {
|
|
209173
209173
|
part = parts[i];
|
|
209174
209174
|
if (part === ".") {
|
|
@@ -209185,15 +209185,15 @@ var require_util13 = __commonJS((exports) => {
|
|
|
209185
209185
|
}
|
|
209186
209186
|
}
|
|
209187
209187
|
}
|
|
209188
|
-
|
|
209189
|
-
if (
|
|
209190
|
-
|
|
209188
|
+
path11 = parts.join("/");
|
|
209189
|
+
if (path11 === "") {
|
|
209190
|
+
path11 = isAbsolute4 ? "/" : ".";
|
|
209191
209191
|
}
|
|
209192
209192
|
if (url2) {
|
|
209193
|
-
url2.path =
|
|
209193
|
+
url2.path = path11;
|
|
209194
209194
|
return urlGenerate(url2);
|
|
209195
209195
|
}
|
|
209196
|
-
return
|
|
209196
|
+
return path11;
|
|
209197
209197
|
}
|
|
209198
209198
|
exports.normalize = normalize2;
|
|
209199
209199
|
function join9(aRoot, aPath) {
|
|
@@ -211750,8 +211750,8 @@ var require_printer = __commonJS((exports) => {
|
|
|
211750
211750
|
return this.accept(sexpr.path) + " " + params + hash;
|
|
211751
211751
|
};
|
|
211752
211752
|
PrintVisitor.prototype.PathExpression = function(id) {
|
|
211753
|
-
var
|
|
211754
|
-
return (id.data ? "@" : "") + "PATH:" +
|
|
211753
|
+
var path11 = id.parts.join("/");
|
|
211754
|
+
return (id.data ? "@" : "") + "PATH:" + path11;
|
|
211755
211755
|
};
|
|
211756
211756
|
PrintVisitor.prototype.StringLiteral = function(string2) {
|
|
211757
211757
|
return '"' + string2.value + '"';
|
|
@@ -216953,7 +216953,7 @@ var import_cors = __toESM(require_lib7(), 1);
|
|
|
216953
216953
|
var import_express = __toESM(require_express(), 1);
|
|
216954
216954
|
var import_http_proxy_middleware = __toESM(require_dist4(), 1);
|
|
216955
216955
|
import * as http2 from "http";
|
|
216956
|
-
import * as
|
|
216956
|
+
import * as path11 from "path";
|
|
216957
216957
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
216958
216958
|
|
|
216959
216959
|
// src/controller/compile.controller.ts
|
|
@@ -225341,6 +225341,8 @@ var esm_default = { watch, FSWatcher };
|
|
|
225341
225341
|
// src/controller/watch-mode.controller.ts
|
|
225342
225342
|
init_errors();
|
|
225343
225343
|
init_logger();
|
|
225344
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
225345
|
+
import path10 from "path";
|
|
225344
225346
|
|
|
225345
225347
|
// src/service/environment_store.ts
|
|
225346
225348
|
var import_client_s32 = __toESM(require_dist_cjs75(), 1);
|
|
@@ -229670,11 +229672,13 @@ init_errors();
|
|
|
229670
229672
|
init_logger();
|
|
229671
229673
|
|
|
229672
229674
|
// src/storage/duckdb/DuckDBConnection.ts
|
|
229673
|
-
import
|
|
229675
|
+
import {
|
|
229676
|
+
DuckDBInstance
|
|
229677
|
+
} from "@duckdb/node-api";
|
|
229674
229678
|
import * as path4 from "path";
|
|
229675
229679
|
|
|
229676
229680
|
class DuckDBConnection2 {
|
|
229677
|
-
|
|
229681
|
+
instance = null;
|
|
229678
229682
|
connection = null;
|
|
229679
229683
|
dbPath;
|
|
229680
229684
|
mutex = new Mutex;
|
|
@@ -229682,68 +229686,42 @@ class DuckDBConnection2 {
|
|
|
229682
229686
|
this.dbPath = dbPath || path4.join(process.cwd(), "publisher.db");
|
|
229683
229687
|
}
|
|
229684
229688
|
async initialize() {
|
|
229685
|
-
|
|
229686
|
-
this.
|
|
229687
|
-
|
|
229688
|
-
|
|
229689
|
-
|
|
229690
|
-
|
|
229691
|
-
|
|
229692
|
-
|
|
229693
|
-
|
|
229694
|
-
reject(new Error("Failed to create connection object"));
|
|
229695
|
-
return;
|
|
229696
|
-
}
|
|
229697
|
-
this.connection.all("SELECT 42 as answer", (testErr, _rows) => {
|
|
229698
|
-
if (testErr) {
|
|
229699
|
-
console.error("Connection test failed:", testErr);
|
|
229700
|
-
reject(new Error(`Failed to verify DuckDB connection: ${testErr.message}`));
|
|
229701
|
-
return;
|
|
229702
|
-
}
|
|
229703
|
-
resolve4();
|
|
229704
|
-
});
|
|
229705
|
-
});
|
|
229706
|
-
});
|
|
229689
|
+
try {
|
|
229690
|
+
this.instance = await DuckDBInstance.create(this.dbPath);
|
|
229691
|
+
this.connection = await this.instance.connect();
|
|
229692
|
+
await this.connection.run("SELECT 42 as answer");
|
|
229693
|
+
} catch (err) {
|
|
229694
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
229695
|
+
console.error("Failed to create DuckDB database:", err);
|
|
229696
|
+
throw new Error(`Failed to initialize DuckDB: ${message}`);
|
|
229697
|
+
}
|
|
229707
229698
|
}
|
|
229708
229699
|
async close() {
|
|
229709
|
-
|
|
229700
|
+
try {
|
|
229710
229701
|
if (this.connection) {
|
|
229711
|
-
this.connection.
|
|
229712
|
-
|
|
229713
|
-
reject(new Error(`Failed to close DuckDB connection: ${err.message}`));
|
|
229714
|
-
return;
|
|
229715
|
-
}
|
|
229716
|
-
if (this.db) {
|
|
229717
|
-
this.db.close((dbErr) => {
|
|
229718
|
-
if (dbErr) {
|
|
229719
|
-
reject(new Error(`Failed to close DuckDB: ${dbErr.message}`));
|
|
229720
|
-
return;
|
|
229721
|
-
}
|
|
229722
|
-
console.log("DuckDB connection closed");
|
|
229723
|
-
resolve4();
|
|
229724
|
-
});
|
|
229725
|
-
} else {
|
|
229726
|
-
resolve4();
|
|
229727
|
-
}
|
|
229728
|
-
});
|
|
229729
|
-
} else {
|
|
229730
|
-
resolve4();
|
|
229702
|
+
this.connection.closeSync();
|
|
229703
|
+
this.connection = null;
|
|
229731
229704
|
}
|
|
229732
|
-
|
|
229705
|
+
if (this.instance) {
|
|
229706
|
+
this.instance.closeSync();
|
|
229707
|
+
this.instance = null;
|
|
229708
|
+
}
|
|
229709
|
+
console.log("DuckDB connection closed");
|
|
229710
|
+
} catch (err) {
|
|
229711
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
229712
|
+
throw new Error(`Failed to close DuckDB connection: ${message}`);
|
|
229713
|
+
}
|
|
229733
229714
|
}
|
|
229734
229715
|
async isInitialized() {
|
|
229735
229716
|
if (!this.connection)
|
|
229736
229717
|
return false;
|
|
229737
229718
|
return this.mutex.runExclusive(async () => {
|
|
229738
|
-
|
|
229739
|
-
this.connection.
|
|
229740
|
-
|
|
229741
|
-
|
|
229742
|
-
|
|
229743
|
-
|
|
229744
|
-
resolve4(rows && rows.length > 0);
|
|
229745
|
-
});
|
|
229746
|
-
});
|
|
229719
|
+
try {
|
|
229720
|
+
const reader = await this.connection.runAndReadAll("SELECT name FROM sqlite_master WHERE type='table' AND name='environments'");
|
|
229721
|
+
return reader.getRowObjectsJS().length > 0;
|
|
229722
|
+
} catch {
|
|
229723
|
+
return false;
|
|
229724
|
+
}
|
|
229747
229725
|
});
|
|
229748
229726
|
}
|
|
229749
229727
|
async run(query, params) {
|
|
@@ -229751,21 +229729,13 @@ class DuckDBConnection2 {
|
|
|
229751
229729
|
throw new Error("Database not initialized");
|
|
229752
229730
|
}
|
|
229753
229731
|
return this.mutex.runExclusive(async () => {
|
|
229754
|
-
|
|
229755
|
-
|
|
229756
|
-
|
|
229757
|
-
|
|
229758
|
-
Query: ${
|
|
229759
|
-
|
|
229760
|
-
|
|
229761
|
-
resolve4();
|
|
229762
|
-
};
|
|
229763
|
-
if (params && params.length > 0) {
|
|
229764
|
-
this.connection.run(query, ...params, callback);
|
|
229765
|
-
} else {
|
|
229766
|
-
this.connection.run(query, callback);
|
|
229767
|
-
}
|
|
229768
|
-
});
|
|
229732
|
+
try {
|
|
229733
|
+
await this.connection.run(query, params);
|
|
229734
|
+
} catch (err) {
|
|
229735
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
229736
|
+
throw new Error(`Query execution failed: ${message}
|
|
229737
|
+
Query: ${query}`);
|
|
229738
|
+
}
|
|
229769
229739
|
});
|
|
229770
229740
|
}
|
|
229771
229741
|
async all(query, params) {
|
|
@@ -229773,33 +229743,20 @@ Query: ${query}`));
|
|
|
229773
229743
|
throw new Error("Database not initialized");
|
|
229774
229744
|
}
|
|
229775
229745
|
return this.mutex.runExclusive(async () => {
|
|
229776
|
-
|
|
229777
|
-
const
|
|
229778
|
-
|
|
229779
|
-
|
|
229780
|
-
|
|
229781
|
-
|
|
229782
|
-
|
|
229783
|
-
|
|
229784
|
-
};
|
|
229785
|
-
if (params && params.length > 0) {
|
|
229786
|
-
this.connection.all(query, ...params, callback);
|
|
229787
|
-
} else {
|
|
229788
|
-
this.connection.all(query, callback);
|
|
229789
|
-
}
|
|
229790
|
-
});
|
|
229746
|
+
try {
|
|
229747
|
+
const reader = await this.connection.runAndReadAll(query, params);
|
|
229748
|
+
return reader.getRowObjectsJS();
|
|
229749
|
+
} catch (err) {
|
|
229750
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
229751
|
+
throw new Error(`Query execution failed: ${message}
|
|
229752
|
+
Query: ${query}`);
|
|
229753
|
+
}
|
|
229791
229754
|
});
|
|
229792
229755
|
}
|
|
229793
229756
|
async get(query, params) {
|
|
229794
229757
|
const rows = await this.all(query, params);
|
|
229795
229758
|
return rows.length > 0 ? rows[0] : null;
|
|
229796
229759
|
}
|
|
229797
|
-
getConnection() {
|
|
229798
|
-
if (!this.connection) {
|
|
229799
|
-
throw new Error("Database not initialized");
|
|
229800
|
-
}
|
|
229801
|
-
return this.connection;
|
|
229802
|
-
}
|
|
229803
229760
|
}
|
|
229804
229761
|
|
|
229805
229762
|
// src/storage/duckdb/DuckDBManifestStore.ts
|
|
@@ -230869,6 +230826,7 @@ init_logger();
|
|
|
230869
230826
|
import crypto4 from "crypto";
|
|
230870
230827
|
import * as fs6 from "fs";
|
|
230871
230828
|
import * as path8 from "path";
|
|
230829
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
230872
230830
|
|
|
230873
230831
|
// src/utils.ts
|
|
230874
230832
|
import * as fs3 from "fs";
|
|
@@ -231480,6 +231438,9 @@ class Model {
|
|
|
231480
231438
|
getAuthorize(sourceName) {
|
|
231481
231439
|
return this.sources?.find((source) => source.name === sourceName)?.authorize ?? [];
|
|
231482
231440
|
}
|
|
231441
|
+
hasAuthorize() {
|
|
231442
|
+
return this.fileLevelAuthorize.length > 0 || (this.sources?.some((s) => (s.authorize?.length ?? 0) > 0) ?? false);
|
|
231443
|
+
}
|
|
231483
231444
|
effectiveAuthorizeFor(sourceName) {
|
|
231484
231445
|
if (sourceName && this.sources?.some((s) => s.name === sourceName)) {
|
|
231485
231446
|
return this.getAuthorize(sourceName);
|
|
@@ -231510,6 +231471,12 @@ class Model {
|
|
|
231510
231471
|
if (!passed)
|
|
231511
231472
|
deny();
|
|
231512
231473
|
}
|
|
231474
|
+
async assertAuthorizedForText(text, givens) {
|
|
231475
|
+
await this.assertAuthorized(this.extractSourceName(text), givens);
|
|
231476
|
+
}
|
|
231477
|
+
async assertAuthorizedForRunnable(runnable, givens) {
|
|
231478
|
+
await this.assertAuthorized(await this.resolveAuthorizeSourceFromRunnable(runnable), givens);
|
|
231479
|
+
}
|
|
231513
231480
|
async resolveAuthorizeSourceFromRunnable(runnable) {
|
|
231514
231481
|
try {
|
|
231515
231482
|
const prepared = await runnable.getPreparedQuery();
|
|
@@ -232196,11 +232163,13 @@ class Package {
|
|
|
232196
232163
|
status
|
|
232197
232164
|
});
|
|
232198
232165
|
try {
|
|
232199
|
-
await fs5.
|
|
232200
|
-
|
|
232201
|
-
|
|
232202
|
-
}
|
|
232203
|
-
|
|
232166
|
+
const stat6 = await fs5.lstat(packagePath).catch(() => null);
|
|
232167
|
+
if (stat6?.isSymbolicLink()) {
|
|
232168
|
+
logger.info(`Skipping cleanup of symlinked package path on failure: ${packagePath}`);
|
|
232169
|
+
} else {
|
|
232170
|
+
await fs5.rm(packagePath, { recursive: true, force: true });
|
|
232171
|
+
logger.info(`Cleaned up failed package directory: ${packagePath}`);
|
|
232172
|
+
}
|
|
232204
232173
|
} catch (cleanupError) {
|
|
232205
232174
|
logger.warn(`Failed to clean up package directory ${packagePath}`, {
|
|
232206
232175
|
error: cleanupError
|
|
@@ -232495,6 +232464,9 @@ class Environment {
|
|
|
232495
232464
|
environmentName;
|
|
232496
232465
|
metadata;
|
|
232497
232466
|
memoryGovernor = null;
|
|
232467
|
+
getEnvironmentPath() {
|
|
232468
|
+
return this.environmentPath;
|
|
232469
|
+
}
|
|
232498
232470
|
constructor(environmentName, environmentPath, malloyConfig, apiConnections) {
|
|
232499
232471
|
assertSafeEnvironmentPath(environmentPath);
|
|
232500
232472
|
this.environmentName = environmentName;
|
|
@@ -232579,8 +232551,8 @@ class Environment {
|
|
|
232579
232551
|
return this.withPackageLock(packageName, async () => {
|
|
232580
232552
|
const modelPath = safeJoinUnderRoot(this.environmentPath, packageName, modelName);
|
|
232581
232553
|
const modelDir = path8.dirname(modelPath);
|
|
232582
|
-
const
|
|
232583
|
-
const
|
|
232554
|
+
const virtualUrl = pathToFileURL2(path8.join(modelDir, "__compile_check.malloy"));
|
|
232555
|
+
const virtualUri = virtualUrl.toString();
|
|
232584
232556
|
let modelContent = "";
|
|
232585
232557
|
try {
|
|
232586
232558
|
modelContent = await fs6.promises.readFile(modelPath, "utf8");
|
|
@@ -232596,6 +232568,10 @@ ${source}` : source;
|
|
|
232596
232568
|
}
|
|
232597
232569
|
};
|
|
232598
232570
|
const pkg = await this._loadOrGetPackageLocked(packageName);
|
|
232571
|
+
const gateModel = pkg.getModel(modelName);
|
|
232572
|
+
if (gateModel) {
|
|
232573
|
+
await gateModel.assertAuthorizedForText(source, givens ?? {});
|
|
232574
|
+
}
|
|
232599
232575
|
const runtime = new Runtime2({
|
|
232600
232576
|
urlReader: interceptingReader,
|
|
232601
232577
|
config: pkg.getMalloyConfig()
|
|
@@ -232603,10 +232579,16 @@ ${source}` : source;
|
|
|
232603
232579
|
try {
|
|
232604
232580
|
const modelMaterializer = runtime.loadModel(virtualUrl);
|
|
232605
232581
|
const model = await modelMaterializer.getModel();
|
|
232582
|
+
let queryMaterializer = null;
|
|
232583
|
+
try {
|
|
232584
|
+
queryMaterializer = modelMaterializer.loadFinalQuery();
|
|
232585
|
+
} catch {}
|
|
232586
|
+
if (queryMaterializer && gateModel?.hasAuthorize()) {
|
|
232587
|
+
await gateModel.assertAuthorizedForRunnable(queryMaterializer, givens ?? {});
|
|
232588
|
+
}
|
|
232606
232589
|
let sql;
|
|
232607
|
-
if (includeSql) {
|
|
232590
|
+
if (includeSql && queryMaterializer) {
|
|
232608
232591
|
try {
|
|
232609
|
-
const queryMaterializer = modelMaterializer.loadFinalQuery();
|
|
232610
232592
|
sql = await queryMaterializer.getSQL({ givens });
|
|
232611
232593
|
} catch {}
|
|
232612
232594
|
}
|
|
@@ -233147,6 +233129,16 @@ function validateEnvironmentAzureUrls(environment) {
|
|
|
233147
233129
|
}
|
|
233148
233130
|
}
|
|
233149
233131
|
}
|
|
233132
|
+
async function clearMountTarget(targetPath) {
|
|
233133
|
+
try {
|
|
233134
|
+
const stats = await fs7.promises.lstat(targetPath);
|
|
233135
|
+
if (stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
233136
|
+
await fs7.promises.rm(targetPath, { recursive: true, force: true });
|
|
233137
|
+
} else {
|
|
233138
|
+
await fs7.promises.unlink(targetPath);
|
|
233139
|
+
}
|
|
233140
|
+
} catch {}
|
|
233141
|
+
}
|
|
233150
233142
|
|
|
233151
233143
|
class EnvironmentStore {
|
|
233152
233144
|
serverRootPath;
|
|
@@ -233161,9 +233153,13 @@ class EnvironmentStore {
|
|
|
233161
233153
|
});
|
|
233162
233154
|
gcsClient;
|
|
233163
233155
|
memoryGovernor = null;
|
|
233156
|
+
inPlaceEnvs = new Set;
|
|
233164
233157
|
constructor(serverRootPath) {
|
|
233165
233158
|
this.serverRootPath = serverRootPath;
|
|
233166
233159
|
this.gcsClient = new Storage;
|
|
233160
|
+
const watchEnvList = (process.env.PUBLISHER_WATCH || "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
233161
|
+
for (const envName of watchEnvList)
|
|
233162
|
+
this.inPlaceEnvs.add(envName);
|
|
233167
233163
|
const storageConfig = {
|
|
233168
233164
|
type: "duckdb",
|
|
233169
233165
|
duckdb: {
|
|
@@ -233173,6 +233169,12 @@ class EnvironmentStore {
|
|
|
233173
233169
|
this.storageManager = new StorageManager(storageConfig);
|
|
233174
233170
|
this.finishedInitialization = this.initialize();
|
|
233175
233171
|
}
|
|
233172
|
+
isInPlace(environmentName) {
|
|
233173
|
+
return this.inPlaceEnvs.has(environmentName);
|
|
233174
|
+
}
|
|
233175
|
+
markInPlace(environmentName) {
|
|
233176
|
+
this.inPlaceEnvs.add(environmentName);
|
|
233177
|
+
}
|
|
233176
233178
|
setMemoryGovernor(governor) {
|
|
233177
233179
|
this.memoryGovernor = governor;
|
|
233178
233180
|
for (const env of this.environments.values()) {
|
|
@@ -233798,21 +233800,47 @@ class EnvironmentStore {
|
|
|
233798
233800
|
}
|
|
233799
233801
|
} else {
|
|
233800
233802
|
if (this.isLocalPath(_package.location)) {
|
|
233801
|
-
sourcePath = _package.location;
|
|
233803
|
+
sourcePath = path9.isAbsolute(_package.location) ? _package.location : path9.join(this.serverRootPath, _package.location);
|
|
233802
233804
|
} else {
|
|
233803
233805
|
sourcePath = safeJoinUnderRoot(tempDownloadPath, groupedLocation);
|
|
233804
233806
|
}
|
|
233805
233807
|
}
|
|
233806
233808
|
const sourceExists = await fs7.promises.access(sourcePath).then(() => true).catch(() => false);
|
|
233807
233809
|
if (sourceExists) {
|
|
233808
|
-
|
|
233809
|
-
|
|
233810
|
-
|
|
233811
|
-
|
|
233812
|
-
|
|
233813
|
-
|
|
233814
|
-
|
|
233810
|
+
const isInPlace = this.inPlaceEnvs.has(environmentName) && this.isLocalPath(_package.location);
|
|
233811
|
+
if (isInPlace) {
|
|
233812
|
+
await clearMountTarget(absolutePackagePath);
|
|
233813
|
+
const absoluteSourcePath = path9.resolve(sourcePath);
|
|
233814
|
+
const linkType = process.platform === "win32" ? "junction" : "dir";
|
|
233815
|
+
try {
|
|
233816
|
+
await fs7.promises.symlink(absoluteSourcePath, absolutePackagePath, linkType);
|
|
233817
|
+
logger.info(`In-place mount (watch mode): linked package "${packageDir}" -> "${absoluteSourcePath}"`);
|
|
233818
|
+
} catch (linkError) {
|
|
233819
|
+
const code = linkError?.code ?? String(linkError);
|
|
233820
|
+
logger.warn(`In-place mount failed for package "${packageDir}" (${code}); falling back to a copy. Source-edit live reload is disabled for this package.`);
|
|
233821
|
+
await clearMountTarget(absolutePackagePath);
|
|
233822
|
+
await fs7.promises.mkdir(absolutePackagePath, {
|
|
233823
|
+
recursive: true
|
|
233824
|
+
});
|
|
233825
|
+
await fs7.promises.cp(sourcePath, absolutePackagePath, {
|
|
233826
|
+
recursive: true
|
|
233827
|
+
});
|
|
233828
|
+
}
|
|
233829
|
+
} else {
|
|
233830
|
+
if (this.inPlaceEnvs.has(environmentName) && !this.isLocalPath(_package.location)) {
|
|
233831
|
+
logger.warn(`Watch mode: package "${packageDir}" has remote location "${_package.location}" — falling back to copy. Source-edit live reload won't work for this package; clone the source locally and use a local-dir location to enable it.`);
|
|
233832
|
+
}
|
|
233833
|
+
await clearMountTarget(absolutePackagePath);
|
|
233834
|
+
await fs7.promises.mkdir(absolutePackagePath, {
|
|
233835
|
+
recursive: true
|
|
233836
|
+
});
|
|
233837
|
+
await fs7.promises.cp(sourcePath, absolutePackagePath, {
|
|
233838
|
+
recursive: true
|
|
233839
|
+
});
|
|
233840
|
+
logger.info(`Extracted package "${packageDir}" from ${groupedLocation.startsWith("https://github.com/") && _package.location.includes("/tree/") ? "GitHub subdirectory" : "shared download"}`);
|
|
233841
|
+
}
|
|
233815
233842
|
} else {
|
|
233843
|
+
await clearMountTarget(absolutePackagePath);
|
|
233816
233844
|
await fs7.promises.mkdir(absolutePackagePath, {
|
|
233817
233845
|
recursive: true
|
|
233818
233846
|
});
|
|
@@ -234092,15 +234120,104 @@ class EnvironmentStore {
|
|
|
234092
234120
|
}
|
|
234093
234121
|
|
|
234094
234122
|
// src/controller/watch-mode.controller.ts
|
|
234123
|
+
var ASSET_EXTS = new Set([
|
|
234124
|
+
".html",
|
|
234125
|
+
".htm",
|
|
234126
|
+
".css",
|
|
234127
|
+
".js",
|
|
234128
|
+
".mjs",
|
|
234129
|
+
".json",
|
|
234130
|
+
".png",
|
|
234131
|
+
".jpg",
|
|
234132
|
+
".jpeg",
|
|
234133
|
+
".gif",
|
|
234134
|
+
".svg",
|
|
234135
|
+
".webp",
|
|
234136
|
+
".ico",
|
|
234137
|
+
".woff",
|
|
234138
|
+
".woff2"
|
|
234139
|
+
]);
|
|
234140
|
+
var MODEL_EXTS = new Set([".malloy", ".malloynb", ".md"]);
|
|
234141
|
+
|
|
234095
234142
|
class WatchModeController {
|
|
234096
234143
|
environmentStore;
|
|
234097
234144
|
watchingPath;
|
|
234098
234145
|
watchingEnvironmentName;
|
|
234099
|
-
watcher;
|
|
234146
|
+
watcher = null;
|
|
234147
|
+
setupChain = null;
|
|
234148
|
+
events = new EventEmitter4;
|
|
234100
234149
|
constructor(environmentStore) {
|
|
234101
234150
|
this.environmentStore = environmentStore;
|
|
234102
234151
|
this.watchingPath = null;
|
|
234103
234152
|
this.watchingEnvironmentName = null;
|
|
234153
|
+
this.events.setMaxListeners(100);
|
|
234154
|
+
}
|
|
234155
|
+
async ensureWatching(environmentName) {
|
|
234156
|
+
if (this.watchingEnvironmentName === environmentName && this.watcher) {
|
|
234157
|
+
return;
|
|
234158
|
+
}
|
|
234159
|
+
const run = (this.setupChain ?? Promise.resolve()).catch(() => {}).then(async () => {
|
|
234160
|
+
if (this.watchingEnvironmentName === environmentName && this.watcher) {
|
|
234161
|
+
return;
|
|
234162
|
+
}
|
|
234163
|
+
const env = await this.environmentStore.getEnvironment(environmentName, false);
|
|
234164
|
+
const watchPath = env.getEnvironmentPath();
|
|
234165
|
+
if (this.watcher) {
|
|
234166
|
+
await this.watcher.close();
|
|
234167
|
+
this.watcher = null;
|
|
234168
|
+
}
|
|
234169
|
+
this.startWatcher(environmentName, watchPath);
|
|
234170
|
+
});
|
|
234171
|
+
this.setupChain = run;
|
|
234172
|
+
await run;
|
|
234173
|
+
}
|
|
234174
|
+
isWatching(environmentName) {
|
|
234175
|
+
return !!this.watcher && this.watchingEnvironmentName === environmentName;
|
|
234176
|
+
}
|
|
234177
|
+
startWatcher(watchName, watchPath) {
|
|
234178
|
+
this.watchingEnvironmentName = watchName;
|
|
234179
|
+
this.watchingPath = watchPath;
|
|
234180
|
+
this.watcher = esm_default.watch(this.watchingPath, {
|
|
234181
|
+
ignored: (filePath, stats) => {
|
|
234182
|
+
if (!stats?.isFile())
|
|
234183
|
+
return false;
|
|
234184
|
+
const ext = path10.extname(filePath).toLowerCase();
|
|
234185
|
+
return !MODEL_EXTS.has(ext) && !ASSET_EXTS.has(ext);
|
|
234186
|
+
},
|
|
234187
|
+
ignoreInitial: true
|
|
234188
|
+
});
|
|
234189
|
+
const reloadPackage = async (pkgName) => {
|
|
234190
|
+
try {
|
|
234191
|
+
const environment = await this.environmentStore.getEnvironment(watchName, false);
|
|
234192
|
+
await environment.getPackage(pkgName, true);
|
|
234193
|
+
logger.info(`Watch: recompiled package "${pkgName}" in environment "${watchName}"`);
|
|
234194
|
+
return true;
|
|
234195
|
+
} catch (error) {
|
|
234196
|
+
logger.error(`Watch: failed to recompile package "${pkgName}" in environment "${watchName}"`, { error });
|
|
234197
|
+
return false;
|
|
234198
|
+
}
|
|
234199
|
+
};
|
|
234200
|
+
const onEvent = (kind) => async (filePath) => {
|
|
234201
|
+
logger.info(`Watch ${kind}: ${filePath}; environment=${watchName}`);
|
|
234202
|
+
const rel = path10.relative(this.watchingPath ?? "", filePath);
|
|
234203
|
+
const segments = rel.split(path10.sep);
|
|
234204
|
+
const pkgName = segments.length > 1 && segments[0] && !segments[0].startsWith("..") ? segments[0] : null;
|
|
234205
|
+
if (!pkgName)
|
|
234206
|
+
return;
|
|
234207
|
+
const ext = path10.extname(filePath).toLowerCase();
|
|
234208
|
+
if (MODEL_EXTS.has(ext)) {
|
|
234209
|
+
const recompiled = await reloadPackage(pkgName);
|
|
234210
|
+
if (!recompiled)
|
|
234211
|
+
return;
|
|
234212
|
+
}
|
|
234213
|
+
this.events.emit(`${watchName}/${pkgName}`, {
|
|
234214
|
+
path: filePath,
|
|
234215
|
+
kind
|
|
234216
|
+
});
|
|
234217
|
+
};
|
|
234218
|
+
this.watcher.on("add", onEvent("add"));
|
|
234219
|
+
this.watcher.on("change", onEvent("change"));
|
|
234220
|
+
this.watcher.on("unlink", onEvent("unlink"));
|
|
234104
234221
|
}
|
|
234105
234222
|
getWatchStatus = async (_req, res) => {
|
|
234106
234223
|
return res.json({
|
|
@@ -234120,7 +234237,6 @@ class WatchModeController {
|
|
|
234120
234237
|
return;
|
|
234121
234238
|
}
|
|
234122
234239
|
const environmentManifest = await EnvironmentStore.reloadEnvironmentManifest(this.environmentStore.serverRootPath);
|
|
234123
|
-
this.watchingEnvironmentName = watchName || null;
|
|
234124
234240
|
const environment = environmentManifest.environments.find((e) => e.name === watchName);
|
|
234125
234241
|
if (!environment || !environment.packages || environment.packages.length === 0) {
|
|
234126
234242
|
res.status(404).json({
|
|
@@ -234128,32 +234244,21 @@ class WatchModeController {
|
|
|
234128
234244
|
});
|
|
234129
234245
|
return;
|
|
234130
234246
|
}
|
|
234131
|
-
|
|
234132
|
-
|
|
234133
|
-
|
|
234134
|
-
|
|
234135
|
-
|
|
234136
|
-
|
|
234137
|
-
|
|
234138
|
-
|
|
234139
|
-
logger.info(`Reloaded environment ${watchName}`);
|
|
234140
|
-
};
|
|
234141
|
-
this.watcher.on("add", async (path10) => {
|
|
234142
|
-
logger.info(`Detected new file ${path10}, reloading environment ${watchName}`);
|
|
234143
|
-
await reloadEnvironment();
|
|
234144
|
-
});
|
|
234145
|
-
this.watcher.on("unlink", async (path10) => {
|
|
234146
|
-
logger.info(`Detected deletion of ${path10}, reloading environment ${watchName}`);
|
|
234147
|
-
await reloadEnvironment();
|
|
234148
|
-
});
|
|
234149
|
-
this.watcher.on("change", async (path10) => {
|
|
234150
|
-
logger.info(`Detected change on ${path10}, reloading environment ${watchName}`);
|
|
234151
|
-
await reloadEnvironment();
|
|
234152
|
-
});
|
|
234247
|
+
try {
|
|
234248
|
+
await this.ensureWatching(watchName);
|
|
234249
|
+
} catch (error) {
|
|
234250
|
+
logger.error(error);
|
|
234251
|
+
const { status } = internalErrorToHttpError(error);
|
|
234252
|
+
res.status(status).json({ error: error.message });
|
|
234253
|
+
return;
|
|
234254
|
+
}
|
|
234153
234255
|
res.json();
|
|
234154
234256
|
};
|
|
234155
234257
|
stopWatchMode = async (_req, res) => {
|
|
234156
|
-
this.watcher
|
|
234258
|
+
if (this.watcher) {
|
|
234259
|
+
await this.watcher.close();
|
|
234260
|
+
this.watcher = null;
|
|
234261
|
+
}
|
|
234157
234262
|
this.watchingPath = null;
|
|
234158
234263
|
this.watchingEnvironmentName = null;
|
|
234159
234264
|
res.json();
|
|
@@ -237259,7 +237364,14 @@ function getMalloyErrorDetails(operation, modelIdentifier, error) {
|
|
|
237259
237364
|
const connectionErrorMatch = error.message.match(/Cannot connect to database/i);
|
|
237260
237365
|
const fieldNotFoundMatch = error.message.match(/Field '([^']+)' not found in (source|query|view) '([^']+)'/i);
|
|
237261
237366
|
const invalidRequestMatch = error.message.match(/Invalid query request\\. Query OR queryName must be defined/i);
|
|
237262
|
-
|
|
237367
|
+
const accessDeniedMatch = error.message.match(/Access denied for source "([^"]+)"/i);
|
|
237368
|
+
if (accessDeniedMatch) {
|
|
237369
|
+
refined = true;
|
|
237370
|
+
const [, sourceName] = accessDeniedMatch;
|
|
237371
|
+
suggestions = [
|
|
237372
|
+
`Suggestion: Access to source '${sourceName}' is restricted by an #(authorize) gate. Supply the givens its authorize expression requires (e.g. a role/region given) and retry. This is an authorization denial, not a syntax error.`
|
|
237373
|
+
];
|
|
237374
|
+
} else if (viewNotFoundMatch) {
|
|
237263
237375
|
refined = true;
|
|
237264
237376
|
const [, viewName, sourceName] = viewNotFoundMatch;
|
|
237265
237377
|
suggestions.unshift(`Suggestion: View '${viewName}' was not found in source '${sourceName}'. Check the view name spelling or try requesting the resource details for the source URI (e.g., 'malloy://.../sources/${sourceName}') to see the list of available views. Views are defined within sources like 'source: ${sourceName} is ... extend { view: ${viewName} is { ... } }'.`);
|
|
@@ -237377,6 +237489,8 @@ async function getModelForQuery(environmentStore, environmentName, packageName,
|
|
|
237377
237489
|
errorDetails = getNotFoundError(`model '${modelPath}' in package '${packageName}' for environment '${environmentName}'`);
|
|
237378
237490
|
} else if (error instanceof ModelCompilationError) {
|
|
237379
237491
|
errorDetails = getMalloyErrorDetails("executeQuery (load model)", `${environmentName}/${packageName}/${modelPath}`, error);
|
|
237492
|
+
} else if (error instanceof AccessDeniedError) {
|
|
237493
|
+
errorDetails = getMalloyErrorDetails("executeQuery (load model)", `${environmentName}/${packageName}/${modelPath}`, error);
|
|
237380
237494
|
} else if (error instanceof ServiceUnavailableError) {
|
|
237381
237495
|
errorDetails = {
|
|
237382
237496
|
message: error.message,
|
|
@@ -237393,25 +237507,25 @@ async function getModelForQuery(environmentStore, environmentName, packageName,
|
|
|
237393
237507
|
}
|
|
237394
237508
|
}
|
|
237395
237509
|
function buildMalloyUri(components, fragment) {
|
|
237396
|
-
let
|
|
237510
|
+
let path11 = "/environment/";
|
|
237397
237511
|
if (components.environment) {
|
|
237398
|
-
|
|
237512
|
+
path11 += encodeURIComponent(components.environment);
|
|
237399
237513
|
} else {
|
|
237400
|
-
|
|
237514
|
+
path11 += "home";
|
|
237401
237515
|
}
|
|
237402
237516
|
if (components.package) {
|
|
237403
|
-
|
|
237517
|
+
path11 += "/package/" + encodeURIComponent(components.package);
|
|
237404
237518
|
}
|
|
237405
237519
|
if (components.resourceType) {
|
|
237406
|
-
|
|
237520
|
+
path11 += "/" + components.resourceType;
|
|
237407
237521
|
if (components.resourceName) {
|
|
237408
|
-
|
|
237522
|
+
path11 += "/" + encodeURIComponent(components.resourceName);
|
|
237409
237523
|
if (components.subResourceType && components.subResourceName) {
|
|
237410
|
-
|
|
237524
|
+
path11 += "/" + components.subResourceType + "/" + encodeURIComponent(components.subResourceName);
|
|
237411
237525
|
}
|
|
237412
237526
|
}
|
|
237413
237527
|
}
|
|
237414
|
-
let uriString = "malloy:/" +
|
|
237528
|
+
let uriString = "malloy:/" + path11;
|
|
237415
237529
|
if (fragment) {
|
|
237416
237530
|
uriString += "#" + fragment;
|
|
237417
237531
|
}
|
|
@@ -239654,6 +239768,10 @@ function parseArgs() {
|
|
|
239654
239768
|
i++;
|
|
239655
239769
|
} else if (arg === "--init") {
|
|
239656
239770
|
process.env.INITIALIZE_STORAGE = "true";
|
|
239771
|
+
} else if (arg === "--watch-env" && args[i + 1]) {
|
|
239772
|
+
const existing = process.env.PUBLISHER_WATCH || "";
|
|
239773
|
+
process.env.PUBLISHER_WATCH = existing ? `${existing},${args[i + 1]}` : args[i + 1];
|
|
239774
|
+
i++;
|
|
239657
239775
|
} else if (arg === "--help" || arg === "-h") {
|
|
239658
239776
|
console.log("Malloy Publisher Server");
|
|
239659
239777
|
console.log("");
|
|
@@ -239668,6 +239786,11 @@ function parseArgs() {
|
|
|
239668
239786
|
console.log(" --shutdown_drain_duration_seconds <number> Time in seconds to keep service in draining state before closing servers (default: 0)");
|
|
239669
239787
|
console.log(" --shutdown_graceful_close_timeout_seconds <number> Time in seconds to wait after closing servers before exit (default: 0)");
|
|
239670
239788
|
console.log(" --init Initialize the storage (default: false)");
|
|
239789
|
+
console.log(" --watch-env <name> Enable dev-mode watch for the named environment.");
|
|
239790
|
+
console.log(" Mounts local-dir packages in-place (symlink, not");
|
|
239791
|
+
console.log(" copy) so source-edit live reload works. A comma-");
|
|
239792
|
+
console.log(" separated PUBLISHER_WATCH mounts all listed envs in");
|
|
239793
|
+
console.log(" place, but only the first one auto-reloads.");
|
|
239671
239794
|
console.log(" --help, -h Show this help message");
|
|
239672
239795
|
process.exit(0);
|
|
239673
239796
|
}
|
|
@@ -239684,8 +239807,8 @@ var MCP_ENDPOINT = "/mcp";
|
|
|
239684
239807
|
var SHUTDOWN_DRAIN_DURATION_SECONDS = Number(process.env.SHUTDOWN_DRAIN_DURATION_SECONDS || 0);
|
|
239685
239808
|
var SHUTDOWN_GRACEFUL_CLOSE_TIMEOUT_SECONDS = Number(process.env.SHUTDOWN_GRACEFUL_CLOSE_TIMEOUT_SECONDS || 0);
|
|
239686
239809
|
var __filename_esm = fileURLToPath4(import.meta.url);
|
|
239687
|
-
var ROOT =
|
|
239688
|
-
var SERVER_ROOT =
|
|
239810
|
+
var ROOT = path11.join(path11.dirname(__filename_esm), "app");
|
|
239811
|
+
var SERVER_ROOT = path11.resolve(process.cwd(), process.env.SERVER_ROOT || ".");
|
|
239689
239812
|
var API_PREFIX2 = "/api/v0";
|
|
239690
239813
|
var isDevelopment = process.env["NODE_ENV"] === "development";
|
|
239691
239814
|
var app = import_express.default();
|
|
@@ -239769,16 +239892,164 @@ mcpApp.all(MCP_ENDPOINT, async (req, res) => {
|
|
|
239769
239892
|
}
|
|
239770
239893
|
}
|
|
239771
239894
|
});
|
|
239895
|
+
var PUBLISHER_RUNTIME_PATH = path11.join(path11.dirname(__filename_esm), "runtime", "publisher.js");
|
|
239896
|
+
app.get("/sdk/publisher.js", (_req, res) => {
|
|
239897
|
+
res.type("application/javascript");
|
|
239898
|
+
res.setHeader("cache-control", "public, max-age=60");
|
|
239899
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
239900
|
+
res.sendFile(PUBLISHER_RUNTIME_PATH, (err) => {
|
|
239901
|
+
if (err) {
|
|
239902
|
+
logger.error("Failed to send publisher.js runtime", { error: err });
|
|
239903
|
+
if (!res.headersSent)
|
|
239904
|
+
res.status(500).end();
|
|
239905
|
+
}
|
|
239906
|
+
});
|
|
239907
|
+
});
|
|
239908
|
+
async function serveFromPackage(req, res) {
|
|
239909
|
+
const subPathRaw = req.params["0"] ?? "";
|
|
239910
|
+
try {
|
|
239911
|
+
const environment = await environmentStore.getEnvironment(req.params.environmentName, false);
|
|
239912
|
+
const pkg = await environment.getPackage(req.params.packageName, false);
|
|
239913
|
+
const publicRoot = path11.join(pkg.getPackagePath(), "public");
|
|
239914
|
+
let subPath = subPathRaw;
|
|
239915
|
+
if (subPath === "" || subPath.endsWith("/")) {
|
|
239916
|
+
subPath = subPath + "index.html";
|
|
239917
|
+
}
|
|
239918
|
+
const fullPath = safeJoinUnderRoot(publicRoot, subPath);
|
|
239919
|
+
const fsp = await import("fs/promises");
|
|
239920
|
+
let realPublicRoot;
|
|
239921
|
+
let realFullPath;
|
|
239922
|
+
try {
|
|
239923
|
+
realPublicRoot = await fsp.realpath(publicRoot);
|
|
239924
|
+
realFullPath = await fsp.realpath(fullPath);
|
|
239925
|
+
} catch {
|
|
239926
|
+
if (!res.headersSent) {
|
|
239927
|
+
res.status(404).end();
|
|
239928
|
+
}
|
|
239929
|
+
return;
|
|
239930
|
+
}
|
|
239931
|
+
const rel = path11.relative(realPublicRoot, realFullPath);
|
|
239932
|
+
if (rel.startsWith("..") || path11.isAbsolute(rel)) {
|
|
239933
|
+
res.status(403).end();
|
|
239934
|
+
return;
|
|
239935
|
+
}
|
|
239936
|
+
const ext = path11.extname(realFullPath).toLowerCase();
|
|
239937
|
+
if (ext === ".html" || ext === ".htm") {
|
|
239938
|
+
const frameAncestors = process.env.PUBLISHER_FRAME_ANCESTORS || "*";
|
|
239939
|
+
res.setHeader("Content-Security-Policy", `frame-ancestors ${frameAncestors}`);
|
|
239940
|
+
res.removeHeader("X-Frame-Options");
|
|
239941
|
+
}
|
|
239942
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
239943
|
+
res.sendFile(realFullPath, (err) => {
|
|
239944
|
+
if (err) {
|
|
239945
|
+
if (!res.headersSent) {
|
|
239946
|
+
res.status(404).end();
|
|
239947
|
+
}
|
|
239948
|
+
}
|
|
239949
|
+
});
|
|
239950
|
+
} catch (e) {
|
|
239951
|
+
if (!res.headersSent) {
|
|
239952
|
+
const { json, status } = internalErrorToHttpError(e);
|
|
239953
|
+
res.status(status).json(json);
|
|
239954
|
+
}
|
|
239955
|
+
}
|
|
239956
|
+
}
|
|
239957
|
+
app.get("/environments/:environmentName/packages/:packageName", (req, res, next) => {
|
|
239958
|
+
if (req.path.endsWith("/"))
|
|
239959
|
+
return next();
|
|
239960
|
+
const canonical = `/environments/${encodeURIComponent(req.params.environmentName)}/packages/${encodeURIComponent(req.params.packageName)}/`;
|
|
239961
|
+
const query = new URLSearchParams;
|
|
239962
|
+
for (const [key, value] of Object.entries(req.query)) {
|
|
239963
|
+
if (Array.isArray(value)) {
|
|
239964
|
+
for (const v of value)
|
|
239965
|
+
query.append(key, String(v));
|
|
239966
|
+
} else if (value !== undefined) {
|
|
239967
|
+
query.append(key, String(value));
|
|
239968
|
+
}
|
|
239969
|
+
}
|
|
239970
|
+
const qs = query.toString();
|
|
239971
|
+
res.redirect(308, qs ? `${canonical}?${qs}` : canonical);
|
|
239972
|
+
});
|
|
239973
|
+
app.get("/environments/:environmentName/packages/:packageName/*", serveFromPackage);
|
|
239974
|
+
var PAGES_DEPTH_CAP = 3;
|
|
239975
|
+
async function listPackagePages(environmentName, packageName, publicRoot) {
|
|
239976
|
+
const fs8 = await import("fs/promises");
|
|
239977
|
+
const out = [];
|
|
239978
|
+
let realPublicRoot;
|
|
239979
|
+
try {
|
|
239980
|
+
realPublicRoot = await fs8.realpath(publicRoot);
|
|
239981
|
+
} catch {
|
|
239982
|
+
return out;
|
|
239983
|
+
}
|
|
239984
|
+
async function walk(dir, depth) {
|
|
239985
|
+
if (depth > PAGES_DEPTH_CAP)
|
|
239986
|
+
return;
|
|
239987
|
+
let entries;
|
|
239988
|
+
try {
|
|
239989
|
+
entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
239990
|
+
} catch {
|
|
239991
|
+
return;
|
|
239992
|
+
}
|
|
239993
|
+
for (const entry of entries) {
|
|
239994
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
239995
|
+
continue;
|
|
239996
|
+
const full = path11.join(dir, entry.name);
|
|
239997
|
+
let realFull;
|
|
239998
|
+
try {
|
|
239999
|
+
realFull = await fs8.realpath(full);
|
|
240000
|
+
} catch {
|
|
240001
|
+
continue;
|
|
240002
|
+
}
|
|
240003
|
+
const contained = path11.relative(realPublicRoot, realFull);
|
|
240004
|
+
if (contained.startsWith("..") || path11.isAbsolute(contained))
|
|
240005
|
+
continue;
|
|
240006
|
+
if (entry.isDirectory()) {
|
|
240007
|
+
await walk(full, depth + 1);
|
|
240008
|
+
} else if (entry.isFile() && (entry.name.endsWith(".html") || entry.name.endsWith(".htm"))) {
|
|
240009
|
+
const rel = path11.relative(publicRoot, full).replace(/\\/g, "/");
|
|
240010
|
+
let title = rel;
|
|
240011
|
+
try {
|
|
240012
|
+
const fh = await fs8.open(full, "r");
|
|
240013
|
+
try {
|
|
240014
|
+
const buf = Buffer.alloc(4096);
|
|
240015
|
+
const { bytesRead } = await fh.read(buf, 0, 4096, 0);
|
|
240016
|
+
const head = buf.slice(0, bytesRead).toString("utf8");
|
|
240017
|
+
const m = head.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
240018
|
+
if (m)
|
|
240019
|
+
title = m[1].trim();
|
|
240020
|
+
} finally {
|
|
240021
|
+
await fh.close();
|
|
240022
|
+
}
|
|
240023
|
+
} catch {}
|
|
240024
|
+
out.push({
|
|
240025
|
+
resource: `/environments/${environmentName}/packages/${packageName}/${rel}`,
|
|
240026
|
+
packageName,
|
|
240027
|
+
path: rel,
|
|
240028
|
+
title
|
|
240029
|
+
});
|
|
240030
|
+
}
|
|
240031
|
+
}
|
|
240032
|
+
}
|
|
240033
|
+
await walk(publicRoot, 0);
|
|
240034
|
+
out.sort((a, b) => {
|
|
240035
|
+
if (a.path === "index.html")
|
|
240036
|
+
return -1;
|
|
240037
|
+
if (b.path === "index.html")
|
|
240038
|
+
return 1;
|
|
240039
|
+
return a.path.localeCompare(b.path);
|
|
240040
|
+
});
|
|
240041
|
+
return out;
|
|
240042
|
+
}
|
|
239772
240043
|
if (!isDevelopment) {
|
|
239773
240044
|
app.use("/", import_express.default.static(ROOT));
|
|
239774
|
-
app.use("/api-doc.html", import_express.default.static(
|
|
240045
|
+
app.use("/api-doc.html", import_express.default.static(path11.join(ROOT, "api-doc.html")));
|
|
239775
240046
|
} else {
|
|
239776
240047
|
app.use(`${API_PREFIX2}`, loggerMiddleware);
|
|
239777
240048
|
app.use(import_http_proxy_middleware.createProxyMiddleware({
|
|
239778
240049
|
target: "http://localhost:5173",
|
|
239779
240050
|
changeOrigin: true,
|
|
239780
240051
|
ws: true,
|
|
239781
|
-
pathFilter: (
|
|
240052
|
+
pathFilter: (path12) => !path12.startsWith("/api/") && !path12.startsWith("/metrics") && !path12.startsWith("/health")
|
|
239782
240053
|
}));
|
|
239783
240054
|
}
|
|
239784
240055
|
var setVersionIdError2 = (res) => {
|
|
@@ -239799,6 +240070,18 @@ try {
|
|
|
239799
240070
|
logger.warn("Failed to register Prometheus metrics endpoint", { error });
|
|
239800
240071
|
}
|
|
239801
240072
|
app.use(drainingGuard);
|
|
240073
|
+
app.get(`${API_PREFIX2}/environments/:environmentName/packages/:packageName/pages`, async (req, res) => {
|
|
240074
|
+
try {
|
|
240075
|
+
const environment = await environmentStore.getEnvironment(req.params.environmentName, false);
|
|
240076
|
+
const pkg = await environment.getPackage(req.params.packageName, false);
|
|
240077
|
+
const pages = await listPackagePages(req.params.environmentName, req.params.packageName, path11.join(pkg.getPackagePath(), "public"));
|
|
240078
|
+
res.json(pages);
|
|
240079
|
+
} catch (error) {
|
|
240080
|
+
logger.error("Failed to list package pages", { error });
|
|
240081
|
+
const { json, status } = internalErrorToHttpError(error);
|
|
240082
|
+
res.status(status).json(json);
|
|
240083
|
+
}
|
|
240084
|
+
});
|
|
239802
240085
|
app.get(`${API_PREFIX2}/status`, async (_req, res) => {
|
|
239803
240086
|
try {
|
|
239804
240087
|
const status = await environmentStore.getStatus();
|
|
@@ -239812,6 +240095,65 @@ app.get(`${API_PREFIX2}/status`, async (_req, res) => {
|
|
|
239812
240095
|
app.get(`${API_PREFIX2}/watch-mode/status`, watchModeController.getWatchStatus);
|
|
239813
240096
|
app.post(`${API_PREFIX2}/watch-mode/start`, watchModeController.startWatching);
|
|
239814
240097
|
app.post(`${API_PREFIX2}/watch-mode/stop`, watchModeController.stopWatchMode);
|
|
240098
|
+
var MAX_SSE_CONNECTIONS = 1000;
|
|
240099
|
+
var sseConnectionCount = 0;
|
|
240100
|
+
app.get(`${API_PREFIX2}/environments/:environmentName/packages/:packageName/events`, async (req, res) => {
|
|
240101
|
+
const env = req.params.environmentName;
|
|
240102
|
+
const pkg = req.params.packageName;
|
|
240103
|
+
try {
|
|
240104
|
+
assertSafePackageName(env);
|
|
240105
|
+
assertSafePackageName(pkg);
|
|
240106
|
+
const environment = await environmentStore.getEnvironment(env, false);
|
|
240107
|
+
await environment.getPackage(pkg, false);
|
|
240108
|
+
} catch (error) {
|
|
240109
|
+
const { json, status } = internalErrorToHttpError(error);
|
|
240110
|
+
res.status(status).json(json);
|
|
240111
|
+
return;
|
|
240112
|
+
}
|
|
240113
|
+
if (sseConnectionCount >= MAX_SSE_CONNECTIONS) {
|
|
240114
|
+
res.status(503).json({
|
|
240115
|
+
code: 503,
|
|
240116
|
+
message: "Too many live-reload connections; try again shortly."
|
|
240117
|
+
});
|
|
240118
|
+
return;
|
|
240119
|
+
}
|
|
240120
|
+
sseConnectionCount++;
|
|
240121
|
+
res.set({
|
|
240122
|
+
"content-type": "text/event-stream",
|
|
240123
|
+
"cache-control": "no-cache",
|
|
240124
|
+
connection: "keep-alive",
|
|
240125
|
+
"x-accel-buffering": "no"
|
|
240126
|
+
});
|
|
240127
|
+
res.flushHeaders();
|
|
240128
|
+
const watching = watchModeController.isWatching(env);
|
|
240129
|
+
res.write(`event: hello
|
|
240130
|
+
data: connected
|
|
240131
|
+
|
|
240132
|
+
`);
|
|
240133
|
+
res.write(`event: mode
|
|
240134
|
+
data: ${watching ? "enabled" : "disabled"}
|
|
240135
|
+
|
|
240136
|
+
`);
|
|
240137
|
+
const key = `${env}/${pkg}`;
|
|
240138
|
+
const send = () => {
|
|
240139
|
+
res.write(`event: changed
|
|
240140
|
+
data: changed
|
|
240141
|
+
|
|
240142
|
+
`);
|
|
240143
|
+
};
|
|
240144
|
+
watchModeController.events.on(key, send);
|
|
240145
|
+
const heartbeat = setInterval(() => {
|
|
240146
|
+
res.write(`: heartbeat
|
|
240147
|
+
|
|
240148
|
+
`);
|
|
240149
|
+
}, 25000);
|
|
240150
|
+
const cleanup = () => {
|
|
240151
|
+
clearInterval(heartbeat);
|
|
240152
|
+
watchModeController.events.off(key, send);
|
|
240153
|
+
sseConnectionCount--;
|
|
240154
|
+
};
|
|
240155
|
+
req.on("close", cleanup);
|
|
240156
|
+
});
|
|
239815
240157
|
app.get(`${API_PREFIX2}/environments`, async (_req, res) => {
|
|
239816
240158
|
try {
|
|
239817
240159
|
res.status(200).json(await environmentStore.listEnvironments());
|
|
@@ -240381,7 +240723,24 @@ registerLegacyRoutes(app, {
|
|
|
240381
240723
|
manifestController
|
|
240382
240724
|
});
|
|
240383
240725
|
if (!isDevelopment) {
|
|
240384
|
-
|
|
240726
|
+
const SPA_INDEX = path11.resolve(ROOT, "index.html");
|
|
240727
|
+
app.get("*", (req, res) => {
|
|
240728
|
+
res.sendFile(SPA_INDEX, (err) => {
|
|
240729
|
+
if (!err)
|
|
240730
|
+
return;
|
|
240731
|
+
if (res.headersSent)
|
|
240732
|
+
return;
|
|
240733
|
+
res.status(404).type("text/html").send(`<!doctype html><meta charset="utf-8">
|
|
240734
|
+
<title>Publisher</title>
|
|
240735
|
+
<style>body{font:14px/1.4 -apple-system,system-ui,sans-serif;margin:40px;max-width:720px;color:#222}</style>
|
|
240736
|
+
<h1>Publisher is running, but the SPA bundle isn't built.</h1>
|
|
240737
|
+
<p>You requested <code>${req.path.replace(/[<>&]/g, (c) => ({ "<": "<", ">": ">", "&": "&" })[c] ?? c)}</code>.
|
|
240738
|
+
The Publisher API is available at <a href="/api/v0/environments">/api/v0/environments</a>.</p>
|
|
240739
|
+
<p>To get the Publisher web UI, run <code>cd packages/app && bunx vite build</code>
|
|
240740
|
+
or start the server with <code>NODE_ENV=development</code> after launching Vite on <code>:5173</code>.</p>
|
|
240741
|
+
<p>For in-package HTML data apps, browse to <code>/environments/<env>/packages/<pkg>/<file></code> directly.</p>`);
|
|
240742
|
+
});
|
|
240743
|
+
});
|
|
240385
240744
|
}
|
|
240386
240745
|
app.use((err, _req, res, _next) => {
|
|
240387
240746
|
logger.error("Unhandled error:", err);
|
|
@@ -240396,12 +240755,26 @@ var mainServer = http2.createServer({ maxHeaderSize: 262144 }, app);
|
|
|
240396
240755
|
mainServer.timeout = 600000;
|
|
240397
240756
|
mainServer.keepAliveTimeout = 600000;
|
|
240398
240757
|
mainServer.headersTimeout = 600000;
|
|
240399
|
-
mainServer.listen(PUBLISHER_PORT, PUBLISHER_HOST, () => {
|
|
240758
|
+
mainServer.listen(PUBLISHER_PORT, PUBLISHER_HOST, async () => {
|
|
240400
240759
|
const address = mainServer.address();
|
|
240401
240760
|
logger.info(`Publisher server listening at http://${address.address}:${address.port}`);
|
|
240402
240761
|
if (isDevelopment) {
|
|
240403
240762
|
logger.info("Running in development mode - proxying to React dev server at http://localhost:5173");
|
|
240404
240763
|
}
|
|
240764
|
+
const watchEnvList = (process.env.PUBLISHER_WATCH || "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
240765
|
+
if (watchEnvList.length > 0) {
|
|
240766
|
+
if (watchEnvList.length > 1) {
|
|
240767
|
+
logger.warn(`Multiple watch environments requested (${watchEnvList.join(", ")}); watch mode auto-reloads one at a time. Watching "${watchEnvList[0]}". The others are mounted in place (their source is live) but will not auto-reload. Pass a single --watch-env (or one PUBLISHER_WATCH value) to silence this.`);
|
|
240768
|
+
}
|
|
240769
|
+
const envName = watchEnvList[0];
|
|
240770
|
+
try {
|
|
240771
|
+
await environmentStore.finishedInitialization;
|
|
240772
|
+
await watchModeController.ensureWatching(envName);
|
|
240773
|
+
logger.info(`Watch mode active for environment "${envName}" (in-place mount, source-edit live reload).`);
|
|
240774
|
+
} catch (error) {
|
|
240775
|
+
logger.error(`Failed to start watch mode for environment "${envName}"`, { error });
|
|
240776
|
+
}
|
|
240777
|
+
}
|
|
240405
240778
|
});
|
|
240406
240779
|
var mcpServer = mcpApp.listen(MCP_PORT, PUBLISHER_HOST, () => {
|
|
240407
240780
|
logger.info(`MCP server listening at http://${PUBLISHER_HOST}:${MCP_PORT}`);
|