@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.
Files changed (35) hide show
  1. package/.prettierignore +1 -0
  2. package/build.ts +9 -0
  3. package/dist/app/api-doc.yaml +3 -0
  4. package/dist/app/assets/{index-2BGzlUQa.js → index-DYO_URL-.js} +100 -100
  5. package/dist/app/assets/{index-D1X7Y0Ve.js → index-Dg-zTLb3.js} +1 -1
  6. package/dist/app/assets/{index.es49-D9XPPIF9.js → index.es50-CDMydA2o.js} +1 -1
  7. package/dist/app/assets/mui-UpaxdnvH.js +159 -0
  8. package/dist/app/index.html +2 -2
  9. package/dist/server.js +307 -98
  10. package/eslint.config.mjs +8 -0
  11. package/package.json +2 -1
  12. package/publisher.config.json +25 -5
  13. package/src/config.ts +25 -2
  14. package/src/constants.ts +1 -1
  15. package/src/controller/package.controller.ts +1 -0
  16. package/src/controller/watch-mode.controller.ts +19 -4
  17. package/src/data_styles.ts +10 -3
  18. package/src/mcp/prompts/index.ts +9 -1
  19. package/src/mcp/resources/package_resource.ts +2 -1
  20. package/src/mcp/resources/source_resource.ts +1 -0
  21. package/src/mcp/resources/view_resource.ts +1 -0
  22. package/src/server.ts +9 -5
  23. package/src/service/connection.ts +17 -4
  24. package/src/service/db_utils.ts +19 -11
  25. package/src/service/model.ts +2 -0
  26. package/src/service/package.spec.ts +76 -54
  27. package/src/service/project.ts +160 -45
  28. package/src/service/project_store.spec.ts +477 -165
  29. package/src/service/project_store.ts +319 -69
  30. package/src/service/scheduler.ts +3 -2
  31. package/src/utils.ts +0 -1
  32. package/tests/harness/e2e.ts +60 -58
  33. package/tests/harness/uris.ts +21 -24
  34. package/tests/integration/mcp/mcp_resource.integration.spec.ts +10 -0
  35. 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 (e) {
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
- return datasets.filter((dataset) => dataset.id).map((dataset) => {
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, stmt, rows) => {
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
- return JSON.parse(import_fs5.default.readFileSync(publisherConfigPath, "utf8"));
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
- for (const connection of [...defaultConnections, ...connectionConfig]) {
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: connectionConfig
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.name) {
238768
- this.projectName = payload.name;
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.resource) {
238775
- this.projectPath = payload.resource.replace(`${API_PREFIX}/projects/`, "");
238776
- if (!await fs8.promises.exists(this.projectPath)) {
238777
- throw new ProjectNotFoundError(`Project path "${this.projectPath}" not found`);
238778
- }
238779
- this.metadata.resource = payload.resource;
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
- const { malloyConnections, apiConnections } = await createConnections(projectPath, defaultConnections);
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)).getPackageMetadata();
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
- return filteredMetadata;
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
- return this.packages.get(packageName);
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 _package = this.packages.get(packageName);
238871
- if (_package !== undefined && !reload) {
238872
- return _package;
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.exists(packagePath) || !(await fs8.promises.stat(packagePath)).isDirectory()) {
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.packages.set(packageName, await Package.create(this.projectName, packageName, packagePath, this.malloyConnections));
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
- throw new PackageNotFoundError(`Package ${packageName} not found`);
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 = new import_storage.Storage;
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(Object.keys(projectManifest.projects).map(async (projectName) => {
238955
- logger2.info(`Adding project "${projectName}"`);
238956
- const project = await this.addProject({
238957
- name: projectName,
238958
- resource: `${API_PREFIX}/projects/${projectName}`,
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 project.listPackages();
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
- await this.finishedInitialization;
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 projectPath = project?.metadata.location || projectManifest.projects[projectName];
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 projectPath = project.location || projectManifest.projects[projectName];
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 (projectPath) {
239005
- absoluteProjectPath = await this.loadProjectIntoDisk(projectName, projectPath);
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
- throw new ProjectNotFoundError(`Project ${projectName} not found`);
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[entry.name] = entry.name;
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}/${projectName}`;
239099
- if (projectPath.startsWith("/")) {
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 "${projectPath}"`);
239102
- await this.mountLocalDirectory(projectPath, absoluteTargetPath, projectName);
239103
- return absoluteTargetPath;
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 "${projectPath}"`, {
239298
+ logger2.error(`Failed to mount local directory "${location}"`, {
239106
239299
  error
239107
239300
  });
239108
- throw error;
239301
+ throw new PackageNotFoundError(`Failed to mount local directory: ${location}`);
239109
239302
  }
239110
239303
  }
239111
- if (projectPath.startsWith("gs://")) {
239304
+ if (location.startsWith("gs://")) {
239112
239305
  try {
239113
- logger2.info(`Downloading GCS path "${projectPath}" to "${absoluteTargetPath}"`);
239114
- await this.downloadGcsDirectory(projectPath, projectName, absoluteTargetPath);
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 path "${projectPath}"`, {
239310
+ logger2.error(`Failed to download GCS directory "${location}"`, {
239117
239311
  error
239118
239312
  });
239119
- throw error;
239313
+ throw new PackageNotFoundError(`Failed to download GCS directory: ${location}`);
239120
239314
  }
239121
- return absoluteTargetPath;
239122
239315
  }
239123
- if (projectPath.startsWith("s3://")) {
239316
+ if (location.startsWith("https://github.com/") || location.startsWith("git@")) {
239124
239317
  try {
239125
- logger2.info(`Mounting S3 path "${projectPath}"`);
239126
- await this.downloadS3Directory(projectPath, projectName, absoluteTargetPath);
239127
- return absoluteTargetPath;
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 mount S3 path "${projectPath}"`, { error });
239130
- throw error;
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 (projectPath.startsWith("https://github.com/") || projectPath.startsWith("git@")) {
239328
+ if (location.startsWith("s3://")) {
239134
239329
  try {
239135
- logger2.info(`Mounting GitHub path "${projectPath}"`);
239136
- await this.downloadGitHubDirectory(projectPath, absoluteTargetPath);
239137
- return absoluteTargetPath;
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 mount GitHub path "${projectPath}"`, {
239334
+ logger2.error(`Failed to download S3 directory "${location}"`, {
239140
239335
  error
239141
239336
  });
239142
- throw error;
239337
+ throw new PackageNotFoundError(`Failed to download S3 directory: ${location}`);
239143
239338
  }
239144
239339
  }
239145
- const errorMsg = `Invalid project path: "${projectPath}". Must be an absolute mounted path or a GCS/S3/GitHub URI.`;
239146
- logger2.error(errorMsg, { projectName, projectPath });
239147
- throw new ProjectNotFoundError(errorMsg);
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 ProjectNotFoundError(`Project ${projectName} not found in "${projectPath}"`);
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
- this.watchingPath = import_path4.default.join(this.projectStore.serverRootPath, projectManifest.projects[req.body.projectName]);
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 project = await this.projectStore.getProject(req.body.projectName, true);
239276
- await this.projectStore.addProject(project.metadata);
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
- res.status(200).json({
243268
- timestamp: Date.now(),
243269
- projects: await projectStore.listProjects()
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.serialize());
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);