@malloy-publisher/server 0.0.195 → 0.0.197-dev

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 (100) 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 +1352 -1310
  16. package/package.json +2 -2
  17. package/publisher.config.json +2 -2
  18. package/src/config.spec.ts +74 -66
  19. package/src/config.ts +50 -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.ts +175 -159
  48. package/src/service/connection.spec.ts +158 -133
  49. package/src/service/connection.ts +42 -39
  50. package/src/service/connection_config.spec.ts +13 -11
  51. package/src/service/connection_config.ts +28 -19
  52. package/src/service/connection_service.spec.ts +63 -43
  53. package/src/service/connection_service.ts +106 -89
  54. package/src/service/{project.ts → environment.ts} +92 -77
  55. package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
  56. package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -83
  57. package/src/service/{project_store.ts → environment_store.ts} +373 -327
  58. package/src/service/manifest_service.spec.ts +15 -15
  59. package/src/service/manifest_service.ts +26 -21
  60. package/src/service/materialization_service.spec.ts +93 -59
  61. package/src/service/materialization_service.ts +71 -62
  62. package/src/service/materialized_table_gc.spec.ts +15 -15
  63. package/src/service/materialized_table_gc.ts +3 -3
  64. package/src/service/model.ts +4 -4
  65. package/src/service/package.spec.ts +2 -2
  66. package/src/service/package.ts +23 -21
  67. package/src/service/resolve_environment.ts +15 -0
  68. package/src/storage/DatabaseInterface.ts +34 -25
  69. package/src/storage/StorageManager.mock.ts +3 -3
  70. package/src/storage/StorageManager.ts +64 -28
  71. package/src/storage/duckdb/ConnectionRepository.ts +13 -11
  72. package/src/storage/duckdb/DuckDBConnection.ts +1 -1
  73. package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
  74. package/src/storage/duckdb/DuckDBRepository.ts +47 -47
  75. package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
  76. package/src/storage/duckdb/ManifestRepository.ts +21 -20
  77. package/src/storage/duckdb/MaterializationRepository.ts +31 -28
  78. package/src/storage/duckdb/PackageRepository.ts +11 -11
  79. package/src/storage/duckdb/manifest_store.spec.ts +2 -2
  80. package/src/storage/duckdb/schema.ts +20 -20
  81. package/src/storage/ducklake/DuckLakeManifestStore.ts +20 -11
  82. package/tests/fixtures/publisher.config.json +1 -1
  83. package/tests/harness/e2e.ts +1 -1
  84. package/tests/harness/mcp_test_setup.ts +12 -24
  85. package/tests/harness/mocks.ts +10 -8
  86. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
  87. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +28 -49
  88. package/tests/integration/mcp/mcp_resource.integration.spec.ts +39 -47
  89. package/tests/integration/mcp/mcp_transport.integration.spec.ts +1 -1
  90. package/tests/unit/duckdb/attached_databases.test.ts +51 -33
  91. package/tests/unit/ducklake/ducklake.test.ts +24 -22
  92. package/tests/unit/mcp/prompt_happy.test.ts +8 -8
  93. package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
  94. package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
  95. package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
  96. package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
  97. package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
  98. package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
  99. package/src/mcp/resources/project_resource.ts +0 -184
  100. 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
+ );
193
195
 
194
- if (projectConfig) {
195
- const projectInstance = await this.addProject(
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
+ );
208
+
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,21 +378,25 @@ export class ProjectStore {
359
378
  materializationStorage?.catalogUrl &&
360
379
  materializationStorage?.dataPath
361
380
  ) {
362
- await this.storageManager.initializeDuckLakeForProject(dbProject.id, {
363
- catalogUrl: materializationStorage.catalogUrl,
364
- dataPath: materializationStorage.dataPath,
365
- });
381
+ await this.storageManager.initializeDuckLakeForEnvironment(
382
+ dbEnvironment.id,
383
+ dbEnvironment.name,
384
+ {
385
+ catalogUrl: materializationStorage.catalogUrl,
386
+ dataPath: materializationStorage.dataPath,
387
+ },
388
+ );
366
389
  }
367
390
 
368
- return dbProject;
391
+ return dbEnvironment;
369
392
  }
370
393
 
