@malloy-publisher/server 0.0.90 → 0.0.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +1 -0
- package/build.ts +9 -0
- package/dist/app/api-doc.yaml +3 -0
- package/dist/app/assets/{index-2BGzlUQa.js → index-DYO_URL-.js} +100 -100
- package/dist/app/assets/{index-D1X7Y0Ve.js → index-Dg-zTLb3.js} +1 -1
- package/dist/app/assets/{index.es49-D9XPPIF9.js → index.es50-CDMydA2o.js} +1 -1
- package/dist/app/assets/mui-UpaxdnvH.js +159 -0
- package/dist/app/index.html +2 -2
- package/dist/server.js +307 -98
- package/eslint.config.mjs +8 -0
- package/package.json +2 -1
- package/publisher.config.json +25 -5
- package/src/config.ts +25 -2
- package/src/constants.ts +1 -1
- package/src/controller/package.controller.ts +1 -0
- package/src/controller/watch-mode.controller.ts +19 -4
- package/src/data_styles.ts +10 -3
- package/src/mcp/prompts/index.ts +9 -1
- package/src/mcp/resources/package_resource.ts +2 -1
- package/src/mcp/resources/source_resource.ts +1 -0
- package/src/mcp/resources/view_resource.ts +1 -0
- package/src/server.ts +9 -5
- package/src/service/connection.ts +17 -4
- package/src/service/db_utils.ts +19 -11
- package/src/service/model.ts +2 -0
- package/src/service/package.spec.ts +76 -54
- package/src/service/project.ts +160 -45
- package/src/service/project_store.spec.ts +477 -165
- package/src/service/project_store.ts +319 -69
- package/src/service/scheduler.ts +3 -2
- package/src/utils.ts +0 -1
- package/tests/harness/e2e.ts +60 -58
- package/tests/harness/uris.ts +21 -24
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +10 -0
- package/dist/app/assets/mui-YektUyEU.js +0 -161
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { GetObjectCommand, S3 } from "@aws-sdk/client-s3";
|
|
2
2
|
import { Storage } from "@google-cloud/storage";
|
|
3
|
+
import AdmZip from "adm-zip";
|
|
3
4
|
import * as fs from "fs";
|
|
4
5
|
import * as path from "path";
|
|
5
|
-
import AdmZip from "adm-zip";
|
|
6
6
|
import simpleGit from "simple-git";
|
|
7
7
|
import { Writable } from "stream";
|
|
8
8
|
import { components } from "../api";
|
|
9
9
|
import { getPublisherConfig, isPublisherConfigFrozen } from "../config";
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import {
|
|
11
|
+
API_PREFIX,
|
|
12
|
+
CONNECTIONS_MANIFEST_NAME,
|
|
13
|
+
PUBLISHER_CONFIG_NAME,
|
|
14
|
+
publisherPath,
|
|
15
|
+
} from "../constants";
|
|
16
|
+
import {
|
|
17
|
+
FrozenConfigError,
|
|
18
|
+
PackageNotFoundError,
|
|
19
|
+
ProjectNotFoundError,
|
|
20
|
+
} from "../errors";
|
|
12
21
|
import { logger } from "../logger";
|
|
13
22
|
import { Project } from "./project";
|
|
14
23
|
type ApiProject = components["schemas"]["Project"];
|
|
@@ -18,13 +27,16 @@ export class ProjectStore {
|
|
|
18
27
|
private projects: Map<string, Project> = new Map();
|
|
19
28
|
public publisherConfigIsFrozen: boolean;
|
|
20
29
|
public finishedInitialization: Promise<void>;
|
|
30
|
+
private isInitialized: boolean = false;
|
|
21
31
|
private s3Client = new S3({
|
|
22
32
|
followRegionRedirects: true,
|
|
23
33
|
});
|
|
24
|
-
private gcsClient
|
|
34
|
+
private gcsClient: Storage;
|
|
25
35
|
|
|
26
36
|
constructor(serverRootPath: string) {
|
|
27
37
|
this.serverRootPath = serverRootPath;
|
|
38
|
+
this.gcsClient = new Storage();
|
|
39
|
+
|
|
28
40
|
this.finishedInitialization = this.initialize();
|
|
29
41
|
}
|
|
30
42
|
|
|
@@ -39,19 +51,19 @@ export class ProjectStore {
|
|
|
39
51
|
);
|
|
40
52
|
logger.info(`Initializing project store.`);
|
|
41
53
|
await Promise.all(
|
|
42
|
-
|
|
43
|
-
logger.info(`Adding project "${
|
|
44
|
-
const
|
|
54
|
+
projectManifest.projects.map(async (project) => {
|
|
55
|
+
logger.info(`Adding project "${project.name}"`);
|
|
56
|
+
const projectInstance = await this.addProject(
|
|
45
57
|
{
|
|
46
|
-
name:
|
|
47
|
-
resource: `${API_PREFIX}/projects/${
|
|
48
|
-
location: projectManifest.projects[projectName],
|
|
58
|
+
name: project.name,
|
|
59
|
+
resource: `${API_PREFIX}/projects/${project.name}`,
|
|
49
60
|
},
|
|
50
61
|
true,
|
|
51
62
|
);
|
|
52
|
-
return
|
|
63
|
+
return projectInstance.listPackages();
|
|
53
64
|
}),
|
|
54
65
|
);
|
|
66
|
+
this.isInitialized = true;
|
|
55
67
|
logger.info(
|
|
56
68
|
`Project store successfully initialized in ${performance.now() - initialTime}ms`,
|
|
57
69
|
);
|
|
@@ -62,8 +74,10 @@ export class ProjectStore {
|
|
|
62
74
|
}
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
public async listProjects() {
|
|
66
|
-
|
|
77
|
+
public async listProjects(skipInitializationCheck: boolean = false) {
|
|
78
|
+
if (!skipInitializationCheck) {
|
|
79
|
+
await this.finishedInitialization;
|
|
80
|
+
}
|
|
67
81
|
return Promise.all(
|
|
68
82
|
Array.from(this.projects.values()).map((project) =>
|
|
69
83
|
project.serialize(),
|
|
@@ -71,6 +85,52 @@ export class ProjectStore {
|
|
|
71
85
|
);
|
|
72
86
|
}
|
|
73
87
|
|
|
88
|
+
public async getStatus() {
|
|
89
|
+
const status = {
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
projects: [] as Array<components["schemas"]["Project"]>,
|
|
92
|
+
initialized: this.isInitialized,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const projects = await this.listProjects(true);
|
|
96
|
+
|
|
97
|
+
await Promise.all(
|
|
98
|
+
projects.map(async (project) => {
|
|
99
|
+
try {
|
|
100
|
+
const packages = project.packages;
|
|
101
|
+
const connections = project.connections;
|
|
102
|
+
|
|
103
|
+
logger.info(`Project ${project.name} status:`, {
|
|
104
|
+
connectionsCount: project.connections?.length || 0,
|
|
105
|
+
packagesCount: packages?.length || 0,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const _connections = connections?.map((connection) => {
|
|
109
|
+
return {
|
|
110
|
+
...connection,
|
|
111
|
+
attributes: undefined,
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const _project = {
|
|
116
|
+
...project,
|
|
117
|
+
connections: _connections,
|
|
118
|
+
};
|
|
119
|
+
project.connections = _connections;
|
|
120
|
+
status.projects.push(_project);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
logger.error("Error listing packages and connections", {
|
|
123
|
+
error,
|
|
124
|
+
});
|
|
125
|
+
throw new Error(
|
|
126
|
+
"Error listing packages and connections: " + error,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
return status;
|
|
132
|
+
}
|
|
133
|
+
|
|
74
134
|
public async getProject(
|
|
75
135
|
projectName: string,
|
|
76
136
|
reload: boolean = false,
|
|
@@ -81,8 +141,11 @@ export class ProjectStore {
|
|
|
81
141
|
const projectManifest = await ProjectStore.reloadProjectManifest(
|
|
82
142
|
this.serverRootPath,
|
|
83
143
|
);
|
|
144
|
+
const projectConfig = projectManifest.projects.find(
|
|
145
|
+
(p) => p.name === projectName,
|
|
146
|
+
);
|
|
84
147
|
const projectPath =
|
|
85
|
-
project?.metadata.location ||
|
|
148
|
+
project?.metadata.location || projectConfig?.packages[0]?.location;
|
|
86
149
|
if (!projectPath) {
|
|
87
150
|
throw new ProjectNotFoundError(
|
|
88
151
|
`Project "${projectName}" could not be resolved to a path.`,
|
|
@@ -111,16 +174,34 @@ export class ProjectStore {
|
|
|
111
174
|
if (!projectName) {
|
|
112
175
|
throw new Error("Project name is required");
|
|
113
176
|
}
|
|
177
|
+
|
|
178
|
+
// Check if project already exists and update it instead of creating a new one
|
|
179
|
+
const existingProject = this.projects.get(projectName);
|
|
180
|
+
if (existingProject) {
|
|
181
|
+
logger.info(`Project ${projectName} already exists, updating it`);
|
|
182
|
+
const updatedProject = await existingProject.update(project);
|
|
183
|
+
this.projects.set(projectName, updatedProject);
|
|
184
|
+
return updatedProject;
|
|
185
|
+
}
|
|
186
|
+
|
|
114
187
|
const projectManifest = await ProjectStore.reloadProjectManifest(
|
|
115
188
|
this.serverRootPath,
|
|
116
189
|
);
|
|
117
|
-
const
|
|
118
|
-
|
|
190
|
+
const projectConfig = projectManifest.projects.find(
|
|
191
|
+
(p) => p.name === projectName,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const hasPackages =
|
|
195
|
+
(project?.packages && project.packages.length > 0) ||
|
|
196
|
+
(projectConfig?.packages && projectConfig.packages.length > 0);
|
|
119
197
|
let absoluteProjectPath: string;
|
|
120
|
-
if (
|
|
198
|
+
if (hasPackages) {
|
|
199
|
+
const packagesToProcess =
|
|
200
|
+
project?.packages || projectConfig?.packages || [];
|
|
121
201
|
absoluteProjectPath = await this.loadProjectIntoDisk(
|
|
122
202
|
projectName,
|
|
123
|
-
|
|
203
|
+
projectName,
|
|
204
|
+
packagesToProcess,
|
|
124
205
|
);
|
|
125
206
|
if (absoluteProjectPath.endsWith(".zip")) {
|
|
126
207
|
absoluteProjectPath = await this.unzipProject(absoluteProjectPath);
|
|
@@ -179,7 +260,7 @@ export class ProjectStore {
|
|
|
179
260
|
}
|
|
180
261
|
const project = this.projects.get(projectName);
|
|
181
262
|
if (!project) {
|
|
182
|
-
|
|
263
|
+
return;
|
|
183
264
|
}
|
|
184
265
|
this.projects.delete(projectName);
|
|
185
266
|
return project;
|
|
@@ -194,17 +275,31 @@ export class ProjectStore {
|
|
|
194
275
|
`Error reading ${PUBLISHER_CONFIG_NAME}. Generating from directory`,
|
|
195
276
|
{ error },
|
|
196
277
|
);
|
|
197
|
-
return { projects:
|
|
278
|
+
return { projects: [] };
|
|
198
279
|
} else {
|
|
199
280
|
// If publisher.config.json is missing, generate the manifest from directories
|
|
200
281
|
try {
|
|
201
282
|
const entries = await fs.promises.readdir(serverRootPath, {
|
|
202
283
|
withFileTypes: true,
|
|
203
284
|
});
|
|
204
|
-
const projects: {
|
|
285
|
+
const projects: {
|
|
286
|
+
name: string;
|
|
287
|
+
packages: {
|
|
288
|
+
name: string;
|
|
289
|
+
location: string;
|
|
290
|
+
}[];
|
|
291
|
+
}[] = [];
|
|
205
292
|
for (const entry of entries) {
|
|
206
293
|
if (entry.isDirectory()) {
|
|
207
|
-
projects
|
|
294
|
+
projects.push({
|
|
295
|
+
name: entry.name,
|
|
296
|
+
packages: [
|
|
297
|
+
{
|
|
298
|
+
name: entry.name,
|
|
299
|
+
location: entry.name,
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
});
|
|
208
303
|
}
|
|
209
304
|
}
|
|
210
305
|
return { projects };
|
|
@@ -212,7 +307,7 @@ export class ProjectStore {
|
|
|
212
307
|
logger.error(`Error listing directories in ${serverRootPath}`, {
|
|
213
308
|
error: lsError,
|
|
214
309
|
});
|
|
215
|
-
return { projects:
|
|
310
|
+
return { projects: [] };
|
|
216
311
|
}
|
|
217
312
|
}
|
|
218
313
|
}
|
|
@@ -234,90 +329,245 @@ export class ProjectStore {
|
|
|
234
329
|
return absoluteProjectPath;
|
|
235
330
|
}
|
|
236
331
|
|
|
237
|
-
private async loadProjectIntoDisk(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
332
|
+
private async loadProjectIntoDisk(
|
|
333
|
+
projectName: string,
|
|
334
|
+
projectPath: string,
|
|
335
|
+
packages: ApiProject["packages"],
|
|
336
|
+
) {
|
|
337
|
+
const absoluteTargetPath = `${publisherPath}/${projectPath}`;
|
|
338
|
+
|
|
339
|
+
if (!packages || packages.length === 0) {
|
|
340
|
+
throw new PackageNotFoundError(
|
|
341
|
+
`No packages found for project ${projectName}`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Group packages by location to optimize downloads
|
|
346
|
+
const locationGroups = new Map<
|
|
347
|
+
string,
|
|
348
|
+
Array<{ name: string; location: string }>
|
|
349
|
+
>();
|
|
350
|
+
|
|
351
|
+
for (const _package of packages) {
|
|
352
|
+
if (!_package.name) {
|
|
353
|
+
throw new PackageNotFoundError(`Package has no name specified`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!_package.location) {
|
|
357
|
+
throw new PackageNotFoundError(
|
|
358
|
+
`Package ${_package.name} has no location specified`,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const location = _package.location;
|
|
363
|
+
const packageName = _package.name;
|
|
364
|
+
|
|
365
|
+
if (!locationGroups.has(location)) {
|
|
366
|
+
locationGroups.set(location, []);
|
|
367
|
+
}
|
|
368
|
+
locationGroups.get(location)!.push({ name: packageName, location });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Processing by each unique location
|
|
372
|
+
for (const [location, packagesForLocation] of locationGroups) {
|
|
373
|
+
// Create a temporary directory for the shared download
|
|
374
|
+
const tempDownloadPath = `${absoluteTargetPath}/.temp_${Buffer.from(
|
|
375
|
+
location,
|
|
376
|
+
)
|
|
377
|
+
.toString("base64")
|
|
378
|
+
.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
379
|
+
await fs.promises.mkdir(tempDownloadPath, { recursive: true });
|
|
380
|
+
|
|
241
381
|
try {
|
|
242
|
-
|
|
243
|
-
await this.
|
|
244
|
-
|
|
245
|
-
|
|
382
|
+
// Download the entire location once
|
|
383
|
+
await this.downloadOrMountLocation(
|
|
384
|
+
location,
|
|
385
|
+
tempDownloadPath,
|
|
246
386
|
projectName,
|
|
387
|
+
"shared",
|
|
247
388
|
);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
389
|
+
|
|
390
|
+
// Extract each package from the downloaded content
|
|
391
|
+
for (const _package of packagesForLocation) {
|
|
392
|
+
const packageDir = _package.name;
|
|
393
|
+
const absolutePackagePath = `${absoluteTargetPath}/${packageDir}`;
|
|
394
|
+
|
|
395
|
+
// Check if the package directory exists in the downloaded content
|
|
396
|
+
const packagePathInDownload = path.join(
|
|
397
|
+
tempDownloadPath,
|
|
398
|
+
packageDir,
|
|
399
|
+
);
|
|
400
|
+
const packageExists = await fs.promises
|
|
401
|
+
.access(packagePathInDownload)
|
|
402
|
+
.then(() => true)
|
|
403
|
+
.catch(() => false);
|
|
404
|
+
|
|
405
|
+
if (packageExists) {
|
|
406
|
+
// Copy the specific package directory
|
|
407
|
+
await fs.promises.mkdir(absolutePackagePath, {
|
|
408
|
+
recursive: true,
|
|
409
|
+
});
|
|
410
|
+
await fs.promises.cp(
|
|
411
|
+
packagePathInDownload,
|
|
412
|
+
absolutePackagePath,
|
|
413
|
+
{ recursive: true },
|
|
414
|
+
);
|
|
415
|
+
logger.info(
|
|
416
|
+
`Extracted package "${packageDir}" from shared download`,
|
|
417
|
+
);
|
|
418
|
+
} else {
|
|
419
|
+
// If package directory doesn't exist, copy the entire download as the package
|
|
420
|
+
// This handles cases where the location itself is the package
|
|
421
|
+
await fs.promises.mkdir(absolutePackagePath, {
|
|
422
|
+
recursive: true,
|
|
423
|
+
});
|
|
424
|
+
await fs.promises.cp(tempDownloadPath, absolutePackagePath, {
|
|
425
|
+
recursive: true,
|
|
426
|
+
});
|
|
427
|
+
logger.info(
|
|
428
|
+
`Copied entire download as package "${packageDir}"`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const connectionsFileInDownload = path.join(
|
|
434
|
+
tempDownloadPath,
|
|
435
|
+
CONNECTIONS_MANIFEST_NAME,
|
|
436
|
+
);
|
|
437
|
+
const connectionsFileInProject = path.join(
|
|
438
|
+
absoluteTargetPath,
|
|
439
|
+
CONNECTIONS_MANIFEST_NAME,
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
await fs.promises.access(
|
|
444
|
+
connectionsFileInDownload,
|
|
445
|
+
fs.constants.F_OK,
|
|
446
|
+
);
|
|
447
|
+
await fs.promises.cp(
|
|
448
|
+
connectionsFileInDownload,
|
|
449
|
+
connectionsFileInProject,
|
|
450
|
+
);
|
|
451
|
+
logger.info(
|
|
452
|
+
`Copied ${CONNECTIONS_MANIFEST_NAME} to project directory`,
|
|
453
|
+
);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.error(error);
|
|
456
|
+
logger.info(`No ${CONNECTIONS_MANIFEST_NAME} found`);
|
|
457
|
+
}
|
|
458
|
+
} finally {
|
|
459
|
+
// Clean up temporary download directory
|
|
460
|
+
await fs.promises.rm(tempDownloadPath, {
|
|
461
|
+
recursive: true,
|
|
462
|
+
force: true,
|
|
252
463
|
});
|
|
253
|
-
throw error;
|
|
254
464
|
}
|
|
255
465
|
}
|
|
256
466
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
467
|
+
return absoluteTargetPath;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private async downloadOrMountLocation(
|
|
471
|
+
location: string,
|
|
472
|
+
targetPath: string,
|
|
473
|
+
projectName: string,
|
|
474
|
+
packageName: string,
|
|
475
|
+
) {
|
|
476
|
+
// Handle absolute paths
|
|
477
|
+
if (location.startsWith("/")) {
|
|
260
478
|
try {
|
|
261
479
|
logger.info(
|
|
262
|
-
`
|
|
480
|
+
`Mounting local directory at "${location}" to "${targetPath}"`,
|
|
263
481
|
);
|
|
264
|
-
await this.
|
|
265
|
-
|
|
482
|
+
await this.mountLocalDirectory(
|
|
483
|
+
location,
|
|
484
|
+
targetPath,
|
|
266
485
|
projectName,
|
|
267
|
-
|
|
486
|
+
packageName,
|
|
268
487
|
);
|
|
488
|
+
return;
|
|
269
489
|
} catch (error) {
|
|
270
|
-
logger.error(`Failed to
|
|
490
|
+
logger.error(`Failed to mount local directory "${location}"`, {
|
|
271
491
|
error,
|
|
272
492
|
});
|
|
273
|
-
throw
|
|
493
|
+
throw new PackageNotFoundError(
|
|
494
|
+
`Failed to mount local directory: ${location}`,
|
|
495
|
+
);
|
|
274
496
|
}
|
|
275
|
-
return absoluteTargetPath;
|
|
276
497
|
}
|
|
277
498
|
|
|
278
|
-
// Handle
|
|
279
|
-
if (
|
|
499
|
+
// Handle GCS paths
|
|
500
|
+
if (location.startsWith("gs://")) {
|
|
280
501
|
try {
|
|
281
|
-
logger.info(
|
|
282
|
-
|
|
283
|
-
projectPath,
|
|
284
|
-
projectName,
|
|
285
|
-
absoluteTargetPath,
|
|
502
|
+
logger.info(
|
|
503
|
+
`Downloading GCS directory from "${location}" to "${targetPath}"`,
|
|
286
504
|
);
|
|
287
|
-
|
|
505
|
+
await this.downloadGcsDirectory(location, projectName, targetPath);
|
|
506
|
+
return;
|
|
288
507
|
} catch (error) {
|
|
289
|
-
logger.error(`Failed to
|
|
290
|
-
|
|
508
|
+
logger.error(`Failed to download GCS directory "${location}"`, {
|
|
509
|
+
error,
|
|
510
|
+
});
|
|
511
|
+
throw new PackageNotFoundError(
|
|
512
|
+
`Failed to download GCS directory: ${location}`,
|
|
513
|
+
);
|
|
291
514
|
}
|
|
292
515
|
}
|
|
293
516
|
|
|
294
|
-
// Handle GitHub
|
|
517
|
+
// Handle GitHub URLs
|
|
295
518
|
if (
|
|
296
|
-
|
|
297
|
-
|
|
519
|
+
location.startsWith("https://github.com/") ||
|
|
520
|
+
location.startsWith("git@")
|
|
298
521
|
) {
|
|
299
522
|
try {
|
|
300
|
-
logger.info(
|
|
301
|
-
|
|
302
|
-
|
|
523
|
+
logger.info(
|
|
524
|
+
`Cloning GitHub repository from "${location}" to "${targetPath}"`,
|
|
525
|
+
);
|
|
526
|
+
await this.downloadGitHubDirectory(location, targetPath);
|
|
527
|
+
return;
|
|
303
528
|
} catch (error) {
|
|
304
|
-
logger.error(`Failed to
|
|
529
|
+
logger.error(`Failed to clone GitHub repository "${location}"`, {
|
|
305
530
|
error,
|
|
306
531
|
});
|
|
307
|
-
throw
|
|
532
|
+
throw new PackageNotFoundError(
|
|
533
|
+
`Failed to clone GitHub repository: ${location}`,
|
|
534
|
+
);
|
|
308
535
|
}
|
|
309
536
|
}
|
|
310
537
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
538
|
+
// Handle S3 paths
|
|
539
|
+
if (location.startsWith("s3://")) {
|
|
540
|
+
try {
|
|
541
|
+
logger.info(
|
|
542
|
+
`Downloading S3 directory from "${location}" to "${targetPath}"`,
|
|
543
|
+
);
|
|
544
|
+
await this.downloadS3Directory(location, projectName, targetPath);
|
|
545
|
+
return;
|
|
546
|
+
} catch (error) {
|
|
547
|
+
logger.error(`Failed to download S3 directory "${location}"`, {
|
|
548
|
+
error,
|
|
549
|
+
});
|
|
550
|
+
throw new PackageNotFoundError(
|
|
551
|
+
`Failed to download S3 directory: ${location}`,
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// If we get here, the path format is not supported
|
|
557
|
+
const errorMsg = `Invalid package path: "${location}". Must be an absolute mounted path or a GCS/S3/GitHub URI.`;
|
|
558
|
+
logger.error(errorMsg, { projectName, location });
|
|
559
|
+
throw new PackageNotFoundError(errorMsg);
|
|
314
560
|
}
|
|
315
561
|
|
|
316
562
|
public async mountLocalDirectory(
|
|
317
563
|
projectPath: string,
|
|
318
564
|
absoluteTargetPath: string,
|
|
319
565
|
projectName: string,
|
|
566
|
+
packageName: string,
|
|
320
567
|
) {
|
|
568
|
+
if (projectPath.endsWith(".zip")) {
|
|
569
|
+
projectPath = await this.unzipProject(projectPath);
|
|
570
|
+
}
|
|
321
571
|
const projectDirExists = (
|
|
322
572
|
await fs.promises.stat(projectPath)
|
|
323
573
|
).isDirectory();
|
|
@@ -331,8 +581,8 @@ export class ProjectStore {
|
|
|
331
581
|
recursive: true,
|
|
332
582
|
});
|
|
333
583
|
} else {
|
|
334
|
-
throw new
|
|
335
|
-
`
|
|
584
|
+
throw new PackageNotFoundError(
|
|
585
|
+
`Package ${packageName} for project ${projectName} not found in "${projectPath}"`,
|
|
336
586
|
);
|
|
337
587
|
}
|
|
338
588
|
}
|
package/src/service/scheduler.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { components } from "../api";
|
|
|
3
3
|
import { logger } from "../logger";
|
|
4
4
|
import { Model } from "./model";
|
|
5
5
|
|
|
6
|
+
// @ts-expect-error TODO: Fix missing Source type in API
|
|
6
7
|
type ApiSource = components["schemas"]["Source"];
|
|
7
8
|
type ApiView = components["schemas"]["View"];
|
|
8
9
|
type ApiQuery = components["schemas"]["Query"];
|
|
@@ -162,8 +163,8 @@ export class Scheduler {
|
|
|
162
163
|
|
|
163
164
|
models.forEach((m) => {
|
|
164
165
|
m.getSources()?.forEach((s) => {
|
|
165
|
-
s.views?.forEach((v) => {
|
|
166
|
-
v.annotations?.forEach((a) => {
|
|
166
|
+
s.views?.forEach((v: ApiView) => {
|
|
167
|
+
v.annotations?.forEach((a: string) => {
|
|
167
168
|
if (a.startsWith(SCHEDULE_ANNOTATION)) {
|
|
168
169
|
schedules.push(new Schedule(m, s, v, undefined, a));
|
|
169
170
|
}
|
package/src/utils.ts
CHANGED