@malloy-publisher/server 0.0.196-dev → 0.0.196

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 (103) hide show
  1. package/dist/app/api-doc.yaml +213 -214
  2. package/dist/app/assets/EnvironmentPage-1j6QDWAy.js +1 -0
  3. package/dist/app/assets/HomePage-DMop21VG.js +1 -0
  4. package/dist/app/assets/MainPage-BbE8ETz1.js +2 -0
  5. package/dist/app/assets/ModelPage-D2jvfe3t.js +1 -0
  6. package/dist/app/assets/PackagePage-BbnhGoD3.js +1 -0
  7. package/dist/app/assets/{RouteError-DefbDO7F.js → RouteError-D3LGEZ3i.js} +1 -1
  8. package/dist/app/assets/WorkbookPage-DttVIj4u.js +1 -0
  9. package/dist/app/assets/{core-BrfQApxh.es-DnvCX4oH.js → core-w79IMXAG.es-Bd0UlzOL.js} +1 -1
  10. package/dist/app/assets/{index-Bu0ub036.js → index-5K9YjIxF.js} +117 -117
  11. package/dist/app/assets/{index-CkzK3JIl.js → index-C513UodQ.js} +1 -1
  12. package/dist/app/assets/{index-CoA6HIGS.js → index-DIgzgp69.js} +1 -1
  13. package/dist/app/assets/{index.umd-B6Ms2PpL.js → index.umd-BMeMPq_9.js} +1 -1
  14. package/dist/app/index.html +1 -1
  15. package/dist/server.mjs +1954 -1318
  16. package/package.json +1 -1
  17. package/publisher.config.json +2 -2
  18. package/src/config.spec.ts +181 -66
  19. package/src/config.ts +68 -47
  20. package/src/controller/compile.controller.ts +10 -7
  21. package/src/controller/connection.controller.ts +79 -58
  22. package/src/controller/database.controller.ts +10 -7
  23. package/src/controller/manifest.controller.ts +23 -14
  24. package/src/controller/materialization.controller.ts +14 -14
  25. package/src/controller/model.controller.ts +35 -20
  26. package/src/controller/package.controller.ts +83 -49
  27. package/src/controller/query.controller.ts +11 -8
  28. package/src/controller/watch-mode.controller.ts +35 -29
  29. package/src/errors.ts +2 -2
  30. package/src/mcp/error_messages.ts +2 -2
  31. package/src/mcp/handler_utils.ts +23 -20
  32. package/src/mcp/mcp_constants.ts +1 -1
  33. package/src/mcp/prompts/handlers.ts +3 -3
  34. package/src/mcp/prompts/prompt_service.ts +5 -5
  35. package/src/mcp/prompts/utils.ts +12 -12
  36. package/src/mcp/resource_metadata.ts +3 -3
  37. package/src/mcp/resources/environment_resource.ts +187 -0
  38. package/src/mcp/resources/model_resource.ts +19 -17
  39. package/src/mcp/resources/notebook_resource.ts +13 -13
  40. package/src/mcp/resources/package_resource.ts +30 -27
  41. package/src/mcp/resources/query_resource.ts +15 -10
  42. package/src/mcp/resources/source_resource.ts +10 -10
  43. package/src/mcp/resources/view_resource.ts +11 -11
  44. package/src/mcp/server.ts +16 -14
  45. package/src/mcp/tools/discovery_tools.ts +67 -49
  46. package/src/mcp/tools/execute_query_tool.ts +14 -14
  47. package/src/server-old.ts +1119 -0
  48. package/src/server.ts +191 -159
  49. package/src/service/connection.spec.ts +158 -133
  50. package/src/service/connection.ts +42 -39
  51. package/src/service/connection_config.spec.ts +13 -11
  52. package/src/service/connection_config.ts +28 -19
  53. package/src/service/connection_service.spec.ts +63 -43
  54. package/src/service/connection_service.ts +106 -89
  55. package/src/service/{project.ts → environment.ts} +92 -77
  56. package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
  57. package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -85
  58. package/src/service/{project_store.ts → environment_store.ts} +368 -326
  59. package/src/service/manifest_service.spec.ts +15 -15
  60. package/src/service/manifest_service.ts +26 -21
  61. package/src/service/materialization_service.spec.ts +93 -59
  62. package/src/service/materialization_service.ts +71 -62
  63. package/src/service/materialized_table_gc.spec.ts +15 -15
  64. package/src/service/materialized_table_gc.ts +3 -3
  65. package/src/service/model.ts +2 -2
  66. package/src/service/package.spec.ts +2 -2
  67. package/src/service/package.ts +23 -21
  68. package/src/service/resolve_environment.ts +15 -0
  69. package/src/storage/DatabaseInterface.ts +34 -25
  70. package/src/storage/StorageManager.mock.ts +3 -3
  71. package/src/storage/StorageManager.ts +24 -23
  72. package/src/storage/duckdb/ConnectionRepository.ts +13 -11
  73. package/src/storage/duckdb/DuckDBConnection.ts +1 -1
  74. package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
  75. package/src/storage/duckdb/DuckDBRepository.ts +47 -47
  76. package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
  77. package/src/storage/duckdb/ManifestRepository.ts +21 -20
  78. package/src/storage/duckdb/MaterializationRepository.ts +31 -28
  79. package/src/storage/duckdb/PackageRepository.ts +11 -11
  80. package/src/storage/duckdb/manifest_store.spec.ts +2 -2
  81. package/src/storage/duckdb/schema.ts +61 -20
  82. package/src/storage/ducklake/DuckLakeManifestStore.ts +14 -14
  83. package/tests/fixtures/publisher.config.json +1 -1
  84. package/tests/harness/e2e.ts +1 -1
  85. package/tests/harness/mcp_test_setup.ts +1 -1
  86. package/tests/harness/mocks.ts +10 -8
  87. package/tests/harness/rest_e2e.ts +2 -2
  88. package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +259 -0
  89. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
  90. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +27 -48
  91. package/tests/integration/mcp/mcp_resource.integration.spec.ts +26 -35
  92. package/tests/unit/duckdb/attached_databases.test.ts +51 -33
  93. package/tests/unit/duckdb/legacy_schema_migration.test.ts +194 -0
  94. package/tests/unit/ducklake/ducklake.test.ts +24 -22
  95. package/tests/unit/mcp/prompt_happy.test.ts +8 -8
  96. package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
  97. package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
  98. package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
  99. package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
  100. package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
  101. package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
  102. package/src/mcp/resources/project_resource.ts +0 -184
  103. package/src/service/resolve_project.ts +0 -13
@@ -11,7 +11,7 @@ import { components } from "../api";
11
11
  import {
12
12
  getProcessedPublisherConfig,
13
13
  isPublisherConfigFrozen,
14
- ProcessedProject,
14
+ ProcessedEnvironment,
15
15
  ProcessedPublisherConfig,
16
16
  } from "../config";