371
394
  private async addPackages(
372
- project: Project,
373
- projectId: string,
395
+ environment: Environment,
396
+ environmentId: string,
374
397
  repository: ReturnType<typeof this.storageManager.getRepository>,
375
398
  ): Promise<void> {
376
- const packages = await project.listPackages();
399
+ const packages = await environment.listPackages();
377
400
 
378
401
  // Sync each package
379
402
  for (const pkg of packages) {
@@ -382,13 +405,13 @@ export class ProjectStore {
382
405
  continue;
383
406
  }
384
407
 
385
- await this.addPackage(pkg, projectId, repository);
408
+ await this.addPackage(pkg, environmentId, repository);
386
409
  }
387
410
  }
388
411
 
389
412
  private async addPackage(
390
413
  pkg: components["schemas"]["Package"],
391
- projectId: string,
414
+ environmentId: string,
392
415
  repository: ReturnType<typeof this.storageManager.getRepository>,
393
416
  ): Promise<void> {
394
417
  const pkgs = pkg as {
@@ -399,7 +422,7 @@ export class ProjectStore {
399
422
  };
400
423
 
401
424
  const packageData = {
402
- projectId,
425
+ environmentId,
403
426
  name: pkgs.name,
404
427
  description: pkgs.description ?? undefined,
405
428
  manifestPath: pkgs.manifestPath ?? "",
@@ -417,7 +440,7 @@ export class ProjectStore {
417
440
  ) {
418
441
  await this.updatePackage(
419
442
  pkgs.name,
420
- projectId,
443
+ environmentId,
421
444
  packageData,
422
445
  repository,
423
446
  );
@@ -429,7 +452,7 @@ export class ProjectStore {
429
452
 
430
453
  private async updatePackage(
431
454
  packageName: string,
432
- projectId: string,
455
+ environmentId: string,
433
456
  packageData: {
434
457
  description: string | undefined;
435
458
  manifestPath: string;
@@ -438,7 +461,7 @@ export class ProjectStore {
438
461
  repository: ReturnType<typeof this.storageManager.getRepository>,
439
462
  ): Promise<void> {
440
463
  const existingPackage = await repository.getPackageByName(
441
- projectId,
464
+ environmentId,
442
465
  packageName,
443
466
  );
444
467
 
@@ -449,12 +472,12 @@ export class ProjectStore {
449
472
  }
450
473
 
451
474
  private async addConnections(
452
- project: Project,
453
- projectId: string,
475
+ environment: Environment,
476
+ environmentId: string,
454
477
  repository: ReturnType<typeof this.storageManager.getRepository>,
455
478
  ): Promise<void> {
456
479
  try {
457
- const connections = project.listApiConnections();
480
+ const connections = environment.listApiConnections();
458
481
  // Add/update connections
459
482
  for (const conn of connections) {
460
483
  if (!conn.name) {
@@ -464,26 +487,29 @@ export class ProjectStore {
464
487
 
465
488
  // Check if connection exists
466
489
  const existingConn = await repository.getConnectionByName(
467
- projectId,
490
+ environmentId,
468
491
  conn.name,
469
492
  );
470
493
 
471
494
  if (existingConn) {
472
- await this.updateConnection(conn, projectId, repository);
495
+ await this.updateConnection(conn, environmentId, repository);
473
496
  } else {
474
- await this.addConnection(conn, projectId, repository);
497
+ await this.addConnection(conn, environmentId, repository);
475
498
  }
476
499
  }
477
500
  } catch (err: unknown) {
478
501
  const error = err as Error;
479
- const projectName = project.metadata?.name;
480
- 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
+ );
481
507
  }
482
508
  }
483
509
 
484
510
  public async addConnection(
485
- conn: ReturnType<Project["listApiConnections"]>[number],
486
- projectId: string,
511
+ conn: ReturnType<Environment["listApiConnections"]>[number],
512
+ environmentId: string,
487
513
  repository: ReturnType<typeof this.storageManager.getRepository>,
488
514
  ): Promise<void> {
489
515
  if (!conn.name) {
@@ -492,7 +518,7 @@ export class ProjectStore {
492
518
  }
493
519
 
494
520
  const connectionData = {
495
- projectId,
521
+ environmentId,
496
522
  name: conn.name,
497
523
  type: conn.type as Connection["type"],
498
524
  config: conn,
@@ -509,8 +535,8 @@ export class ProjectStore {
509
535
  }
510
536
 
511
537
  public async updateConnection(
512
- conn: ReturnType<Project["listApiConnections"]>[number],
513
- projectId: string,
538
+ conn: ReturnType<Environment["listApiConnections"]>[number],
539
+ environmentId: string,
514
540
  repository: ReturnType<typeof this.storageManager.getRepository>,
515
541
  ): Promise<void> {
516
542
  if (!conn.name) {
@@ -518,12 +544,12 @@ export class ProjectStore {
518
544
  }
519
545
 
520
546
  const existingConn = await repository.getConnectionByName(
521
- projectId,
547
+ environmentId,
522
548
  conn.name,
523
549
  );
524
550
 
525
551
  if (!existingConn) {
526
- logger.error(`Connection "${conn.name}" not found in project`);
552
+ logger.error(`Connection "${conn.name}" not found in environment`);
527
553
  }
528
554
 
529
555
  const connectionData = {
@@ -544,31 +570,34 @@ export class ProjectStore {
544
570
  }
545
571
 
546
572
  public async addPackageToDatabase(
547
- projectName: string,
573
+ environmentName: string,
548
574
  packageName: string,
549
575
  ): Promise<void> {
550
- const project = await this.getProject(projectName, false);
576
+ const environment = await this.getEnvironment(environmentName, false);
551
577
  const repository = this.storageManager.getRepository();
552
578
 
553
- // Get the project ID from database
554
- const dbProject = await repository.getProjectByName(projectName);
579
+ // Get the environment ID from database
580
+ const dbEnvironment =
581
+ await repository.getEnvironmentByName(environmentName);
555
582
 
556
- if (!dbProject) {
557
- logger.error(`Project "${projectName}" not found in database`);
558
- 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
+ );
559
588
  }
560
589
 
561
- // Get the package from the project
562
- const packages = await project.listPackages();
590
+ // Get the package from the environment
591
+ const packages = await environment.listPackages();
563
592
  const pkg = packages.find((p) => p.name === packageName);
564
593
 
565
594
  if (!pkg) {
566
- logger.warn(`Package "${packageName}" not found in project`);
595
+ logger.warn(`Package "${packageName}" not found in environment`);
567
596
  return;
568
597
  }
569
598
 
570
599
  // Sync the specific package
571
- await this.addPackage(pkg, dbProject.id, repository);
600
+ await this.addPackage(pkg, dbEnvironment.id, repository);
572
601
  logger.info(`Synced package "${packageName}" to database`);
573
602
  }
574
603
 
@@ -576,22 +605,23 @@ export class ProjectStore {
576
605
  * Delete a package from the database
577
606
  */
578
607
  public async deletePackageFromDatabase(
579
- projectName: string,
608
+ environmentName: string,
580
609
  packageName: string,
581
610
  ): Promise<void> {
582
611
  const repository = this.storageManager.getRepository();
583
612
 
584
- // Get the project ID from database
585
- const dbProject = await repository.getProjectByName(projectName);
613
+ // Get the environment ID from database
614
+ const dbEnvironment =
615
+ await repository.getEnvironmentByName(environmentName);
586
616
 
587
- if (!dbProject) {
588
- logger.error(`Project "${projectName}" not found in database`);
617
+ if (!dbEnvironment) {
618
+ logger.error(`Environment "${environmentName}" not found in database`);
589
619
  return;
590
620
  }
591
621
 
592
622
  // Find and delete the package
593
623
  const existingPackage = await repository.getPackageByName(
594
- dbProject.id,
624
+ dbEnvironment.id,
595
625
  packageName,
596
626
  );
597
627
 
@@ -640,13 +670,13 @@ export class ProjectStore {
640
670
  await fs.promises.mkdir(uploadDocsPath, { recursive: true });
641
671
  }
642
672
 
643
- public async listProjects(skipInitializationCheck: boolean = false) {
673
+ public async listEnvironments(skipInitializationCheck: boolean = false) {
644
674
  if (!skipInitializationCheck) {
645
675
  await this.finishedInitialization;
646
676
  }
647
677
  return Promise.all(
648
- Array.from(this.projects.values()).map((project) =>
649
- project.serialize(),
678
+ Array.from(this.environments.values()).map((environment) =>
679
+ environment.serialize(),
650
680
  ),
651
681
  );
652
682
  }
@@ -654,23 +684,23 @@ export class ProjectStore {
654
684
  public async getStatus() {
655
685
  const status = {
656
686
  timestamp: Date.now(),
657
- projects: [] as Array<components["schemas"]["Project"]>,
687
+ environments: [] as Array<components["schemas"]["Environment"]>,
658
688
  initialized: this.isInitialized,
659
689
  frozenConfig: isPublisherConfigFrozen(this.serverRootPath),
660
690
  operationalState:
661
691
  getOperationalState() as components["schemas"]["ServerStatus"]["operationalState"],
662
692
  };
663
693
 
664
- const projects = await this.listProjects(true);
694
+ const environments = await this.listEnvironments(true);
665
695
 
666
696
  await Promise.all(
667
- projects.map(async (project) => {
697
+ environments.map(async (environment) => {
668
698
  try {
669
- const packages = project.packages;
670
- const connections = project.connections;
699
+ const packages = environment.packages;
700
+ const connections = environment.connections;
671
701
 
672
- logger.debug(`Project ${project.name} status:`, {
673
- connectionsCount: project.connections?.length || 0,
702
+ logger.debug(`Environment ${environment.name} status:`, {
703
+ connectionsCount: environment.connections?.length || 0,
674
704
  packagesCount: packages?.length || 0,
675
705
  });
676
706
 
@@ -681,12 +711,12 @@ export class ProjectStore {
681
711
  };
682
712
  });
683
713
 
684
- const _project = {
685
- ...project,
714
+ const _environment = {
715
+ ...environment,
686
716
  connections: _connections,
687
717
  };
688
- project.connections = _connections;
689
- status.projects.push(_project);
718
+ environment.connections = _connections;
719
+ status.environments.push(_environment);
690
720
  } catch (error) {
691
721
  logger.error("Error listing packages and connections", {
692
722
  error,
@@ -700,62 +730,63 @@ export class ProjectStore {
700
730
  return status;
701
731
  }
702
732
 
703
- public async getProject(
704
- projectName: string,
733
+ public async getEnvironment(
734
+ environmentName: string,
705
735
  reload: boolean = false,
706
- ): Promise<Project> {
736
+ ): Promise<Environment> {
707
737
  await this.finishedInitialization;
708
738
 
709
- // Check if project is already loaded first
710
- const project = this.projects.get(projectName);
711
- if (project !== undefined && !reload) {
712
- 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;
713
743
  }
714
744
 
715
745
  // We need to acquire the mutex to prevent concurrent requests from creating the
716
- // project multiple times.
717
- let projectMutex = this.projectMutexes.get(projectName);
718
- if (projectMutex?.isLocked()) {
719
- await projectMutex.waitForUnlock();
720
- const existingProject = this.projects.get(projectName);
721
- if (existingProject && !reload) {
722
- 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;
723
753
  }
724
754
  }
725
- projectMutex = new Mutex();
726
- this.projectMutexes.set(projectName, projectMutex);
755
+ environmentMutex = new Mutex();
756
+ this.environmentMutexes.set(environmentName, environmentMutex);
727
757
 
728
- return projectMutex.runExclusive(async () => {
758
+ return environmentMutex.runExclusive(async () => {
729
759
  // Double-check after acquiring mutex
730
- const existingProject = this.projects.get(projectName);
731
- if (existingProject !== undefined && !reload) {
732
- return existingProject;
760
+ const existingEnvironment = this.environments.get(environmentName);
761
+ if (existingEnvironment !== undefined && !reload) {
762
+ return existingEnvironment;
733
763
  }
734
764
 
735
- const projectManifest = await ProjectStore.reloadProjectManifest(
736
- this.serverRootPath,
737
- );
738
- const projectConfig = projectManifest.projects.find(
739
- (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,
740
771
  );
741
- const projectPath =
742
- existingProject?.metadata.location ||
743
- projectConfig?.packages[0]?.location;
744
- if (!projectPath) {
745
- throw new ProjectNotFoundError(
746
- `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.`,
747
778
  );
748
779
  }
749
- return await this.addProject({
750
- name: projectName,
751
- resource: `${API_PREFIX}/projects/${projectName}`,
752
- connections: projectConfig?.connections || [],
780
+ return await this.addEnvironment({
781
+ name: environmentName,
782
+ resource: `${API_PREFIX}/environments/${environmentName}`,
783
+ connections: environmentConfig?.connections || [],
753
784
  });
754
785
  });
755
786
  }
756
787
 
757
- public async addProject(
758
- project: ApiProject,
788
+ public async addEnvironment(
789
+ environment: ApiEnvironment,
759
790
  skipInitialization: boolean = false,
760
791
  ) {
761
792
  if (!skipInitialization) {
@@ -764,136 +795,151 @@ export class ProjectStore {
764
795
  if (!skipInitialization && this.publisherConfigIsFrozen) {
765
796
  throw new FrozenConfigError();
766
797
  }
767
- const projectName = project.name;
768
- if (!projectName) {
769
- throw new Error("Project name is required");
770
- }
771
- // Check if project already exists and update it instead of creating a new one
772
- const existingProject = this.projects.get(projectName);
773
- if (existingProject) {
774
- const updatedProject = await existingProject.update(project);
775
- this.projects.set(projectName, updatedProject);
776
- await this.addProjectToDatabase(updatedProject);
777
- return updatedProject;
778
- }
779
- const projectManifest = await ProjectStore.reloadProjectManifest(
780
- this.serverRootPath,
781
- );
782
- const projectConfig = projectManifest.projects.find(
783
- (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,
784
815
  );
785
816
  const hasPackages =
786
- (project?.packages && project.packages.length > 0) ||
787
- (projectConfig?.packages && projectConfig.packages.length > 0);
788
- let absoluteProjectPath: string;
817
+ (environment?.packages && environment.packages.length > 0) ||
818
+ (environmentConfig?.packages && environmentConfig.packages.length > 0);
819
+ let absoluteEnvironmentPath: string;
789
820
  if (hasPackages) {
790
821
  const packagesToProcess =
791
- project?.packages || projectConfig?.packages || [];
792
- absoluteProjectPath = await this.loadProjectIntoDisk(
793
- projectName,
822
+ environment?.packages || environmentConfig?.packages || [];
823
+ absoluteEnvironmentPath = await this.loadEnvironmentIntoDisk(
824
+ environmentName,
794
825
  packagesToProcess,
795
826
  );
796
- if (absoluteProjectPath.endsWith(".zip")) {
797
- absoluteProjectPath = await this.unzipProject(absoluteProjectPath);
827
+ if (absoluteEnvironmentPath.endsWith(".zip")) {
828
+ absoluteEnvironmentPath = await this.unzipEnvironment(
829
+ absoluteEnvironmentPath,
830
+ );
798
831
  }
799
832
  } else {
800
- absoluteProjectPath = await this.scaffoldProject(project);
833
+ absoluteEnvironmentPath = await this.scaffoldEnvironment(environment);
801
834
  }
802
- const newProject = await Project.create(
803
- projectName,
804
- absoluteProjectPath,
805
- project.connections || [],
835
+ const newEnvironment = await Environment.create(
836
+ environmentName,
837
+ absoluteEnvironmentPath,
838
+ environment.connections || [],
806
839
  );
807
840
 
808
- if (!newProject.metadata) newProject.metadata = {};
809
- newProject.metadata.location = absoluteProjectPath;
810
- if (project.materializationStorage !== undefined) {
811
- newProject.metadata.materializationStorage =
812
- 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;
813
846
  }
814
847
 
815
- this.projects.set(projectName, newProject);
848
+ this.environments.set(environmentName, newEnvironment);
816
849
 
817
- project?.packages?.forEach((_package) => {
850
+ environment?.packages?.forEach((_package) => {
818
851
  if (_package.name) {
819
- newProject.setPackageStatus(_package.name, PackageStatus.SERVING);
852
+ newEnvironment.setPackageStatus(
853
+ _package.name,
854
+ PackageStatus.SERVING,
855
+ );
820
856
  }
821
857
  });
822
858
 
823
- await this.addProjectToDatabase(newProject);
859
+ await this.addEnvironmentToDatabase(newEnvironment);
824
860
 
825
- return newProject;
861
+ return newEnvironment;
826
862
  }
827
863
 
828
- public async unzipProject(absoluteProjectPath: string) {
864
+ public async unzipEnvironment(absoluteEnvironmentPath: string) {
829
865
  logger.info(
830
- `Detected zip file at "${absoluteProjectPath}". Unzipping...`,
866
+ `Detected zip file at "${absoluteEnvironmentPath}". Unzipping...`,
831
867
  );
832
- const unzippedProjectPath = absoluteProjectPath.replace(".zip", "");
833
- await fs.promises.rm(unzippedProjectPath, {
868
+ const unzippedEnvironmentPath = absoluteEnvironmentPath.replace(
869
+ ".zip",
870
+ "",
871
+ );
872
+ await fs.promises.rm(unzippedEnvironmentPath, {
834
873
  recursive: true,
835
874
  force: true,
836
875
  });
837
- await fs.promises.mkdir(unzippedProjectPath, { recursive: true });
876
+ await fs.promises.mkdir(unzippedEnvironmentPath, { recursive: true });
838
877
 
839
- const zip = new AdmZip(absoluteProjectPath);
840
- zip.extractAllTo(unzippedProjectPath, true);
878
+ const zip = new AdmZip(absoluteEnvironmentPath);
879
+ zip.extractAllTo(unzippedEnvironmentPath, true);
841
880
 
842
- return unzippedProjectPath;
881
+ return unzippedEnvironmentPath;
843
882
  }
844
883
 
845
- public async updateProject(project: ApiProject) {
884
+ public async updateEnvironment(environment: ApiEnvironment) {
846
885
  await this.finishedInitialization;
847
886
  if (this.publisherConfigIsFrozen) {
848
887
  throw new FrozenConfigError();
849
888
  }
850
- validateProjectAzureUrls(project);
851
- const projectName = project.name;
852
- if (!projectName) {
853
- 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");
854
893
  }
855
- const existingProject = this.projects.get(projectName);
856
- if (!existingProject) {
857
- 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
+ );
858
899
  }
859
- const updatedProject = await existingProject.update(project);
860
- this.projects.set(projectName, updatedProject);
861
- await this.addProjectToDatabase(updatedProject);
862
- return updatedProject;
900
+ const updatedEnvironment = await existingEnvironment.update(environment);
901
+ this.environments.set(environmentName, updatedEnvironment);
902
+ await this.addEnvironmentToDatabase(updatedEnvironment);
903
+ return updatedEnvironment;
863
904
  }
864
905
 
865
- public async deleteProject(
866
- projectName: string,
867
- ): Promise<Project | undefined> {
906
+ public async deleteEnvironment(
907
+ environmentName: string,
908
+ ): Promise<Environment | undefined> {
868
909
  await this.finishedInitialization;
869
910
  if (this.publisherConfigIsFrozen) {
870
911
  throw new FrozenConfigError();
871
912
  }
872
- const project = this.projects.get(projectName);
873
- if (!project) {
913
+ const environment = this.environments.get(environmentName);
914
+ if (!environment) {
874
915
  return;
875
916
  }
876
917
 
877
- const projectPath = project.metadata?.location;
918
+ const environmentPath = environment.metadata?.location;
878
919
 
879
- // Close all connections before removing the project
880
- await project.closeAllConnections();
920
+ // Close all connections before removing the environment
921
+ await environment.closeAllConnections();
881
922
 
882
- this.projects.delete(projectName);
883
- await this.deleteProjectFromDatabase(projectName);
884
- if (projectPath) {
923
+ this.environments.delete(environmentName);
924
+ await this.deleteEnvironmentFromDatabase(environmentName);
925
+ if (environmentPath) {
885
926
  try {
886
- await fs.promises.rm(projectPath, { recursive: true, force: true });
887
- 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}`);
888
932
  } catch (err) {
889
- logger.error("Error removing project directory", { error: err });
933
+ logger.error("Error removing environment directory", {
934
+ error: err,
935
+ });
890
936
  }
891
937
  }
892
938
 
893
- return project;
939
+ return environment;
894
940
  }
895
941
 
896
- public static async reloadProjectManifest(
942
+ public static async reloadEnvironmentManifest(
897
943
  serverRootPath: string,
898
944
  ): Promise<ProcessedPublisherConfig> {
899
945
  try {
@@ -904,17 +950,17 @@ export class ProjectStore {
904
950
  `Error reading ${PUBLISHER_CONFIG_NAME}. Generating from directory`,
905
951
  { error },
906
952
  );
907
- return { frozenConfig: false, projects: [] };
953
+ return { frozenConfig: false, environments: [] };
908
954
  } else {
909
955
  // If publisher.config.json is missing, generate the manifest from directories
910
956
  try {
911
957
  const entries = await fs.promises.readdir(serverRootPath, {
912
958
  withFileTypes: true,
913
959
  });
914
- const projects: ProcessedProject[] = [];
960
+ const environments: ProcessedEnvironment[] = [];
915
961
  for (const entry of entries) {
916
962
  if (entry.isDirectory()) {
917
- projects.push({
963
+ environments.push({
918
964
  name: entry.name,
919
965
  packages: [
920
966
  {
@@ -926,31 +972,31 @@ export class ProjectStore {
926
972
  });
927
973
  }
928
974
  }
929
- return { frozenConfig: false, projects };
975
+ return { frozenConfig: false, environments };
930
976
  } catch (lsError) {
931
977
  logger.error(`Error listing directories in ${serverRootPath}`, {
932
978
  error: lsError,
933
979
  });
934
- return { frozenConfig: false, projects: [] };
980
+ return { frozenConfig: false, environments: [] };
935
981
  }
936
982
  }
937
983
  }
938
984
  }
939
985
 
940
- private async scaffoldProject(project: ApiProject) {
941
- const projectName = project.name;
942
- if (!projectName) {
943
- 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");
944
990
  }
945
- const absoluteProjectPath = `${this.serverRootPath}/${PUBLISHER_DATA_DIR}/${projectName}`;
946
- await fs.promises.mkdir(absoluteProjectPath, { recursive: true });
947
- if (project.readme) {
991
+ const absoluteEnvironmentPath = `${this.serverRootPath}/${PUBLISHER_DATA_DIR}/${environmentName}`;
992
+ await fs.promises.mkdir(absoluteEnvironmentPath, { recursive: true });
993
+ if (environment.readme) {
948
994
  await fs.promises.writeFile(
949
- path.join(absoluteProjectPath, "README.md"),
950
- project.readme,
995
+ path.join(absoluteEnvironmentPath, "README.md"),
996
+ environment.readme,
951
997
  );
952
998
  }
953
- return absoluteProjectPath;
999
+ return absoluteEnvironmentPath;
954
1000
  }
955
1001
 
956
1002
  private isLocalPath(location: string) {
@@ -978,17 +1024,17 @@ export class ProjectStore {
978
1024
  return location.startsWith("s3://");
979
1025
  }
980
1026
 
981
- private async loadProjectIntoDisk(
982
- projectName: string,
983
- packages: ApiProject["packages"],
1027
+ private async loadEnvironmentIntoDisk(
1028
+ environmentName: string,
1029
+ packages: ApiEnvironment["packages"],
984
1030
  ) {
985
- const absoluteTargetPath = `${this.serverRootPath}/${PUBLISHER_DATA_DIR}/${projectName}`;
1031
+ const absoluteTargetPath = `${this.serverRootPath}/${PUBLISHER_DATA_DIR}/${environmentName}`;
986
1032
 
987
1033
  await fs.promises.mkdir(absoluteTargetPath, { recursive: true });
988
1034
 
989
1035
  if (!packages || packages.length === 0) {
990
1036
  throw new PackageNotFoundError(
991
- `No packages found for project ${projectName}`,
1037
+ `No packages found for environment ${environmentName}`,
992
1038
  );
993
1039
  }
994
1040
 
@@ -1045,7 +1091,7 @@ export class ProjectStore {
1045
1091
  await this.downloadOrMountLocation(
1046
1092
  groupedLocation,
1047
1093
  tempDownloadPath,
1048
- projectName,
1094
+ environmentName,
1049
1095
  "shared",
1050
1096
  );
1051
1097
  // Extract each package from the downloaded content
@@ -1144,7 +1190,7 @@ export class ProjectStore {
1144
1190
  private async downloadOrMountLocation(
1145
1191
  location: string,
1146
1192
  targetPath: string,
1147
- projectName: string,
1193
+ environmentName: string,
1148
1194
  packageName: string,
1149
1195
  ) {
1150
1196
  const isCompressedFile = location.endsWith(".zip");
@@ -1156,7 +1202,7 @@ export class ProjectStore {
1156
1202
  );
1157
1203
  await this.downloadGcsDirectory(
1158
1204
  location,
1159
- projectName,
1205
+ environmentName,
1160
1206
  targetPath,
1161
1207
  isCompressedFile,
1162
1208
  );
@@ -1201,7 +1247,7 @@ export class ProjectStore {
1201
1247
  );
1202
1248
  await this.downloadS3Directory(
1203
1249
  location,
1204
- projectName,
1250
+ environmentName,
1205
1251
  targetPath,
1206
1252
  isCompressedFile,
1207
1253
  );
@@ -1230,7 +1276,7 @@ export class ProjectStore {
1230
1276
  await this.mountLocalDirectory(
1231
1277
  packagePath,
1232
1278
  targetPath,
1233
- projectName,
1279
+ environmentName,
1234
1280
  packageName,
1235
1281
  );
1236
1282
  return;
@@ -1248,40 +1294,40 @@ export class ProjectStore {
1248
1294
 
1249
1295
  // If we get here, the path format is not supported
1250
1296
  const errorMsg = `Invalid package path: "${location}". Must be an absolute mounted path or a GCS/S3/GitHub URI.`;
1251
- logger.error(errorMsg, { projectName, location });
1297
+ logger.error(errorMsg, { environmentName, location });
1252
1298
  throw new PackageNotFoundError(errorMsg);
1253
1299
  }
1254
1300
 
1255
1301
  public async mountLocalDirectory(
1256
- projectPath: string,
1302
+ environmentPath: string,
1257
1303
  absoluteTargetPath: string,
1258
- projectName: string,
1304
+ environmentName: string,
1259
1305
  packageName: string,
1260
1306
  ) {
1261
- if (projectPath.endsWith(".zip")) {
1262
- projectPath = await this.unzipProject(projectPath);
1307
+ if (environmentPath.endsWith(".zip")) {
1308
+ environmentPath = await this.unzipEnvironment(environmentPath);
1263
1309
  }
1264
- const projectDirExists =
1265
- (await fs.promises.stat(projectPath))?.isDirectory() ?? false;
1266
- if (projectDirExists) {
1310
+ const environmentDirExists =
1311
+ (await fs.promises.stat(environmentPath))?.isDirectory() ?? false;
1312
+ if (environmentDirExists) {
1267
1313
  await fs.promises.rm(absoluteTargetPath, {
1268
1314
  recursive: true,
1269
1315
  force: true,
1270
1316
  });
1271
1317
  await fs.promises.mkdir(absoluteTargetPath, { recursive: true });
1272
- await fs.promises.cp(projectPath, absoluteTargetPath, {
1318
+ await fs.promises.cp(environmentPath, absoluteTargetPath, {
1273
1319
  recursive: true,
1274
1320
  });
1275
1321
  } else {
1276
1322
  throw new PackageNotFoundError(
1277
- `Package ${packageName} for project ${projectName} not found in "${projectPath}"`,
1323
+ `Package ${packageName} for environment ${environmentName} not found in "${environmentPath}"`,
1278
1324
  );
1279
1325
  }
1280
1326
  }
1281
1327
 
1282
1328
  async downloadGcsDirectory(
1283
1329
  gcsPath: string,
1284
- projectName: string,
1330
+ environmentName: string,
1285
1331
  absoluteDirPath: string,
1286
1332
  isCompressedFile: boolean,
1287
1333
  ) {
@@ -1292,8 +1338,8 @@ export class ProjectStore {
1292
1338
  prefix,
1293
1339
  });
1294
1340
  if (files.length === 0) {
1295
- throw new ProjectNotFoundError(
1296
- `Project ${projectName} not found in ${gcsPath}`,
1341
+ throw new EnvironmentNotFoundError(
1342
+ `Environment ${environmentName} not found in ${gcsPath}`,
1297
1343
  );
1298
1344
  }
1299
1345
  if (!isCompressedFile) {
@@ -1324,14 +1370,14 @@ export class ProjectStore {
1324
1370
  }),
1325
1371
  );
1326
1372
  if (isCompressedFile) {
1327
- await this.unzipProject(absoluteDirPath);
1373
+ await this.unzipEnvironment(absoluteDirPath);
1328
1374
  }
1329
1375
  logger.info(`Downloaded GCS directory ${gcsPath} to ${absoluteDirPath}`);
1330
1376
  }
1331
1377
 
1332
1378
  async downloadS3Directory(
1333
1379
  s3Path: string,
1334
- projectName: string,
1380
+ environmentName: string,
1335
1381
  absoluteDirPath: string,
1336
1382
  isCompressedFile: boolean = false,
1337
1383
  ) {
@@ -1352,8 +1398,8 @@ export class ProjectStore {
1352
1398
  });
1353
1399
  const item = await this.s3Client.send(command);
1354
1400
  if (!item.Body) {
1355
- throw new ProjectNotFoundError(
1356
- `Project ${projectName} not found in ${s3Path}`,
1401
+ throw new EnvironmentNotFoundError(
1402
+ `Environment ${environmentName} not found in ${s3Path}`,
1357
1403
  );
1358
1404
  }
1359
1405
  const file = fs.createWriteStream(zipFilePath);
@@ -1364,7 +1410,7 @@ export class ProjectStore {
1364
1410
  });
1365
1411
 
1366
1412
  // Extract the zip file
1367
- await this.unzipProject(zipFilePath);
1413
+ await this.unzipEnvironment(zipFilePath);
1368
1414
  logger.info(`Downloaded S3 zip file ${s3Path} to ${absoluteDirPath}`);
1369
1415
  return;
1370
1416
  }
@@ -1378,8 +1424,8 @@ export class ProjectStore {
1378
1424
  await fs.promises.mkdir(absoluteDirPath, { recursive: true });
1379
1425
 
1380
1426
  if (!objects.Contents || objects.Contents.length === 0) {
1381
- throw new ProjectNotFoundError(
1382
- `Project ${projectName} not found in ${s3Path}`,
1427
+ throw new EnvironmentNotFoundError(
1428
+ `Environment ${environmentName} not found in ${s3Path}`,
1383
1429
  );
1384
1430
  }
1385
1431
  await Promise.all(