@malloy-publisher/server 0.0.90 → 0.0.92
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/.prettierignore +1 -0
- package/build.ts +9 -0
- package/dist/app/api-doc.yaml +3 -0
- package/dist/app/assets/{index-2BGzlUQa.js → index-DYO_URL-.js} +100 -100
- package/dist/app/assets/{index-D1X7Y0Ve.js → index-Dg-zTLb3.js} +1 -1
- package/dist/app/assets/{index.es49-D9XPPIF9.js → index.es50-CDMydA2o.js} +1 -1
- package/dist/app/assets/mui-UpaxdnvH.js +159 -0
- package/dist/app/index.html +2 -2
- package/dist/server.js +307 -98
- package/eslint.config.mjs +8 -0
- package/package.json +2 -1
- package/publisher.config.json +25 -5
- package/src/config.ts +25 -2
- package/src/constants.ts +1 -1
- package/src/controller/package.controller.ts +1 -0
- package/src/controller/watch-mode.controller.ts +19 -4
- package/src/data_styles.ts +10 -3
- package/src/mcp/prompts/index.ts +9 -1
- package/src/mcp/resources/package_resource.ts +2 -1
- package/src/mcp/resources/source_resource.ts +1 -0
- package/src/mcp/resources/view_resource.ts +1 -0
- package/src/server.ts +9 -5
- package/src/service/connection.ts +17 -4
- package/src/service/db_utils.ts +19 -11
- package/src/service/model.ts +2 -0
- package/src/service/package.spec.ts +76 -54
- package/src/service/project.ts +160 -45
- package/src/service/project_store.spec.ts +477 -165
- package/src/service/project_store.ts +319 -69
- package/src/service/scheduler.ts +3 -2
- package/src/utils.ts +0 -1
- package/tests/harness/e2e.ts +60 -58
- package/tests/harness/uris.ts +21 -24
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +10 -0
- package/dist/app/assets/mui-YektUyEU.js +0 -161
package/dist/server.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
var __create = Object.create;
|
|
2
3
|
var __getProtoOf = Object.getPrototypeOf;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
@@ -231302,7 +231303,7 @@ var ROW_LIMIT = 1000;
|
|
|
231302
231303
|
var publisherPath = "/etc/publisher";
|
|
231303
231304
|
try {
|
|
231304
231305
|
import_fs.default.accessSync(publisherPath, import_fs.default.constants.W_OK);
|
|
231305
|
-
} catch
|
|
231306
|
+
} catch {
|
|
231306
231307
|
publisherPath = "/tmp/publisher";
|
|
231307
231308
|
}
|
|
231308
231309
|
|
|
@@ -231562,13 +231563,16 @@ async function getSchemasForConnection(connection) {
|
|
|
231562
231563
|
const projectId = connection.bigqueryConnection.defaultProjectId;
|
|
231563
231564
|
const options = projectId ? { projectId } : {};
|
|
231564
231565
|
const [datasets] = await bigquery.getDatasets(options);
|
|
231565
|
-
|
|
231566
|
+
const schemas = await Promise.all(datasets.filter((dataset) => dataset.id).map(async (dataset) => {
|
|
231567
|
+
const [metadata] = await dataset.getMetadata();
|
|
231566
231568
|
return {
|
|
231567
231569
|
name: dataset.id,
|
|
231568
231570
|
isHidden: false,
|
|
231569
|
-
isDefault: false
|
|
231571
|
+
isDefault: false,
|
|
231572
|
+
description: metadata?.description
|
|
231570
231573
|
};
|
|
231571
|
-
});
|
|
231574
|
+
}));
|
|
231575
|
+
return schemas;
|
|
231572
231576
|
} catch (error) {
|
|
231573
231577
|
console.error(`Error getting schemas for BigQuery connection ${connection.name}:`, error);
|
|
231574
231578
|
throw new Error(`Failed to get schemas for BigQuery connection ${connection.name}: ${error.message}`);
|
|
@@ -231714,7 +231718,7 @@ async function getSnowflakeSchemas(connection) {
|
|
|
231714
231718
|
return new Promise((resolve, reject) => {
|
|
231715
231719
|
connection.execute({
|
|
231716
231720
|
sqlText: "SHOW SCHEMAS",
|
|
231717
|
-
complete: (err,
|
|
231721
|
+
complete: (err, _stmt, rows) => {
|
|
231718
231722
|
if (err) {
|
|
231719
231723
|
reject(err);
|
|
231720
231724
|
} else {
|
|
@@ -231962,7 +231966,7 @@ class PackageController {
|
|
|
231962
231966
|
if (packageLocation.endsWith(".zip")) {
|
|
231963
231967
|
packageLocation = await this.projectStore.unzipProject(packageLocation);
|
|
231964
231968
|
}
|
|
231965
|
-
await this.projectStore.mountLocalDirectory(packageLocation, absoluteTargetPath, projectName);
|
|
231969
|
+
await this.projectStore.mountLocalDirectory(packageLocation, absoluteTargetPath, projectName, packageName);
|
|
231966
231970
|
}
|
|
231967
231971
|
}
|
|
231968
231972
|
}
|
|
@@ -233579,9 +233583,9 @@ var import_path4 = __toESM(require("path"));
|
|
|
233579
233583
|
// src/service/project_store.ts
|
|
233580
233584
|
var import_client_s3 = __toESM(require_dist_cjs72());
|
|
233581
233585
|
var import_storage = require("@google-cloud/storage");
|
|
233586
|
+
var import_adm_zip = __toESM(require_adm_zip());
|
|
233582
233587
|
var fs9 = __toESM(require("fs"));
|
|
233583
233588
|
var path7 = __toESM(require("path"));
|
|
233584
|
-
var import_adm_zip = __toESM(require_adm_zip());
|
|
233585
233589
|
|
|
233586
233590
|
// ../../node_modules/simple-git/dist/esm/index.js
|
|
233587
233591
|
var import_node_buffer = require("node:buffer");
|
|
@@ -237571,7 +237575,11 @@ var import_fs5 = __toESM(require("fs"));
|
|
|
237571
237575
|
var import_path2 = __toESM(require("path"));
|
|
237572
237576
|
var getPublisherConfig = (serverRoot) => {
|
|
237573
237577
|
const publisherConfigPath = import_path2.default.join(serverRoot, PUBLISHER_CONFIG_NAME);
|
|
237574
|
-
|
|
237578
|
+
const rawConfig = JSON.parse(import_fs5.default.readFileSync(publisherConfigPath, "utf8"));
|
|
237579
|
+
if (rawConfig.projects && typeof rawConfig.projects === "object" && !Array.isArray(rawConfig.projects)) {
|
|
237580
|
+
throw new Error("Config has changed. Please update your config to the new format.");
|
|
237581
|
+
}
|
|
237582
|
+
return rawConfig;
|
|
237575
237583
|
};
|
|
237576
237584
|
var isPublisherConfigFrozen = (serverRoot) => {
|
|
237577
237585
|
const publisherConfig = getPublisherConfig(serverRoot);
|
|
@@ -237811,13 +237819,20 @@ async function readConnectionConfig(basePath) {
|
|
|
237811
237819
|
async function createConnections(basePath, defaultConnections = []) {
|
|
237812
237820
|
const connectionMap = new Map;
|
|
237813
237821
|
const connectionConfig = await readConnectionConfig(basePath);
|
|
237814
|
-
|
|
237822
|
+
const allConnections = [...defaultConnections, ...connectionConfig];
|
|
237823
|
+
const processedConnections = new Set;
|
|
237824
|
+
const apiConnections = [];
|
|
237825
|
+
for (const connection of allConnections) {
|
|
237826
|
+
if (connection.name && processedConnections.has(connection.name)) {
|
|
237827
|
+
continue;
|
|
237828
|
+
}
|
|
237815
237829
|
logger2.info(`Adding connection ${connection.name}`, {
|
|
237816
237830
|
connection
|
|
237817
237831
|
});
|
|
237818
237832
|
if (!connection.name) {
|
|
237819
237833
|
throw "Invalid connection configuration. No name.";
|
|
237820
237834
|
}
|
|
237835
|
+
processedConnections.add(connection.name);
|
|
237821
237836
|
switch (connection.type) {
|
|
237822
237837
|
case "postgres": {
|
|
237823
237838
|
const configReader = async () => {
|
|
@@ -237929,10 +237944,11 @@ async function createConnections(basePath, defaultConnections = []) {
|
|
|
237929
237944
|
throw new Error(`Unsupported connection type: ${connection.type}`);
|
|
237930
237945
|
}
|
|
237931
237946
|
}
|
|
237947
|
+
apiConnections.push(connection);
|
|
237932
237948
|
}
|
|
237933
237949
|
return {
|
|
237934
237950
|
malloyConnections: connectionMap,
|
|
237935
|
-
apiConnections
|
|
237951
|
+
apiConnections
|
|
237936
237952
|
};
|
|
237937
237953
|
}
|
|
237938
237954
|
function getConnectionAttributes(connection) {
|
|
@@ -238744,6 +238760,7 @@ class Package {
|
|
|
238744
238760
|
class Project {
|
|
238745
238761
|
packages = new Map;
|
|
238746
238762
|
packageMutexes = new Map;
|
|
238763
|
+
packageStatuses = new Map;
|
|
238747
238764
|
malloyConnections;
|
|
238748
238765
|
apiConnections;
|
|
238749
238766
|
internalConnections;
|
|
@@ -238764,28 +238781,33 @@ class Project {
|
|
|
238764
238781
|
this.reloadProjectMetadata();
|
|
238765
238782
|
}
|
|
238766
238783
|
async update(payload) {
|
|
238767
|
-
if (payload.
|
|
238768
|
-
this.
|
|
238769
|
-
this.packages.forEach((_package) => {
|
|
238770
|
-
_package.setProjectName(this.projectName);
|
|
238771
|
-
});
|
|
238772
|
-
this.metadata.name = this.projectName;
|
|
238784
|
+
if (payload.readme !== undefined) {
|
|
238785
|
+
this.metadata.readme = payload.readme;
|
|
238773
238786
|
}
|
|
238774
|
-
if (payload.
|
|
238775
|
-
|
|
238776
|
-
|
|
238777
|
-
|
|
238778
|
-
|
|
238779
|
-
this.
|
|
238787
|
+
if (payload.connections) {
|
|
238788
|
+
logger2.info(`Updating ${payload.connections.length} connections for project ${this.projectName}`);
|
|
238789
|
+
const { malloyConnections, apiConnections } = await createConnections(this.projectPath, payload.connections);
|
|
238790
|
+
this.malloyConnections = malloyConnections;
|
|
238791
|
+
this.apiConnections = apiConnections;
|
|
238792
|
+
this.internalConnections = apiConnections;
|
|
238793
|
+
logger2.info(`Successfully updated connections for project ${this.projectName}`, {
|
|
238794
|
+
malloyConnections: malloyConnections.size,
|
|
238795
|
+
apiConnections: apiConnections.length,
|
|
238796
|
+
internalConnections: apiConnections.length
|
|
238797
|
+
});
|
|
238780
238798
|
}
|
|
238781
|
-
this.metadata.readme = payload.readme;
|
|
238782
238799
|
return this;
|
|
238783
238800
|
}
|
|
238784
238801
|
static async create(projectName, projectPath, defaultConnections) {
|
|
238785
238802
|
if (!(await fs8.promises.stat(projectPath)).isDirectory()) {
|
|
238786
238803
|
throw new ProjectNotFoundError(`Project path ${projectPath} not found`);
|
|
238787
238804
|
}
|
|
238788
|
-
|
|
238805
|
+
let malloyConnections = new Map;
|
|
238806
|
+
let apiConnections = [];
|
|
238807
|
+
logger2.info(`Creating project with connection configuration`);
|
|
238808
|
+
const result = await createConnections(projectPath, defaultConnections);
|
|
238809
|
+
malloyConnections = result.malloyConnections;
|
|
238810
|
+
apiConnections = result.apiConnections;
|
|
238789
238811
|
logger2.info(`Loaded ${malloyConnections.size + apiConnections.length} connections for project ${projectName}`, {
|
|
238790
238812
|
malloyConnections,
|
|
238791
238813
|
apiConnections
|
|
@@ -238844,14 +238866,18 @@ class Project {
|
|
|
238844
238866
|
const packageDirectories = files.filter((file) => file.isDirectory() && fs8.existsSync(path6.join(this.projectPath, file.name, PACKAGE_MANIFEST_NAME)));
|
|
238845
238867
|
const packageMetadata = await Promise.all(packageDirectories.map(async (directory) => {
|
|
238846
238868
|
try {
|
|
238847
|
-
return (await this.getPackage(directory.name, false))
|
|
238869
|
+
return (this.packageStatuses.get(directory.name)?.status === "loading" /* LOADING */ ? undefined : await this.getPackage(directory.name, false))?.getPackageMetadata();
|
|
238848
238870
|
} catch (error) {
|
|
238849
238871
|
logger2.error(`Failed to load package: ${directory.name} due to : ${error}`);
|
|
238850
238872
|
return;
|
|
238851
238873
|
}
|
|
238852
238874
|
}));
|
|
238853
238875
|
const filteredMetadata = packageMetadata.filter((metadata) => metadata);
|
|
238854
|
-
|
|
238876
|
+
const finalMetadata = filteredMetadata.filter((metadata) => {
|
|
238877
|
+
const packageStatus = this.packageStatuses.get(metadata.name || "");
|
|
238878
|
+
return packageStatus?.status !== "unloading" /* UNLOADING */;
|
|
238879
|
+
});
|
|
238880
|
+
return finalMetadata;
|
|
238855
238881
|
} catch (error) {
|
|
238856
238882
|
logger2.error("Error listing packages", { error });
|
|
238857
238883
|
console.error(error);
|
|
@@ -238859,41 +238885,56 @@ class Project {
|
|
|
238859
238885
|
}
|
|
238860
238886
|
}
|
|
238861
238887
|
async getPackage(packageName, reload = false) {
|
|
238888
|
+
const _package = this.packages.get(packageName);
|
|
238889
|
+
if (_package !== undefined && !reload) {
|
|
238890
|
+
return _package;
|
|
238891
|
+
}
|
|
238862
238892
|
let packageMutex = this.packageMutexes.get(packageName);
|
|
238863
238893
|
if (packageMutex?.isLocked()) {
|
|
238864
238894
|
await packageMutex.waitForUnlock();
|
|
238865
|
-
|
|
238895
|
+
const existingPackage = this.packages.get(packageName);
|
|
238896
|
+
if (existingPackage) {
|
|
238897
|
+
return existingPackage;
|
|
238898
|
+
}
|
|
238866
238899
|
}
|
|
238867
238900
|
packageMutex = new Mutex;
|
|
238868
238901
|
this.packageMutexes.set(packageName, packageMutex);
|
|
238869
238902
|
return packageMutex.runExclusive(async () => {
|
|
238870
|
-
const
|
|
238871
|
-
if (
|
|
238872
|
-
return
|
|
238903
|
+
const existingPackage = this.packages.get(packageName);
|
|
238904
|
+
if (existingPackage !== undefined && !reload) {
|
|
238905
|
+
return existingPackage;
|
|
238873
238906
|
}
|
|
238907
|
+
this.setPackageStatus(packageName, "loading" /* LOADING */);
|
|
238874
238908
|
try {
|
|
238875
238909
|
const _package2 = await Package.create(this.projectName, packageName, path6.join(this.projectPath, packageName), this.malloyConnections);
|
|
238876
238910
|
this.packages.set(packageName, _package2);
|
|
238911
|
+
this.setPackageStatus(packageName, "serving" /* SERVING */);
|
|
238877
238912
|
return _package2;
|
|
238878
238913
|
} catch (error) {
|
|
238879
238914
|
this.packages.delete(packageName);
|
|
238915
|
+
this.packageStatuses.delete(packageName);
|
|
238880
238916
|
throw error;
|
|
238881
|
-
} finally {
|
|
238882
|
-
packageMutex.release();
|
|
238883
|
-
this.packageMutexes.delete(packageName);
|
|
238884
238917
|
}
|
|
238885
238918
|
});
|
|
238886
238919
|
}
|
|
238887
238920
|
async addPackage(packageName) {
|
|
238888
238921
|
const packagePath = path6.join(this.projectPath, packageName);
|
|
238889
|
-
if (!await fs8.promises.
|
|
238922
|
+
if (!await fs8.promises.access(packagePath).then(() => true).catch(() => false) || !(await fs8.promises.stat(packagePath)).isDirectory()) {
|
|
238890
238923
|
throw new PackageNotFoundError(`Package ${packageName} not found`);
|
|
238891
238924
|
}
|
|
238892
238925
|
logger2.info(`Adding package ${packageName} to project ${this.projectName}`, {
|
|
238893
238926
|
packagePath,
|
|
238894
238927
|
malloyConnections: this.malloyConnections
|
|
238895
238928
|
});
|
|
238896
|
-
this.
|
|
238929
|
+
this.setPackageStatus(packageName, "loading" /* LOADING */);
|
|
238930
|
+
try {
|
|
238931
|
+
this.packages.set(packageName, await Package.create(this.projectName, packageName, packagePath, this.malloyConnections));
|
|
238932
|
+
} catch (error) {
|
|
238933
|
+
logger2.error("Error adding package", { error });
|
|
238934
|
+
this.deletePackageStatus(packageName);
|
|
238935
|
+
throw error;
|
|
238936
|
+
}
|
|
238937
|
+
this.setPackageStatus(packageName, "serving" /* SERVING */);
|
|
238897
238938
|
return this.packages.get(packageName);
|
|
238898
238939
|
}
|
|
238899
238940
|
async updatePackage(packageName, body) {
|
|
@@ -238912,15 +238953,45 @@ class Project {
|
|
|
238912
238953
|
});
|
|
238913
238954
|
return _package.getPackageMetadata();
|
|
238914
238955
|
}
|
|
238956
|
+
getPackageStatus(packageName) {
|
|
238957
|
+
return this.packageStatuses.get(packageName);
|
|
238958
|
+
}
|
|
238959
|
+
setPackageStatus(packageName, status) {
|
|
238960
|
+
const currentStatus = this.packageStatuses.get(packageName);
|
|
238961
|
+
this.packageStatuses.set(packageName, {
|
|
238962
|
+
name: packageName,
|
|
238963
|
+
loadTimestamp: currentStatus?.loadTimestamp || Date.now(),
|
|
238964
|
+
status
|
|
238965
|
+
});
|
|
238966
|
+
}
|
|
238967
|
+
deletePackageStatus(packageName) {
|
|
238968
|
+
this.packageStatuses.delete(packageName);
|
|
238969
|
+
}
|
|
238915
238970
|
async deletePackage(packageName) {
|
|
238916
238971
|
const _package = this.packages.get(packageName);
|
|
238917
238972
|
if (!_package) {
|
|
238918
|
-
|
|
238973
|
+
return;
|
|
238974
|
+
}
|
|
238975
|
+
const packageStatus = this.packageStatuses.get(packageName);
|
|
238976
|
+
if (packageStatus?.status === "loading" /* LOADING */) {
|
|
238977
|
+
logger2.error("Package loading. Can't unload.", {
|
|
238978
|
+
projectName: this.projectName,
|
|
238979
|
+
packageName
|
|
238980
|
+
});
|
|
238981
|
+
throw new Error("Package loading. Can't unload. " + this.projectName + " " + packageName);
|
|
238982
|
+
} else if (packageStatus?.status === "serving" /* SERVING */) {
|
|
238983
|
+
this.setPackageStatus(packageName, "unloading" /* UNLOADING */);
|
|
238984
|
+
}
|
|
238985
|
+
try {
|
|
238986
|
+
await fs8.promises.rm(path6.join(this.projectPath, packageName), {
|
|
238987
|
+
recursive: true,
|
|
238988
|
+
force: true
|
|
238989
|
+
});
|
|
238990
|
+
} catch (err) {
|
|
238991
|
+
logger2.error("Error removing package directory while unloading package", { error: err, projectName: this.projectName, packageName });
|
|
238919
238992
|
}
|
|
238920
|
-
await fs8.promises.rm(path6.join(this.projectPath, packageName), {
|
|
238921
|
-
recursive: true
|
|
238922
|
-
});
|
|
238923
238993
|
this.packages.delete(packageName);
|
|
238994
|
+
this.packageStatuses.delete(packageName);
|
|
238924
238995
|
}
|
|
238925
238996
|
async serialize() {
|
|
238926
238997
|
return {
|
|
@@ -238937,12 +239008,14 @@ class ProjectStore {
|
|
|
238937
239008
|
projects = new Map;
|
|
238938
239009
|
publisherConfigIsFrozen;
|
|
238939
239010
|
finishedInitialization;
|
|
239011
|
+
isInitialized = false;
|
|
238940
239012
|
s3Client = new import_client_s3.S3({
|
|
238941
239013
|
followRegionRedirects: true
|
|
238942
239014
|
});
|
|
238943
|
-
gcsClient
|
|
239015
|
+
gcsClient;
|
|
238944
239016
|
constructor(serverRootPath) {
|
|
238945
239017
|
this.serverRootPath = serverRootPath;
|
|
239018
|
+
this.gcsClient = new import_storage.Storage;
|
|
238946
239019
|
this.finishedInitialization = this.initialize();
|
|
238947
239020
|
}
|
|
238948
239021
|
async initialize() {
|
|
@@ -238951,15 +239024,15 @@ class ProjectStore {
|
|
|
238951
239024
|
this.publisherConfigIsFrozen = isPublisherConfigFrozen(this.serverRootPath);
|
|
238952
239025
|
const projectManifest = await ProjectStore.reloadProjectManifest(this.serverRootPath);
|
|
238953
239026
|
logger2.info(`Initializing project store.`);
|
|
238954
|
-
await Promise.all(
|
|
238955
|
-
logger2.info(`Adding project "${
|
|
238956
|
-
const
|
|
238957
|
-
name:
|
|
238958
|
-
resource: `${API_PREFIX}/projects/${
|
|
238959
|
-
location: projectManifest.projects[projectName]
|
|
239027
|
+
await Promise.all(projectManifest.projects.map(async (project) => {
|
|
239028
|
+
logger2.info(`Adding project "${project.name}"`);
|
|
239029
|
+
const projectInstance = await this.addProject({
|
|
239030
|
+
name: project.name,
|
|
239031
|
+
resource: `${API_PREFIX}/projects/${project.name}`
|
|
238960
239032
|
}, true);
|
|
238961
|
-
return
|
|
239033
|
+
return projectInstance.listPackages();
|
|
238962
239034
|
}));
|
|
239035
|
+
this.isInitialized = true;
|
|
238963
239036
|
logger2.info(`Project store successfully initialized in ${performance.now() - initialTime}ms`);
|
|
238964
239037
|
} catch (error) {
|
|
238965
239038
|
logger2.error("Error initializing project store", { error });
|
|
@@ -238967,16 +239040,55 @@ class ProjectStore {
|
|
|
238967
239040
|
process.exit(1);
|
|
238968
239041
|
}
|
|
238969
239042
|
}
|
|
238970
|
-
async listProjects() {
|
|
238971
|
-
|
|
239043
|
+
async listProjects(skipInitializationCheck = false) {
|
|
239044
|
+
if (!skipInitializationCheck) {
|
|
239045
|
+
await this.finishedInitialization;
|
|
239046
|
+
}
|
|
238972
239047
|
return Promise.all(Array.from(this.projects.values()).map((project) => project.serialize()));
|
|
238973
239048
|
}
|
|
239049
|
+
async getStatus() {
|
|
239050
|
+
const status = {
|
|
239051
|
+
timestamp: Date.now(),
|
|
239052
|
+
projects: [],
|
|
239053
|
+
initialized: this.isInitialized
|
|
239054
|
+
};
|
|
239055
|
+
const projects = await this.listProjects(true);
|
|
239056
|
+
await Promise.all(projects.map(async (project) => {
|
|
239057
|
+
try {
|
|
239058
|
+
const packages = project.packages;
|
|
239059
|
+
const connections = project.connections;
|
|
239060
|
+
logger2.info(`Project ${project.name} status:`, {
|
|
239061
|
+
connectionsCount: project.connections?.length || 0,
|
|
239062
|
+
packagesCount: packages?.length || 0
|
|
239063
|
+
});
|
|
239064
|
+
const _connections = connections?.map((connection) => {
|
|
239065
|
+
return {
|
|
239066
|
+
...connection,
|
|
239067
|
+
attributes: undefined
|
|
239068
|
+
};
|
|
239069
|
+
});
|
|
239070
|
+
const _project = {
|
|
239071
|
+
...project,
|
|
239072
|
+
connections: _connections
|
|
239073
|
+
};
|
|
239074
|
+
project.connections = _connections;
|
|
239075
|
+
status.projects.push(_project);
|
|
239076
|
+
} catch (error) {
|
|
239077
|
+
logger2.error("Error listing packages and connections", {
|
|
239078
|
+
error
|
|
239079
|
+
});
|
|
239080
|
+
throw new Error("Error listing packages and connections: " + error);
|
|
239081
|
+
}
|
|
239082
|
+
}));
|
|
239083
|
+
return status;
|
|
239084
|
+
}
|
|
238974
239085
|
async getProject(projectName, reload = false) {
|
|
238975
239086
|
await this.finishedInitialization;
|
|
238976
239087
|
let project = this.projects.get(projectName);
|
|
238977
239088
|
if (project === undefined || reload) {
|
|
238978
239089
|
const projectManifest = await ProjectStore.reloadProjectManifest(this.serverRootPath);
|
|
238979
|
-
const
|
|
239090
|
+
const projectConfig = projectManifest.projects.find((p) => p.name === projectName);
|
|
239091
|
+
const projectPath = project?.metadata.location || projectConfig?.packages[0]?.location;
|
|
238980
239092
|
if (!projectPath) {
|
|
238981
239093
|
throw new ProjectNotFoundError(`Project "${projectName}" could not be resolved to a path.`);
|
|
238982
239094
|
}
|
|
@@ -238998,11 +239110,20 @@ class ProjectStore {
|
|
|
238998
239110
|
if (!projectName) {
|
|
238999
239111
|
throw new Error("Project name is required");
|
|
239000
239112
|
}
|
|
239113
|
+
const existingProject = this.projects.get(projectName);
|
|
239114
|
+
if (existingProject) {
|
|
239115
|
+
logger2.info(`Project ${projectName} already exists, updating it`);
|
|
239116
|
+
const updatedProject = await existingProject.update(project);
|
|
239117
|
+
this.projects.set(projectName, updatedProject);
|
|
239118
|
+
return updatedProject;
|
|
239119
|
+
}
|
|
239001
239120
|
const projectManifest = await ProjectStore.reloadProjectManifest(this.serverRootPath);
|
|
239002
|
-
const
|
|
239121
|
+
const projectConfig = projectManifest.projects.find((p) => p.name === projectName);
|
|
239122
|
+
const hasPackages = project?.packages && project.packages.length > 0 || projectConfig?.packages && projectConfig.packages.length > 0;
|
|
239003
239123
|
let absoluteProjectPath;
|
|
239004
|
-
if (
|
|
239005
|
-
|
|
239124
|
+
if (hasPackages) {
|
|
239125
|
+
const packagesToProcess = project?.packages || projectConfig?.packages || [];
|
|
239126
|
+
absoluteProjectPath = await this.loadProjectIntoDisk(projectName, projectName, packagesToProcess);
|
|
239006
239127
|
if (absoluteProjectPath.endsWith(".zip")) {
|
|
239007
239128
|
absoluteProjectPath = await this.unzipProject(absoluteProjectPath);
|
|
239008
239129
|
}
|
|
@@ -239049,7 +239170,7 @@ class ProjectStore {
|
|
|
239049
239170
|
}
|
|
239050
239171
|
const project = this.projects.get(projectName);
|
|
239051
239172
|
if (!project) {
|
|
239052
|
-
|
|
239173
|
+
return;
|
|
239053
239174
|
}
|
|
239054
239175
|
this.projects.delete(projectName);
|
|
239055
239176
|
return project;
|
|
@@ -239060,16 +239181,24 @@ class ProjectStore {
|
|
|
239060
239181
|
} catch (error) {
|
|
239061
239182
|
if (error.code !== "ENOENT") {
|
|
239062
239183
|
logger2.error(`Error reading ${PUBLISHER_CONFIG_NAME}. Generating from directory`, { error });
|
|
239063
|
-
return { projects:
|
|
239184
|
+
return { projects: [] };
|
|
239064
239185
|
} else {
|
|
239065
239186
|
try {
|
|
239066
239187
|
const entries = await fs9.promises.readdir(serverRootPath, {
|
|
239067
239188
|
withFileTypes: true
|
|
239068
239189
|
});
|
|
239069
|
-
const projects =
|
|
239190
|
+
const projects = [];
|
|
239070
239191
|
for (const entry of entries) {
|
|
239071
239192
|
if (entry.isDirectory()) {
|
|
239072
|
-
projects
|
|
239193
|
+
projects.push({
|
|
239194
|
+
name: entry.name,
|
|
239195
|
+
packages: [
|
|
239196
|
+
{
|
|
239197
|
+
name: entry.name,
|
|
239198
|
+
location: entry.name
|
|
239199
|
+
}
|
|
239200
|
+
]
|
|
239201
|
+
});
|
|
239073
239202
|
}
|
|
239074
239203
|
}
|
|
239075
239204
|
return { projects };
|
|
@@ -239077,7 +239206,7 @@ class ProjectStore {
|
|
|
239077
239206
|
logger2.error(`Error listing directories in ${serverRootPath}`, {
|
|
239078
239207
|
error: lsError
|
|
239079
239208
|
});
|
|
239080
|
-
return { projects:
|
|
239209
|
+
return { projects: [] };
|
|
239081
239210
|
}
|
|
239082
239211
|
}
|
|
239083
239212
|
}
|
|
@@ -239094,59 +239223,128 @@ class ProjectStore {
|
|
|
239094
239223
|
}
|
|
239095
239224
|
return absoluteProjectPath;
|
|
239096
239225
|
}
|
|
239097
|
-
async loadProjectIntoDisk(projectName, projectPath) {
|
|
239098
|
-
const absoluteTargetPath = `${publisherPath}/${
|
|
239099
|
-
if (
|
|
239226
|
+
async loadProjectIntoDisk(projectName, projectPath, packages) {
|
|
239227
|
+
const absoluteTargetPath = `${publisherPath}/${projectPath}`;
|
|
239228
|
+
if (!packages || packages.length === 0) {
|
|
239229
|
+
throw new PackageNotFoundError(`No packages found for project ${projectName}`);
|
|
239230
|
+
}
|
|
239231
|
+
const locationGroups = new Map;
|
|
239232
|
+
for (const _package of packages) {
|
|
239233
|
+
if (!_package.name) {
|
|
239234
|
+
throw new PackageNotFoundError(`Package has no name specified`);
|
|
239235
|
+
}
|
|
239236
|
+
if (!_package.location) {
|
|
239237
|
+
throw new PackageNotFoundError(`Package ${_package.name} has no location specified`);
|
|
239238
|
+
}
|
|
239239
|
+
const location = _package.location;
|
|
239240
|
+
const packageName = _package.name;
|
|
239241
|
+
if (!locationGroups.has(location)) {
|
|
239242
|
+
locationGroups.set(location, []);
|
|
239243
|
+
}
|
|
239244
|
+
locationGroups.get(location).push({ name: packageName, location });
|
|
239245
|
+
}
|
|
239246
|
+
for (const [location, packagesForLocation] of locationGroups) {
|
|
239247
|
+
const tempDownloadPath = `${absoluteTargetPath}/.temp_${Buffer.from(location).toString("base64").replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
239248
|
+
await fs9.promises.mkdir(tempDownloadPath, { recursive: true });
|
|
239249
|
+
try {
|
|
239250
|
+
await this.downloadOrMountLocation(location, tempDownloadPath, projectName, "shared");
|
|
239251
|
+
for (const _package of packagesForLocation) {
|
|
239252
|
+
const packageDir = _package.name;
|
|
239253
|
+
const absolutePackagePath = `${absoluteTargetPath}/${packageDir}`;
|
|
239254
|
+
const packagePathInDownload = path7.join(tempDownloadPath, packageDir);
|
|
239255
|
+
const packageExists = await fs9.promises.access(packagePathInDownload).then(() => true).catch(() => false);
|
|
239256
|
+
if (packageExists) {
|
|
239257
|
+
await fs9.promises.mkdir(absolutePackagePath, {
|
|
239258
|
+
recursive: true
|
|
239259
|
+
});
|
|
239260
|
+
await fs9.promises.cp(packagePathInDownload, absolutePackagePath, { recursive: true });
|
|
239261
|
+
logger2.info(`Extracted package "${packageDir}" from shared download`);
|
|
239262
|
+
} else {
|
|
239263
|
+
await fs9.promises.mkdir(absolutePackagePath, {
|
|
239264
|
+
recursive: true
|
|
239265
|
+
});
|
|
239266
|
+
await fs9.promises.cp(tempDownloadPath, absolutePackagePath, {
|
|
239267
|
+
recursive: true
|
|
239268
|
+
});
|
|
239269
|
+
logger2.info(`Copied entire download as package "${packageDir}"`);
|
|
239270
|
+
}
|
|
239271
|
+
}
|
|
239272
|
+
const connectionsFileInDownload = path7.join(tempDownloadPath, CONNECTIONS_MANIFEST_NAME);
|
|
239273
|
+
const connectionsFileInProject = path7.join(absoluteTargetPath, CONNECTIONS_MANIFEST_NAME);
|
|
239274
|
+
try {
|
|
239275
|
+
await fs9.promises.access(connectionsFileInDownload, fs9.constants.F_OK);
|
|
239276
|
+
await fs9.promises.cp(connectionsFileInDownload, connectionsFileInProject);
|
|
239277
|
+
logger2.info(`Copied ${CONNECTIONS_MANIFEST_NAME} to project directory`);
|
|
239278
|
+
} catch (error) {
|
|
239279
|
+
console.error(error);
|
|
239280
|
+
logger2.info(`No ${CONNECTIONS_MANIFEST_NAME} found`);
|
|
239281
|
+
}
|
|
239282
|
+
} finally {
|
|
239283
|
+
await fs9.promises.rm(tempDownloadPath, {
|
|
239284
|
+
recursive: true,
|
|
239285
|
+
force: true
|
|
239286
|
+
});
|
|
239287
|
+
}
|
|
239288
|
+
}
|
|
239289
|
+
return absoluteTargetPath;
|
|
239290
|
+
}
|
|
239291
|
+
async downloadOrMountLocation(location, targetPath, projectName, packageName) {
|
|
239292
|
+
if (location.startsWith("/")) {
|
|
239100
239293
|
try {
|
|
239101
|
-
logger2.info(`Mounting local directory at "${
|
|
239102
|
-
await this.mountLocalDirectory(
|
|
239103
|
-
return
|
|
239294
|
+
logger2.info(`Mounting local directory at "${location}" to "${targetPath}"`);
|
|
239295
|
+
await this.mountLocalDirectory(location, targetPath, projectName, packageName);
|
|
239296
|
+
return;
|
|
239104
239297
|
} catch (error) {
|
|
239105
|
-
logger2.error(`Failed to mount local directory "${
|
|
239298
|
+
logger2.error(`Failed to mount local directory "${location}"`, {
|
|
239106
239299
|
error
|
|
239107
239300
|
});
|
|
239108
|
-
throw
|
|
239301
|
+
throw new PackageNotFoundError(`Failed to mount local directory: ${location}`);
|
|
239109
239302
|
}
|
|
239110
239303
|
}
|
|
239111
|
-
if (
|
|
239304
|
+
if (location.startsWith("gs://")) {
|
|
239112
239305
|
try {
|
|
239113
|
-
logger2.info(`Downloading GCS
|
|
239114
|
-
await this.downloadGcsDirectory(
|
|
239306
|
+
logger2.info(`Downloading GCS directory from "${location}" to "${targetPath}"`);
|
|
239307
|
+
await this.downloadGcsDirectory(location, projectName, targetPath);
|
|
239308
|
+
return;
|
|
239115
239309
|
} catch (error) {
|
|
239116
|
-
logger2.error(`Failed to download GCS
|
|
239310
|
+
logger2.error(`Failed to download GCS directory "${location}"`, {
|
|
239117
239311
|
error
|
|
239118
239312
|
});
|
|
239119
|
-
throw
|
|
239313
|
+
throw new PackageNotFoundError(`Failed to download GCS directory: ${location}`);
|
|
239120
239314
|
}
|
|
239121
|
-
return absoluteTargetPath;
|
|
239122
239315
|
}
|
|
239123
|
-
if (
|
|
239316
|
+
if (location.startsWith("https://github.com/") || location.startsWith("git@")) {
|
|
239124
239317
|
try {
|
|
239125
|
-
logger2.info(`
|
|
239126
|
-
await this.
|
|
239127
|
-
return
|
|
239318
|
+
logger2.info(`Cloning GitHub repository from "${location}" to "${targetPath}"`);
|
|
239319
|
+
await this.downloadGitHubDirectory(location, targetPath);
|
|
239320
|
+
return;
|
|
239128
239321
|
} catch (error) {
|
|
239129
|
-
logger2.error(`Failed to
|
|
239130
|
-
|
|
239322
|
+
logger2.error(`Failed to clone GitHub repository "${location}"`, {
|
|
239323
|
+
error
|
|
239324
|
+
});
|
|
239325
|
+
throw new PackageNotFoundError(`Failed to clone GitHub repository: ${location}`);
|
|
239131
239326
|
}
|
|
239132
239327
|
}
|
|
239133
|
-
if (
|
|
239328
|
+
if (location.startsWith("s3://")) {
|
|
239134
239329
|
try {
|
|
239135
|
-
logger2.info(`
|
|
239136
|
-
await this.
|
|
239137
|
-
return
|
|
239330
|
+
logger2.info(`Downloading S3 directory from "${location}" to "${targetPath}"`);
|
|
239331
|
+
await this.downloadS3Directory(location, projectName, targetPath);
|
|
239332
|
+
return;
|
|
239138
239333
|
} catch (error) {
|
|
239139
|
-
logger2.error(`Failed to
|
|
239334
|
+
logger2.error(`Failed to download S3 directory "${location}"`, {
|
|
239140
239335
|
error
|
|
239141
239336
|
});
|
|
239142
|
-
throw
|
|
239337
|
+
throw new PackageNotFoundError(`Failed to download S3 directory: ${location}`);
|
|
239143
239338
|
}
|
|
239144
239339
|
}
|
|
239145
|
-
const errorMsg = `Invalid
|
|
239146
|
-
logger2.error(errorMsg, { projectName,
|
|
239147
|
-
throw new
|
|
239340
|
+
const errorMsg = `Invalid package path: "${location}". Must be an absolute mounted path or a GCS/S3/GitHub URI.`;
|
|
239341
|
+
logger2.error(errorMsg, { projectName, location });
|
|
239342
|
+
throw new PackageNotFoundError(errorMsg);
|
|
239148
239343
|
}
|
|
239149
|
-
async mountLocalDirectory(projectPath, absoluteTargetPath, projectName) {
|
|
239344
|
+
async mountLocalDirectory(projectPath, absoluteTargetPath, projectName, packageName) {
|
|
239345
|
+
if (projectPath.endsWith(".zip")) {
|
|
239346
|
+
projectPath = await this.unzipProject(projectPath);
|
|
239347
|
+
}
|
|
239150
239348
|
const projectDirExists = (await fs9.promises.stat(projectPath)).isDirectory();
|
|
239151
239349
|
if (projectDirExists) {
|
|
239152
239350
|
await fs9.promises.rm(absoluteTargetPath, {
|
|
@@ -239158,7 +239356,7 @@ class ProjectStore {
|
|
|
239158
239356
|
recursive: true
|
|
239159
239357
|
});
|
|
239160
239358
|
} else {
|
|
239161
|
-
throw new
|
|
239359
|
+
throw new PackageNotFoundError(`Package ${packageName} for project ${projectName} not found in "${projectPath}"`);
|
|
239162
239360
|
}
|
|
239163
239361
|
}
|
|
239164
239362
|
async downloadGcsDirectory(gcsPath, projectName, absoluteDirPath) {
|
|
@@ -239266,14 +239464,21 @@ class WatchModeController {
|
|
|
239266
239464
|
startWatching = async (req, res) => {
|
|
239267
239465
|
const projectManifest = await ProjectStore.reloadProjectManifest(this.projectStore.serverRootPath);
|
|
239268
239466
|
this.watchingProjectName = req.body.projectName;
|
|
239269
|
-
|
|
239467
|
+
const project = projectManifest.projects.find((p) => p.name === req.body.projectName);
|
|
239468
|
+
if (!project || !project.packages || project.packages.length === 0) {
|
|
239469
|
+
res.status(404).json({
|
|
239470
|
+
error: `Project ${req.body.projectName} not found or has no packages`
|
|
239471
|
+
});
|
|
239472
|
+
return;
|
|
239473
|
+
}
|
|
239474
|
+
this.watchingPath = import_path4.default.join(this.projectStore.serverRootPath, req.body.projectName);
|
|
239270
239475
|
this.watcher = esm_default.watch(this.watchingPath, {
|
|
239271
239476
|
ignored: (path9, stats) => !!stats?.isFile() && !path9.endsWith(".malloy") && !path9.endsWith(".md"),
|
|
239272
239477
|
ignoreInitial: true
|
|
239273
239478
|
});
|
|
239274
239479
|
const reloadProject = async () => {
|
|
239275
|
-
const
|
|
239276
|
-
await this.projectStore.addProject(
|
|
239480
|
+
const project2 = await this.projectStore.getProject(req.body.projectName, true);
|
|
239481
|
+
await this.projectStore.addProject(project2.metadata);
|
|
239277
239482
|
logger2.info(`Reloaded ${req.body.projectName}`);
|
|
239278
239483
|
};
|
|
239279
239484
|
this.watcher.on("add", async (path9) => {
|
|
@@ -242421,7 +242626,7 @@ async function handleGetPackageContents(uri, params, projectStore) {
|
|
|
242421
242626
|
}
|
|
242422
242627
|
const project = await projectStore.getProject(projectName, false);
|
|
242423
242628
|
const packageInstance = await project.getPackage(packageName, false);
|
|
242424
|
-
const entries = packageInstance.listModels();
|
|
242629
|
+
const entries = await packageInstance.listModels();
|
|
242425
242630
|
const resourceDefinitions = [];
|
|
242426
242631
|
for (const entry of entries) {
|
|
242427
242632
|
const entryPath = entry.path;
|
|
@@ -243264,10 +243469,14 @@ var setVersionIdError = (res) => {
|
|
|
243264
243469
|
app.use(import_cors.default());
|
|
243265
243470
|
app.use(bodyParser.json());
|
|
243266
243471
|
app.get(`${API_PREFIX2}/status`, async (_req, res) => {
|
|
243267
|
-
|
|
243268
|
-
|
|
243269
|
-
|
|
243270
|
-
})
|
|
243472
|
+
try {
|
|
243473
|
+
const status = await projectStore.getStatus();
|
|
243474
|
+
res.status(200).json(status);
|
|
243475
|
+
} catch (error) {
|
|
243476
|
+
logger2.error("Error getting status", { error });
|
|
243477
|
+
const { json: json2, status } = internalErrorToHttpError(error);
|
|
243478
|
+
res.status(status).json(json2);
|
|
243479
|
+
}
|
|
243271
243480
|
});
|
|
243272
243481
|
app.get(`${API_PREFIX2}/watch-mode/status`, watchModeController.getWatchStatus);
|
|
243273
243482
|
app.post(`${API_PREFIX2}/watch-mode/start`, watchModeController.startWatching);
|
|
@@ -243315,7 +243524,7 @@ app.patch(`${API_PREFIX2}/projects/:projectName`, async (req, res) => {
|
|
|
243315
243524
|
app.delete(`${API_PREFIX2}/projects/:projectName`, async (req, res) => {
|
|
243316
243525
|
try {
|
|
243317
243526
|
const project = await projectStore.deleteProject(req.params.projectName);
|
|
243318
|
-
res.status(200).json(await project
|
|
243527
|
+
res.status(200).json(await project?.serialize());
|
|
243319
243528
|
} catch (error) {
|
|
243320
243529
|
logger2.error(error);
|
|
243321
243530
|
const { json: json2, status } = internalErrorToHttpError(error);
|