17
17
  import {
@@ -21,16 +21,16 @@ import {
21
21
  } from "../constants";
22
22
  import {
23
23
  BadRequestError,
24
+ EnvironmentNotFoundError,
24
25
  FrozenConfigError,
25
26
  PackageNotFoundError,
26
- ProjectNotFoundError,
27
27
  } from "../errors";
28
28
  import { getOperationalState, markNotReady, markReady } from "../health";
29
29
  import { formatDuration, logger } from "../logger";
30
30
  import { Connection } from "../storage/DatabaseInterface";
31
31
  import { StorageConfig, StorageManager } from "../storage/StorageManager";
32
- import { PackageStatus, Project } from "./project";
33
- type ApiProject = components["schemas"]["Project"];
32
+ import { Environment, PackageStatus } from "./environment";
33
+ type ApiEnvironment = components["schemas"]["Environment"];
34
34
 
35
35
  const AZURE_SUPPORTED_SCHEMES = ["https://", "http://", "abfss://", "az://"];
36
36
  const AZURE_DATA_EXTENSIONS = [
@@ -74,8 +74,8 @@ function validateAzureUrl(url: string, fieldName: string): void {
74
74
  }
75
75
  }
76
76
 
77
- function validateProjectAzureUrls(project: ApiProject): void {
78
- for (const conn of project.connections || []) {
77
+ function validateEnvironmentAzureUrls(environment: ApiEnvironment): void {
78
+ for (const conn of environment.connections || []) {
79
79
  if (conn.type !== "duckdb") continue;
80
80
  for (const db of conn.duckdbConnection?.attachedDatabases || []) {
81
81
  if (db.type !== "azure" || !db.azureConnection) continue;
@@ -89,10 +89,10 @@ function validateProjectAzureUrls(project: ApiProject): void {
89
89
  }
90
90
  }
91
91
 
92
- export class ProjectStore {
92
+ export class EnvironmentStore {
93
93
  public serverRootPath: string;
94
- private projects: Map<string, Project> = new Map();
95
- private projectMutexes = new Map<string, Mutex>();
94
+ private environments: Map<string, Environment> = new Map();
95
+ private environmentMutexes = new Map<string, Mutex>();
96
96
  public publisherConfigIsFrozen: boolean;
97
97
  public finishedInitialization: Promise<void>;
98
98
  private isInitialized: boolean = false;
@@ -117,29 +117,29 @@ export class ProjectStore {
117
117
  this.finishedInitialization = this.initialize();
118
118
  }
119
119
 
120
- private async addConfiguredProject(project: ProcessedProject) {
120
+ private async addConfiguredEnvironment(environment: ProcessedEnvironment) {
121
121
  try {
122
- await this.addProject(
122
+ await this.addEnvironment(
123
123
  {
124
- name: project.name,
125
- resource: `${API_PREFIX}/projects/${project.name}`,
126
- connections: project.connections,
127
- packages: project.packages,
124
+ name: environment.name,
125
+ resource: `${API_PREFIX}/environments/${environment.name}`,
126
+ connections: environment.connections,
127
+ packages: environment.packages,
128
128
  },
129
129
  true,
130
130
  );
131
131
  } catch (error) {
132
- this.logProjectInitializationError(project.name, error);
132
+ this.logEnvironmentInitializationError(environment.name, error);
133
133
  }
134
134
  }
135
135
 
136
- private logProjectInitializationError(
137
- projectName: string | undefined,
136
+ private logEnvironmentInitializationError(
137
+ environmentName: string | undefined,
138
138
  error: unknown,
139
139
  ) {
140
- const label = projectName ? ` "${projectName}"` : "";
140
+ const label = environmentName ? ` "${environmentName}"` : "";
141
141
  logger.error(
142
- `Error initializing project${label}; skipping project`,
142
+ `Error initializing environment${label}; skipping environment`,
143
143
  this.extractErrorDataFromError(error),
144
144
  );
145
145
  }
@@ -155,62 +155,69 @@ export class ProjectStore {
155
155
  this.serverRootPath,
156
156
  );
157
157
 
158
- const projectManifest = await ProjectStore.reloadProjectManifest(
159
- this.serverRootPath,
160
- );
158
+ const environmentManifest =
159
+ await EnvironmentStore.reloadEnvironmentManifest(
160
+ this.serverRootPath,
161
+ );
161
162
 
162
163
  await this.cleanupAndCreatePublisherPath();
163
164
 
164
165
  const repository = this.storageManager.getRepository();
165
166
 
166
167
  if (reInit) {
167
- // Load projects from config file
168
+ // Load environments from config file
168
169
  await Promise.all(
169
- projectManifest.projects.map(async (project) => {
170
- await this.addConfiguredProject(project);
170
+ environmentManifest.environments.map(async (environment) => {
171
+ await this.addConfiguredEnvironment(environment);
171
172
  }),
172
173
  );
173
174
  } else {
174
- // Load existing projects from database
175
- const existingProjects = await repository.listProjects();
175
+ // Load existing environments from database
176
+ const existingEnvironments = await repository.listEnvironments();
176
177
 
177
- if (existingProjects.length > 0) {
178
- // Load projects from database
178
+ if (existingEnvironments.length > 0) {
179
+ // Load environments from database
179
180
  await Promise.all(
180
- existingProjects.map(async (dbProject) => {
181
+ existingEnvironments.map(async (dbEnvironment) => {
181
182
  try {
182
- // Check if project files exist on disk
183
- const projectExists = await fs.promises
184
- .access(dbProject.path)
183
+ // Check if environment files exist on disk
184
+ const environmentExists = await fs.promises
185
+ .access(dbEnvironment.path)
185
186
  .then(() => true)
186
187
  .catch(() => false);
187
188
 
188
- if (!projectExists) {
189
+ if (!environmentExists) {
189
190
  // Try to find in config and reload
190
- const projectConfig = projectManifest.projects.find(
191
- (p) => p.name === dbProject.name,
192
- );
191
+ const environmentConfig =
192
+ environmentManifest.environments.find(
193
+ (p) => p.name === dbEnvironment.name,
194
+ );
195
+
196
+ if (environmentConfig) {
197
+ const environmentInstance =
198
+ await this.addEnvironment(
199
+ {
200
+ name: environmentConfig.name,
201
+ resource: `${API_PREFIX}/environments/${environmentConfig.name}`,
202
+ connections:
203
+ environmentConfig.connections,
204
+ packages: environmentConfig.packages,
205
+ },
206
+ true,
207
+ );
193
208
 
194
- if (projectConfig) {
195
- const projectInstance = await this.addProject(
209
+ // Update database with new path
210
+ await repository.updateEnvironment(
211
+ dbEnvironment.id,
196
212
  {
197
- name: projectConfig.name,
198
- resource: `${API_PREFIX}/projects/${projectConfig.name}`,
199
- connections: projectConfig.connections,
200
- packages: projectConfig.packages,
213
+ path: environmentInstance.metadata.location,
201
214
  },
202
- true,
203
215
  );
204
216
 
205
- // Update database with new path
206
- await repository.updateProject(dbProject.id, {
207
- path: projectInstance.metadata.location,
208
- });
209
-
210
- return projectInstance.listPackages();
217
+ return environmentInstance.listPackages();
211
218
  } else {
212
219
  logger.error(
213
- `Project "${dbProject.name}" not found in config and files missing`,
220
+ `Environment "${dbEnvironment.name}" not found in config and files missing`,
214
221
  );
215
222
  return;
216
223
  }
@@ -218,12 +225,12 @@ export class ProjectStore {
218
225
 
219
226
  // Get connections from database
220
227
  const connections = await repository.listConnections(
221
- dbProject.id,
228
+ dbEnvironment.id,
222
229
  );
223
230
 
224
- const projectInstance = await Project.create(
225
- dbProject.name,
226
- dbProject.path,
231
+ const environmentInstance = await Environment.create(
232
+ dbEnvironment.name,
233
+ dbEnvironment.path,
227
234
  connections.map((conn) => ({
228
235
  name: conn.name,
229
236
  type: conn.type,
@@ -234,21 +241,24 @@ export class ProjectStore {
234
241
 
235
242
  // Get packages from database
236
243
  const packages = await repository.listPackages(
237
- dbProject.id,
244
+ dbEnvironment.id,
238
245
  );
239
246
  packages.forEach((pkg) => {
240
- projectInstance.setPackageStatus(
247
+ environmentInstance.setPackageStatus(
241
248
  pkg.name,
242
249
  PackageStatus.SERVING,
243
250
  );
244
251
  });
245
252
 
246
- this.projects.set(dbProject.name, projectInstance);
253
+ this.environments.set(
254
+ dbEnvironment.name,
255
+ environmentInstance,
256
+ );
247
257
 
248
- return projectInstance.listPackages();
258
+ return environmentInstance.listPackages();
249
259
  } catch (error) {
250
- this.logProjectInitializationError(
251
- dbProject.name,
260
+ this.logEnvironmentInitializationError(
261
+ dbEnvironment.name,
252
262
  error,
253
263
  );
254
264
  }
@@ -257,8 +267,8 @@ export class ProjectStore {
257
267
  } else {
258
268
  // Fallback to config file if database is empty
259
269
  await Promise.all(
260
- projectManifest.projects.map(async (project) => {
261
- await this.addConfiguredProject(project);
270
+ environmentManifest.environments.map(async (environment) => {
271
+ await this.addConfiguredEnvironment(environment);
262
272
  }),
263
273
  );
264
274
  }
@@ -268,90 +278,99 @@ export class ProjectStore {
268
278
  markReady();
269
279
  const initializationDuration = performance.now() - initialTime;
270
280
  logger.info(
271
- `Project store successfully initialized in ${formatDuration(initializationDuration)}`,
281
+ `Environment store successfully initialized in ${formatDuration(initializationDuration)}`,
272
282
  );
273
283
  } catch (error) {
274
284
  markNotReady();
275
285
  const errorData = this.extractErrorDataFromError(error);
276
- logger.error("Error initializing project store", errorData);
286
+ logger.error("Error initializing environment store", errorData);
277
287
  }
278
288
  }
279
289
 
280
- public async addProjectToDatabase(project: Project): Promise<void> {
281
- if (!project) {
282
- logger.error("Cannot sync: project is null or undefined");
290
+ public async addEnvironmentToDatabase(
291
+ environment: Environment,
292
+ ): Promise<void> {
293
+ if (!environment) {
294
+ logger.error("Cannot sync: environment is null or undefined");
283
295
  return;
284
296
  }
285
297
 
286
- const projectName = project.metadata?.name;
287
- if (!projectName) {
288
- throw new Error("Project name is required but not found");
298
+ const environmentName = environment.metadata?.name;
299
+ if (!environmentName) {
300
+ throw new Error("Environment name is required but not found");
289
301
  }
290
302
 
291
303
  const repository = this.storageManager.getRepository();
292
304
 
293
- // Sync project metadata
294
- const dbProject = await this.addProjectMetadata(project, repository);
305
+ // Sync environment metadata
306
+ const dbEnvironment = await this.addEnvironmentMetadata(
307
+ environment,
308
+ repository,
309
+ );
295
310
 
296
311
  // Sync connections
297
- await this.addConnections(project, dbProject.id, repository);
312
+ await this.addConnections(environment, dbEnvironment.id, repository);
298
313
 
299
314
  // Sync packages
300
- await this.addPackages(project, dbProject.id, repository);
315
+ await this.addPackages(environment, dbEnvironment.id, repository);
301
316
 
302
- logger.info(`Synced project "${projectName}" to database`);
317
+ logger.info(`Synced environment "${environmentName}" to database`);
303
318
  }
304
319
 
305
- public async deleteProjectFromDatabase(projectName: string): Promise<void> {
320
+ public async deleteEnvironmentFromDatabase(
321
+ environmentName: string,
322
+ ): Promise<void> {
306
323
  const repository = this.storageManager.getRepository();
307
324
 
308
- // Get the project from database
309
- const dbProject = await repository.getProjectByName(projectName);
325
+ // Get the environment from database
326
+ const dbEnvironment =
327
+ await repository.getEnvironmentByName(environmentName);
310
328
 
311
- if (!dbProject) {
312
- logger.error(`Project "${projectName}" not found in database`);
329
+ if (!dbEnvironment) {
330
+ logger.error(`Environment "${environmentName}" not found in database`);
313
331
  return;
314
332
  }
315
333
 
316
- // Delete the project (this will cascade delete connections and packages)
317
- await repository.deleteProject(dbProject.id);
318
- logger.info(`Deleted project "${projectName}" from database`);
334
+ // Delete the environment (this will cascade delete connections and packages)
335
+ await repository.deleteEnvironment(dbEnvironment.id);
336
+ logger.info(`Deleted environment "${environmentName}" from database`);
319
337
  }
320
338
 
321
- private async addProjectMetadata(
322
- project: Project,
339
+ private async addEnvironmentMetadata(
340
+ environment: Environment,
323
341
  repository: ReturnType<typeof this.storageManager.getRepository>,
324
342
  ): Promise<{ id: string; name: string }> {
325
- const projectName = project.metadata?.name;
326
- if (!projectName) {
327
- throw new Error("Project name is required but not found");
328
- }
329
- const projectPath = project.metadata?.location || "";
330
- const projectDescription = project.metadata?.readme;
331
-
332
- const projectData = {
333
- name: projectName,
334
- path: projectPath,
335
- description: projectDescription,
336
- metadata: project.metadata || {},
343
+ const environmentName = environment.metadata?.name;
344
+ if (!environmentName) {
345
+ throw new Error("Environment name is required but not found");
346
+ }
347
+ const environmentPath = environment.metadata?.location || "";
348
+ const environmentDescription = environment.metadata?.readme;
349
+
350
+ const environmentData = {
351
+ name: environmentName,
352
+ path: environmentPath,
353
+ description: environmentDescription,
354
+ metadata: environment.metadata || {},
337
355
  };
338
- const existingProject = await repository.getProjectByName(projectName);
356
+ const existingEnvironment =
357
+ await repository.getEnvironmentByName(environmentName);
339
358
 
340
- let dbProject: { id: string; name: string };
341
- if (existingProject) {
359
+ let dbEnvironment: { id: string; name: string };
360
+ if (existingEnvironment) {
342
361
  const updateData = {
343
- description: projectDescription,
344
- metadata: project.metadata || {},
362
+ description: environmentDescription,
363
+ metadata: environment.metadata || {},
345
364
  };
346
365
 
347
- await repository.updateProject(existingProject.id, updateData);
348
- dbProject = { id: existingProject.id, name: projectName };
366
+ await repository.updateEnvironment(existingEnvironment.id, updateData);
367
+ dbEnvironment = { id: existingEnvironment.id, name: environmentName };
349
368
  } else {
350
- dbProject = await repository.createProject(projectData);
369
+ dbEnvironment = await repository.createEnvironment(environmentData);
351
370
  }
352
371
 
353
- // Initialize DuckLake manifest storage if configured on the project.
354
- const materializationStorage = project.metadata
372
+ // Initialize DuckLake manifest storage if configured on the environment.
373
+ const materializationStorage = environment.metadata
355
374
  ?.materializationStorage as
356
375
  | { catalogUrl?: string; dataPath?: string }
357
376
  | undefined;
@@ -359,9 +378,9 @@ export class ProjectStore {
359
378
  materializationStorage?.catalogUrl &&
360
379
  materializationStorage?.dataPath
361
380
  ) {
362
- await this.storageManager.initializeDuckLakeForProject(
363
- dbProject.id,
364
- dbProject.name,
381
+ await this.storageManager.initializeDuckLakeForEnvironment(
382
+ dbEnvironment.id,
383
+ dbEnvironment.name,
365
384
  {
366
385
  catalogUrl: materializationStorage.catalogUrl,
367
386
  dataPath: materializationStorage.dataPath,
@@ -369,15 +388,15 @@ export class ProjectStore {
369
388
  );
370
389
  }
371
390
 
372
- return dbProject;
391
+ return dbEnvironment;
373
392
  }
374
393
 
375
394
  private async addPackages(
376
- project: Project,
377
- projectId: string,
395
+ environment: Environment,
396
+ environmentId: string,
378
397
  repository: ReturnType<typeof this.storageManager.getRepository>,
379
398
  ): Promise<void> {
380
- const packages = await project.listPackages();
399
+ const packages = await environment.listPackages();
381
400
 
382
401
  // Sync each package
383
402
  for (const pkg of packages) {
@@ -386,13 +405,13 @@ export class ProjectStore {
386
405
  continue;
387
406
  }
388
407
 
389
- await this.addPackage(pkg, projectId, repository);
408
+ await this.addPackage(pkg, environmentId, repository);
390
409
  }
391
410
  }
392
411
 
393
412
  private async addPackage(
394
413
  pkg: components["schemas"]["Package"],
395
- projectId: string,
414
+ environmentId: string,
396
415
  repository: ReturnType<typeof this.storageManager.getRepository>,
397
416
  ): Promise<void> {
398
417
  const pkgs = pkg as {
@@ -403,7 +422,7 @@ export class ProjectStore {
403
422
  };
404
423
 
405
424
  const packageData = {
406
- projectId,
425
+ environmentId,
407
426
  name: pkgs.name,
408
427
  description: pkgs.description ?? undefined,
409
428
  manifestPath: pkgs.manifestPath ?? "",
@@ -421,7 +440,7 @@ export class ProjectStore {
421
440
  ) {
422
441
  await this.updatePackage(
423
442
  pkgs.name,
424
- projectId,
443
+ environmentId,
425
444
  packageData,
426
445
  repository,
427
446
  );
@@ -433,7 +452,7 @@ export class ProjectStore {
433
452
 
434
453
  private async updatePackage(
435
454
  packageName: string,
436
- projectId: string,
455
+ environmentId: string,
437
456
  packageData: {
438
457
  description: string | undefined;
439
458
  manifestPath: string;
@@ -442,7 +461,7 @@ export class ProjectStore {
442
461
  repository: ReturnType<typeof this.storageManager.getRepository>,
443
462
  ): Promise<void> {
444
463
  const existingPackage = await repository.getPackageByName(
445
- projectId,
464
+ environmentId,
446
465
  packageName,
447
466
  );
448
467
 
@@ -453,12 +472,12 @@ export class ProjectStore {
453
472
  }
454
473
 
455
474
  private async addConnections(
456
- project: Project,
457
- projectId: string,
475
+ environment: Environment,
476
+ environmentId: string,
458
477
  repository: ReturnType<typeof this.storageManager.getRepository>,
459
478
  ): Promise<void> {
460
479
  try {
461
- const connections = project.listApiConnections();
480
+ const connections = environment.listApiConnections();
462
481
  // Add/update connections
463
482
  for (const conn of connections) {
464
483
  if (!conn.name) {
@@ -468,26 +487,29 @@ export class ProjectStore {
468
487
 
469
488
  // Check if connection exists
470
489
  const existingConn = await repository.getConnectionByName(
471
- projectId,
490
+ environmentId,
472
491
  conn.name,
473
492
  );
474
493
 
475
494
  if (existingConn) {
476
- await this.updateConnection(conn, projectId, repository);
495
+ await this.updateConnection(conn, environmentId, repository);
477
496
  } else {
478
- await this.addConnection(conn, projectId, repository);
497
+ await this.addConnection(conn, environmentId, repository);
479
498
  }
480
499
  }
481
500
  } catch (err: unknown) {
482
501
  const error = err as Error;
483
- const projectName = project.metadata?.name;
484
- logger.error(`Error syncing connections for "${projectName}":`, error);
502
+ const environmentName = environment.metadata?.name;
503
+ logger.error(
504
+ `Error syncing connections for "${environmentName}":`,
505
+ error,
506
+ );
485
507
  }
486
508
  }
487
509
 
488
510
  public async addConnection(
489
- conn: ReturnType<Project["listApiConnections"]>[number],
490
- projectId: string,
511
+ conn: ReturnType<Environment["listApiConnections"]>[number],
512
+ environmentId: string,
491
513
  repository: ReturnType<typeof this.storageManager.getRepository>,
492
514
  ): Promise<void> {
493
515
  if (!conn.name) {
@@ -496,7 +518,7 @@ export class ProjectStore {
496
518
  }
497
519
 
498
520
  const connectionData = {
499
- projectId,
521
+ environmentId,
500
522
  name: conn.name,
501
523
  type: conn.type as Connection["type"],
502
524
  config: conn,
@@ -513,8 +535,8 @@ export class ProjectStore {
513
535
  }
514
536
 
515
537
  public async updateConnection(
516
- conn: ReturnType<Project["listApiConnections"]>[number],
517
- projectId: string,
538
+ conn: ReturnType<Environment["listApiConnections"]>[number],
539
+ environmentId: string,
518
540
  repository: ReturnType<typeof this.storageManager.getRepository>,
519
541
  ): Promise<void> {
520
542
  if (!conn.name) {
@@ -522,12 +544,12 @@ export class ProjectStore {
522
544
  }
523
545
 
524
546
  const existingConn = await repository.getConnectionByName(
525
- projectId,
547
+ environmentId,
526
548
  conn.name,
527
549
  );
528
550
 
529
551
  if (!existingConn) {
530
- logger.error(`Connection "${conn.name}" not found in project`);
552
+ logger.error(`Connection "${conn.name}" not found in environment`);
531
553
  }
532
554
 
533
555
  const connectionData = {
@@ -548,31 +570,34 @@ export class ProjectStore {
548
570
  }
549
571
 
550
572
  public async addPackageToDatabase(
551
- projectName: string,
573
+ environmentName: string,
552
574
  packageName: string,
553
575
  ): Promise<void> {
554
- const project = await this.getProject(projectName, false);
576
+ const environment = await this.getEnvironment(environmentName, false);
555
577
  const repository = this.storageManager.getRepository();
556
578
 
557
- // Get the project ID from database
558
- const dbProject = await repository.getProjectByName(projectName);
579
+ // Get the environment ID from database
580
+ const dbEnvironment =
581
+ await repository.getEnvironmentByName(environmentName);
559
582
 
560
- if (!dbProject) {
561
- logger.error(`Project "${projectName}" not found in database`);
562
- throw new Error(`Project "${projectName}" not found in database`);
583
+ if (!dbEnvironment) {
584
+ logger.error(`Environment "${environmentName}" not found in database`);
585
+ throw new Error(
586
+ `Environment "${environmentName}" not found in database`,
587
+ );
563
588
  }
564
589
 
565
- // Get the package from the project
566
- const packages = await project.listPackages();
590
+ // Get the package from the environment
591
+ const packages = await environment.listPackages();
567
592
  const pkg = packages.find((p) => p.name === packageName);
568
593
 
569
594
  if (!pkg) {
570
- logger.warn(`Package "${packageName}" not found in project`);
595
+ logger.warn(`Package "${packageName}" not found in environment`);
571
596
  return;
572
597
  }
573
598
 
574
599
  // Sync the specific package
575
- await this.addPackage(pkg, dbProject.id, repository);
600
+ await this.addPackage(pkg, dbEnvironment.id, repository);
576
601
  logger.info(`Synced package "${packageName}" to database`);
577
602
  }
578
603
 
@@ -580,22 +605,23 @@ export class ProjectStore {
580
605
  * Delete a package from the database
581
606
  */
582
607
  public async deletePackageFromDatabase(
583
- projectName: string,
608
+ environmentName: string,
584
609
  packageName: string,
585
610
  ): Promise<void> {
586
611
  const repository = this.storageManager.getRepository();
587
612
 
588
- // Get the project ID from database
589
- const dbProject = await repository.getProjectByName(projectName);
613
+ // Get the environment ID from database
614
+ const dbEnvironment =
615
+ await repository.getEnvironmentByName(environmentName);
590
616
 
591
- if (!dbProject) {
592
- logger.error(`Project "${projectName}" not found in database`);
617
+ if (!dbEnvironment) {
618
+ logger.error(`Environment "${environmentName}" not found in database`);
593
619
  return;
594
620
  }
595
621
 
596
622
  // Find and delete the package
597
623
  const existingPackage = await repository.getPackageByName(
598
- dbProject.id,
624
+ dbEnvironment.id,
599
625
  packageName,
600
626
  );
601
627
 
@@ -644,13 +670,13 @@ export class ProjectStore {
644
670
  await fs.promises.mkdir(uploadDocsPath, { recursive: true });
645
671
  }
646
672
 
647
- public async listProjects(skipInitializationCheck: boolean = false) {
673
+ public async listEnvironments(skipInitializationCheck: boolean = false) {
648
674
  if (!skipInitializationCheck) {
649
675
  await this.finishedInitialization;
650
676
  }
651
677
  return Promise.all(
652
- Array.from(this.projects.values()).map((project) =>
653
- project.serialize(),
678
+ Array.from(this.environments.values()).map((environment) =>
679
+ environment.serialize(),
654
680
  ),
655
681
  );
656
682
  }
@@ -658,23 +684,23 @@ export class ProjectStore {
658
684
  public async getStatus() {
659
685
  const status = {
660
686
  timestamp: Date.now(),
661
- projects: [] as Array<components["schemas"]["Project"]>,
687
+ environments: [] as Array<components["schemas"]["Environment"]>,
662
688
  initialized: this.isInitialized,
663
689
  frozenConfig: isPublisherConfigFrozen(this.serverRootPath),
664
690
  operationalState:
665
691
  getOperationalState() as components["schemas"]["ServerStatus"]["operationalState"],
666
692
  };
667
693
 
668
- const projects = await this.listProjects(true);
694
+ const environments = await this.listEnvironments(true);
669
695
 
670
696
  await Promise.all(
671
- projects.map(async (project) => {
697
+ environments.map(async (environment) => {
672
698
  try {
673
- const packages = project.packages;
674
- const connections = project.connections;
699
+ const packages = environment.packages;
700
+ const connections = environment.connections;
675
701
 
676
- logger.debug(`Project ${project.name} status:`, {
677
- connectionsCount: project.connections?.length || 0,
702
+ logger.debug(`Environment ${environment.name} status:`, {
703
+ connectionsCount: environment.connections?.length || 0,
678
704
  packagesCount: packages?.length || 0,
679
705
  });
680
706
 
@@ -685,12 +711,12 @@ export class ProjectStore {
685
711
  };
686
712
  });
687
713
 
688
- const _project = {
689
- ...project,
714
+ const _environment = {
715
+ ...environment,
690
716
  connections: _connections,
691
717
  };
692
- project.connections = _connections;
693
- status.projects.push(_project);
718
+ environment.connections = _connections;
719
+ status.environments.push(_environment);
694
720
  } catch (error) {
695
721
  logger.error("Error listing packages and connections", {
696
722
  error,
@@ -704,62 +730,63 @@ export class ProjectStore {
704
730
  return status;
705
731
  }
706
732
 
707
- public async getProject(
708
- projectName: string,
733
+ public async getEnvironment(
734
+ environmentName: string,
709
735
  reload: boolean = false,
710
- ): Promise<Project> {
736
+ ): Promise<Environment> {
711
737
  await this.finishedInitialization;
712
738
 
713
- // Check if project is already loaded first
714
- const project = this.projects.get(projectName);
715
- if (project !== undefined && !reload) {
716
- return project;
739
+ // Check if environment is already loaded first
740
+ const environment = this.environments.get(environmentName);
741
+ if (environment !== undefined && !reload) {
742
+ return environment;
717
743
  }
718
744
 
719
745
  // We need to acquire the mutex to prevent concurrent requests from creating the
720
- // project multiple times.
721
- let projectMutex = this.projectMutexes.get(projectName);
722
- if (projectMutex?.isLocked()) {
723
- await projectMutex.waitForUnlock();
724
- const existingProject = this.projects.get(projectName);
725
- if (existingProject && !reload) {
726
- return existingProject;
746
+ // environment multiple times.
747
+ let environmentMutex = this.environmentMutexes.get(environmentName);
748
+ if (environmentMutex?.isLocked()) {
749
+ await environmentMutex.waitForUnlock();
750
+ const existingEnvironment = this.environments.get(environmentName);
751
+ if (existingEnvironment && !reload) {
752
+ return existingEnvironment;
727
753
  }
728
754
  }
729
- projectMutex = new Mutex();
730
- this.projectMutexes.set(projectName, projectMutex);
755
+ environmentMutex = new Mutex();
756
+ this.environmentMutexes.set(environmentName, environmentMutex);
731
757
 
732
- return projectMutex.runExclusive(async () => {
758
+ return environmentMutex.runExclusive(async () => {
733
759
  // Double-check after acquiring mutex
734
- const existingProject = this.projects.get(projectName);
735
- if (existingProject !== undefined && !reload) {
736
- return existingProject;
760
+ const existingEnvironment = this.environments.get(environmentName);
761
+ if (existingEnvironment !== undefined && !reload) {
762
+ return existingEnvironment;
737
763
  }
738
764
 
739
- const projectManifest = await ProjectStore.reloadProjectManifest(
740
- this.serverRootPath,
741
- );
742
- const projectConfig = projectManifest.projects.find(
743
- (p) => p.name === projectName,
765
+ const environmentManifest =
766
+ await EnvironmentStore.reloadEnvironmentManifest(
767
+ this.serverRootPath,
768
+ );
769
+ const environmentConfig = environmentManifest.environments.find(
770
+ (e) => e.name === environmentName,
744
771
  );
745
- const projectPath =
746
- existingProject?.metadata.location ||
747
- projectConfig?.packages[0]?.location;
748
- if (!projectPath) {
749
- throw new ProjectNotFoundError(
750
- `Project "${projectName}" could not be resolved to a path.`,
772
+ const environmentPath =
773
+ existingEnvironment?.metadata.location ||
774
+ environmentConfig?.packages[0]?.location;
775
+ if (!environmentPath) {
776
+ throw new EnvironmentNotFoundError(
777
+ `Environment "${environmentName}" could not be resolved to a path.`,
751
778
  );
752
779
  }
753
- return await this.addProject({
754
- name: projectName,
755
- resource: `${API_PREFIX}/projects/${projectName}`,
756
- connections: projectConfig?.connections || [],
780
+ return await this.addEnvironment({
781
+ name: environmentName,
782
+ resource: `${API_PREFIX}/environments/${environmentName}`,
783
+ connections: environmentConfig?.connections || [],
757
784
  });
758
785
  });
759
786
  }
760
787
 
761
- public async addProject(
762
- project: ApiProject,
788
+ public async addEnvironment(
789
+ environment: ApiEnvironment,
763
790
  skipInitialization: boolean = false,
764
791
  ) {
765
792
  if (!skipInitialization) {
@@ -768,136 +795,151 @@ export class ProjectStore {
768
795
  if (!skipInitialization && this.publisherConfigIsFrozen) {
769
796
  throw new FrozenConfigError();
770
797
  }
771
- const projectName = project.name;
772
- if (!projectName) {
773
- throw new Error("Project name is required");
774
- }
775
- // Check if project already exists and update it instead of creating a new one
776
- const existingProject = this.projects.get(projectName);
777
- if (existingProject) {
778
- const updatedProject = await existingProject.update(project);
779
- this.projects.set(projectName, updatedProject);
780
- await this.addProjectToDatabase(updatedProject);
781
- return updatedProject;
782
- }
783
- const projectManifest = await ProjectStore.reloadProjectManifest(
784
- this.serverRootPath,
785
- );
786
- const projectConfig = projectManifest.projects.find(
787
- (p) => p.name === projectName,
798
+ const environmentName = environment.name;
799
+ if (!environmentName) {
800
+ throw new Error("Environment name is required");
801
+ }
802
+ // Check if environment already exists and update it instead of creating a new one
803
+ const existingEnvironment = this.environments.get(environmentName);
804
+ if (existingEnvironment) {
805
+ const updatedEnvironment =
806
+ await existingEnvironment.update(environment);
807
+ this.environments.set(environmentName, updatedEnvironment);
808
+ await this.addEnvironmentToDatabase(updatedEnvironment);
809
+ return updatedEnvironment;
810
+ }
811
+ const environmentManifest =
812
+ await EnvironmentStore.reloadEnvironmentManifest(this.serverRootPath);
813
+ const environmentConfig = environmentManifest.environments.find(
814
+ (e) => e.name === environmentName,
788
815
  );
789
816
  const hasPackages =
790
- (project?.packages && project.packages.length > 0) ||
791
- (projectConfig?.packages && projectConfig.packages.length > 0);
792
- let absoluteProjectPath: string;
817
+ (environment?.packages && environment.packages.length > 0) ||
818
+ (environmentConfig?.packages && environmentConfig.packages.length > 0);
819
+ let absoluteEnvironmentPath: string;
793
820
  if (hasPackages) {
794
821
  const packagesToProcess =
795
- project?.packages || projectConfig?.packages || [];
796
- absoluteProjectPath = await this.loadProjectIntoDisk(
797
- projectName,
822
+ environment?.packages || environmentConfig?.packages || [];
823
+ absoluteEnvironmentPath = await this.loadEnvironmentIntoDisk(
824
+ environmentName,
798
825
  packagesToProcess,
799
826
  );
800
- if (absoluteProjectPath.endsWith(".zip")) {
801
- absoluteProjectPath = await this.unzipProject(absoluteProjectPath);
827
+ if (absoluteEnvironmentPath.endsWith(".zip")) {
828
+ absoluteEnvironmentPath = await this.unzipEnvironment(
829
+ absoluteEnvironmentPath,
830
+ );
802
831
  }
803
832
  } else {
804
- absoluteProjectPath = await this.scaffoldProject(project);
833
+ absoluteEnvironmentPath = await this.scaffoldEnvironment(environment);
805
834
  }
806
- const newProject = await Project.create(
807
- projectName,
808
- absoluteProjectPath,
809
- project.connections || [],
835
+ const newEnvironment = await Environment.create(
836
+ environmentName,
837
+ absoluteEnvironmentPath,
838
+ environment.connections || [],
810
839
  );
811
840
 
812
- if (!newProject.metadata) newProject.metadata = {};
813
- newProject.metadata.location = absoluteProjectPath;
814
- if (project.materializationStorage !== undefined) {
815
- newProject.metadata.materializationStorage =
816
- project.materializationStorage;
841
+ if (!newEnvironment.metadata) newEnvironment.metadata = {};
842
+ newEnvironment.metadata.location = absoluteEnvironmentPath;
843
+ if (environment.materializationStorage !== undefined) {
844
+ newEnvironment.metadata.materializationStorage =
845
+ environment.materializationStorage;
817
846
  }
818
847
 
819
- this.projects.set(projectName, newProject);
848
+ this.environments.set(environmentName, newEnvironment);
820
849
 
821
- project?.packages?.forEach((_package) => {
850
+ environment?.packages?.forEach((_package) => {
822
851
  if (_package.name) {
823
- newProject.setPackageStatus(_package.name, PackageStatus.SERVING);
852
+ newEnvironment.setPackageStatus(
853
+ _package.name,
854
+ PackageStatus.SERVING,
855
+ );
824
856
  }
825
857
  });
826
858
 
827
- await this.addProjectToDatabase(newProject);
859
+ await this.addEnvironmentToDatabase(newEnvironment);
828
860
 
829
- return newProject;
861
+ return newEnvironment;
830
862
  }
831
863
 
832
- public async unzipProject(absoluteProjectPath: string) {
864
+ public async unzipEnvironment(absoluteEnvironmentPath: string) {
833
865
  logger.info(
834
- `Detected zip file at "${absoluteProjectPath}". Unzipping...`,
866
+ `Detected zip file at "${absoluteEnvironmentPath}". Unzipping...`,
867
+ );
868
+ const unzippedEnvironmentPath = absoluteEnvironmentPath.replace(
869
+ ".zip",
870
+ "",
835
871
  );
836
- const unzippedProjectPath = absoluteProjectPath.replace(".zip", "");
837
- await fs.promises.rm(unzippedProjectPath, {
872
+ await fs.promises.rm(unzippedEnvironmentPath, {
838
873
  recursive: true,
839
874
  force: true,
840
875
  });
841
- await fs.promises.mkdir(unzippedProjectPath, { recursive: true });
876
+ await fs.promises.mkdir(unzippedEnvironmentPath, { recursive: true });
842
877
 
843
- const zip = new AdmZip(absoluteProjectPath);
844
- zip.extractAllTo(unzippedProjectPath, true);
878
+ const zip = new AdmZip(absoluteEnvironmentPath);
879
+ zip.extractAllTo(unzippedEnvironmentPath, true);
845
880
 
846
- return unzippedProjectPath;
881
+ return unzippedEnvironmentPath;
847
882
  }
848
883
 
849
- public async updateProject(project: ApiProject) {
884
+ public async updateEnvironment(environment: ApiEnvironment) {
850
885
  await this.finishedInitialization;
851
886
  if (this.publisherConfigIsFrozen) {
852
887
  throw new FrozenConfigError();
853
888
  }
854
- validateProjectAzureUrls(project);
855
- const projectName = project.name;
856
- if (!projectName) {
857
- throw new Error("Project name is required");
889
+ validateEnvironmentAzureUrls(environment);
890
+ const environmentName = environment.name;
891
+ if (!environmentName) {
892
+ throw new Error("Environment name is required");
858
893
  }
859
- const existingProject = this.projects.get(projectName);
860
- if (!existingProject) {
861
- throw new ProjectNotFoundError(`Project ${projectName} not found`);
894
+ const existingEnvironment = this.environments.get(environmentName);
895
+ if (!existingEnvironment) {
896
+ throw new EnvironmentNotFoundError(
897
+ `Environment ${environmentName} not found`,
898
+ );
862
899
  }
863
- const updatedProject = await existingProject.update(project);
864
- this.projects.set(projectName, updatedProject);
865
- await this.addProjectToDatabase(updatedProject);
866
- return updatedProject;
900
+ const updatedEnvironment = await existingEnvironment.update(environment);
901
+ this.environments.set(environmentName, updatedEnvironment);
902
+ await this.addEnvironmentToDatabase(updatedEnvironment);
903
+ return updatedEnvironment;
867
904
  }
868
905
 
869
- public async deleteProject(
870
- projectName: string,
871
- ): Promise<Project | undefined> {
906
+ public async deleteEnvironment(
907
+ environmentName: string,
908
+ ): Promise<Environment | undefined> {
872
909
  await this.finishedInitialization;
873
910
  if (this.publisherConfigIsFrozen) {
874
911
  throw new FrozenConfigError();
875
912
  }
876
- const project = this.projects.get(projectName);
877
- if (!project) {
913
+ const environment = this.environments.get(environmentName);
914
+ if (!environment) {
878
915
  return;
879
916
  }
880
917
 
881
- const projectPath = project.metadata?.location;
918
+ const environmentPath = environment.metadata?.location;
882
919
 
883
- // Close all connections before removing the project
884
- await project.closeAllConnections();
920
+ // Close all connections before removing the environment
921
+ await environment.closeAllConnections();
885
922
 
886
- this.projects.delete(projectName);
887
- await this.deleteProjectFromDatabase(projectName);
888
- if (projectPath) {
923
+ this.environments.delete(environmentName);
924
+ await this.deleteEnvironmentFromDatabase(environmentName);
925
+ if (environmentPath) {
889
926
  try {
890
- await fs.promises.rm(projectPath, { recursive: true, force: true });
891
- logger.info(`Deleted project directory: ${projectPath}`);
927
+ await fs.promises.rm(environmentPath, {
928
+ recursive: true,
929
+ force: true,
930
+ });
931
+ logger.info(`Deleted environment directory: ${environmentPath}`);
892
932
  } catch (err) {
893
- logger.error("Error removing project directory", { error: err });
933
+ logger.error("Error removing environment directory", {
934
+ error: err,
935
+ });
894
936
  }
895
937
  }
896
938
 
897
- return project;
939
+ return environment;
898
940
  }
899
941
 
900
- public static async reloadProjectManifest(
942
+ public static async reloadEnvironmentManifest(
901
943
  serverRootPath: string,
902
944
  ): Promise<ProcessedPublisherConfig> {
903
945
  try {
@@ -908,17 +950,17 @@ export class ProjectStore {
908
950
  `Error reading ${PUBLISHER_CONFIG_NAME}. Generating from directory`,
909
951
  { error },
910
952
  );
911
- return { frozenConfig: false, projects: [] };
953
+ return { frozenConfig: false, environments: [] };
912
954
  } else {
913
955
  // If publisher.config.json is missing, generate the manifest from directories
914
956
  try {
915
957
  const entries = await fs.promises.readdir(serverRootPath, {
916
958
  withFileTypes: true,
917
959
  });
918
- const projects: ProcessedProject[] = [];
960
+ const environments: ProcessedEnvironment[] = [];
919
961
  for (const entry of entries) {
920
962
  if (entry.isDirectory()) {
921
- projects.push({
963
+ environments.push({
922
964
  name: entry.name,
923
965
  packages: [
924
966
  {
@@ -930,31 +972,31 @@ export class ProjectStore {
930
972
  });
931
973
  }
932
974
  }
933
- return { frozenConfig: false, projects };
975
+ return { frozenConfig: false, environments };
934
976
  } catch (lsError) {
935
977
  logger.error(`Error listing directories in ${serverRootPath}`, {
936
978
  error: lsError,
937
979
  });
938
- return { frozenConfig: false, projects: [] };
980
+ return { frozenConfig: false, environments: [] };
939
981
  }
940
982
  }
941
983
  }
942
984
  }
943
985
 
944
- private async scaffoldProject(project: ApiProject) {
945
- const projectName = project.name;
946
- if (!projectName) {
947
- throw new Error("Project name is required");
986
+ private async scaffoldEnvironment(environment: ApiEnvironment) {
987
+ const environmentName = environment.name;
988
+ if (!environmentName) {
989
+ throw new Error("Environment name is required");
948
990
  }
949
- const absoluteProjectPath = `${this.serverRootPath}/${PUBLISHER_DATA_DIR}/${projectName}`;
950
- await fs.promises.mkdir(absoluteProjectPath, { recursive: true });
951
- if (project.readme) {
991
+ const absoluteEnvironmentPath = `${this.serverRootPath}/${PUBLISHER_DATA_DIR}/${environmentName}`;
992
+ await fs.promises.mkdir(absoluteEnvironmentPath, { recursive: true });
993
+ if (environment.readme) {
952
994
  await fs.promises.writeFile(
953
- path.join(absoluteProjectPath, "README.md"),
954
- project.readme,
995
+ path.join(absoluteEnvironmentPath, "README.md"),
996
+ environment.readme,
955
997
  );
956
998
  }
957
- return absoluteProjectPath;
999
+ return absoluteEnvironmentPath;
958
1000
  }
959
1001
 
960
1002
  private isLocalPath(location: string) {
@@ -982,17 +1024,17 @@ export class ProjectStore {
982
1024
  return location.startsWith("s3://");
983
1025
  }
984
1026
 
985
- private async loadProjectIntoDisk(
986
- projectName: string,
987
- packages: ApiProject["packages"],
1027
+ private async loadEnvironmentIntoDisk(
1028
+ environmentName: string,
1029
+ packages: ApiEnvironment["packages"],
988
1030
  ) {
989
- const absoluteTargetPath = `${this.serverRootPath}/${PUBLISHER_DATA_DIR}/${projectName}`;
1031
+ const absoluteTargetPath = `${this.serverRootPath}/${PUBLISHER_DATA_DIR}/${environmentName}`;
990
1032
 
991
1033
  await fs.promises.mkdir(absoluteTargetPath, { recursive: true });
992
1034
 
993
1035
  if (!packages || packages.length === 0) {
994
1036
  throw new PackageNotFoundError(
995
- `No packages found for project ${projectName}`,
1037
+ `No packages found for environment ${environmentName}`,
996
1038
  );
997
1039
  }
998
1040
 
@@ -1049,7 +1091,7 @@ export class ProjectStore {
1049
1091
  await this.downloadOrMountLocation(
1050
1092
  groupedLocation,
1051
1093
  tempDownloadPath,
1052
- projectName,
1094
+ environmentName,
1053
1095
  "shared",
1054
1096
  );
1055
1097
  // Extract each package from the downloaded content
@@ -1148,7 +1190,7 @@ export class ProjectStore {
1148
1190
  private async downloadOrMountLocation(
1149
1191
  location: string,
1150
1192
  targetPath: string,
1151
- projectName: string,
1193
+ environmentName: string,
1152
1194
  packageName: string,
1153
1195
  ) {
1154
1196
  const isCompressedFile = location.endsWith(".zip");
@@ -1160,7 +1202,7 @@ export class ProjectStore {
1160
1202
  );
1161
1203
  await this.downloadGcsDirectory(
1162
1204
  location,
1163
- projectName,
1205
+ environmentName,
1164
1206
  targetPath,
1165
1207
  isCompressedFile,
1166
1208
  );
@@ -1205,7 +1247,7 @@ export class ProjectStore {
1205
1247
  );
1206
1248
  await this.downloadS3Directory(
1207
1249
  location,
1208
- projectName,
1250
+ environmentName,
1209
1251
  targetPath,
1210
1252
  isCompressedFile,
1211
1253
  );
@@ -1234,7 +1276,7 @@ export class ProjectStore {
1234
1276
  await this.mountLocalDirectory(
1235
1277
  packagePath,
1236
1278
  targetPath,
1237
- projectName,
1279
+ environmentName,
1238
1280
  packageName,
1239
1281
  );
1240
1282
  return;
@@ -1252,40 +1294,40 @@ export class ProjectStore {
1252
1294
 
1253
1295
  // If we get here, the path format is not supported
1254
1296
  const errorMsg = `Invalid package path: "${location}". Must be an absolute mounted path or a GCS/S3/GitHub URI.`;
1255
- logger.error(errorMsg, { projectName, location });
1297
+ logger.error(errorMsg, { environmentName, location });
1256
1298
  throw new PackageNotFoundError(errorMsg);
1257
1299
  }
1258
1300
 
1259
1301
  public async mountLocalDirectory(
1260
- projectPath: string,
1302
+ environmentPath: string,
1261
1303
  absoluteTargetPath: string,
1262
- projectName: string,
1304
+ environmentName: string,
1263
1305
  packageName: string,
1264
1306
  ) {
1265
- if (projectPath.endsWith(".zip")) {
1266
- projectPath = await this.unzipProject(projectPath);
1307
+ if (environmentPath.endsWith(".zip")) {
1308
+ environmentPath = await this.unzipEnvironment(environmentPath);
1267
1309
  }
1268
- const projectDirExists =
1269
- (await fs.promises.stat(projectPath))?.isDirectory() ?? false;
1270
- if (projectDirExists) {
1310
+ const environmentDirExists =
1311
+ (await fs.promises.stat(environmentPath))?.isDirectory() ?? false;
1312
+ if (environmentDirExists) {
1271
1313
  await fs.promises.rm(absoluteTargetPath, {
1272
1314
  recursive: true,
1273
1315
  force: true,
1274
1316
  });
1275
1317
  await fs.promises.mkdir(absoluteTargetPath, { recursive: true });
1276
- await fs.promises.cp(projectPath, absoluteTargetPath, {
1318
+ await fs.promises.cp(environmentPath, absoluteTargetPath, {
1277
1319
  recursive: true,
1278
1320
  });
1279
1321
  } else {
1280
1322
  throw new PackageNotFoundError(
1281
- `Package ${packageName} for project ${projectName} not found in "${projectPath}"`,
1323
+ `Package ${packageName} for environment ${environmentName} not found in "${environmentPath}"`,
1282
1324
  );
1283
1325
  }
1284
1326
  }
1285
1327
 
1286
1328
  async downloadGcsDirectory(
1287
1329
  gcsPath: string,
1288
- projectName: string,
1330
+ environmentName: string,
1289
1331
  absoluteDirPath: string,
1290
1332
  isCompressedFile: boolean,
1291
1333
  ) {
@@ -1296,8 +1338,8 @@ export class ProjectStore {
1296
1338
  prefix,
1297
1339
  });
1298
1340
  if (files.length === 0) {
1299
- throw new ProjectNotFoundError(
1300
- `Project ${projectName} not found in ${gcsPath}`,
1341
+ throw new EnvironmentNotFoundError(
1342
+ `Environment ${environmentName} not found in ${gcsPath}`,
1301
1343
  );
1302
1344
  }
1303
1345
  if (!isCompressedFile) {
@@ -1328,14 +1370,14 @@ export class ProjectStore {
1328
1370
  }),
1329
1371
  );
1330
1372
  if (isCompressedFile) {
1331
- await this.unzipProject(absoluteDirPath);
1373
+ await this.unzipEnvironment(absoluteDirPath);
1332
1374
  }
1333
1375
  logger.info(`Downloaded GCS directory ${gcsPath} to ${absoluteDirPath}`);
1334
1376
  }
1335
1377
 
1336
1378
  async downloadS3Directory(
1337
1379
  s3Path: string,
1338
- projectName: string,
1380
+ environmentName: string,
1339
1381
  absoluteDirPath: string,
1340
1382
  isCompressedFile: boolean = false,
1341
1383
  ) {
@@ -1356,8 +1398,8 @@ export class ProjectStore {
1356
1398
  });
1357
1399
  const item = await this.s3Client.send(command);
1358
1400
  if (!item.Body) {
1359
- throw new ProjectNotFoundError(
1360
- `Project ${projectName} not found in ${s3Path}`,
1401
+ throw new EnvironmentNotFoundError(
1402
+ `Environment ${environmentName} not found in ${s3Path}`,
1361
1403
  );
1362
1404
  }
1363
1405
  const file = fs.createWriteStream(zipFilePath);
@@ -1368,7 +1410,7 @@ export class ProjectStore {
1368
1410
  });
1369
1411
 
1370
1412
  // Extract the zip file
1371
- await this.unzipProject(zipFilePath);
1413
+ await this.unzipEnvironment(zipFilePath);
1372
1414
  logger.info(`Downloaded S3 zip file ${s3Path} to ${absoluteDirPath}`);
1373
1415
  return;
1374
1416
  }
@@ -1382,8 +1424,8 @@ export class ProjectStore {
1382
1424
  await fs.promises.mkdir(absoluteDirPath, { recursive: true });
1383
1425
 
1384
1426
  if (!objects.Contents || objects.Contents.length === 0) {
1385
- throw new ProjectNotFoundError(
1386
- `Project ${projectName} not found in ${s3Path}`,
1427
+ throw new EnvironmentNotFoundError(
1428
+ `Environment ${environmentName} not found in ${s3Path}`,
1387
1429
  );
1388
1430
  }
1389
1431
  await Promise.all(