@malloy-publisher/server 0.0.204 → 0.0.206
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/build.ts +10 -1
- package/dist/app/api-doc.yaml +494 -397
- package/dist/app/assets/{EnvironmentPage-CX06cjOF.js → EnvironmentPage-BYwBeC2F.js} +1 -1
- package/dist/app/assets/HomePage-ivu4vdpj.js +1 -0
- package/dist/app/assets/{MainPage-nUJ9YatG.js → MainPage-B2DnHEDU.js} +2 -2
- package/dist/app/assets/MaterializationsPage-BZEuwF9P.js +1 -0
- package/dist/app/assets/ModelPage-Dpu3bfPg.js +1 -0
- package/dist/app/assets/{PackagePage-BaEVdEAG.js → PackagePage-B8PwcRHt.js} +1 -1
- package/dist/app/assets/{RouteError-BShQjZio.js → RouteError-BhbywAeC.js} +1 -1
- package/dist/app/assets/{WorkbookPage-CBn6ZjJW.js → WorkbookPage-C-JXsJG0.js} +1 -1
- package/dist/app/assets/{core-DECXYL4E.es-OaRfXwuQ.js → core-pPlPr7jK.es-CNEOlxKB.js} +1 -1
- package/dist/app/assets/{index-BLfPC1gy.js → index-BHEm8Egc.js} +1 -1
- package/dist/app/assets/{index-Dy3YhAZQ.js → index-BsvDrV14.js} +1 -1
- package/dist/app/assets/{index-DqiJ0bWp.js → index-ChR1fKR2.js} +134 -134
- package/dist/app/assets/{index.umd-DAN9K8yC.js → index.umd-BVLPYNuj.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/instrumentation.mjs +18 -8
- package/dist/package_load_worker.mjs +19 -2
- package/dist/runtime/publisher.js +318 -0
- package/dist/server.mjs +1703 -1443
- package/package.json +5 -4
- package/scripts/bake-duckdb-extensions.js +104 -0
- package/src/constants.ts +12 -0
- package/src/controller/materialization.controller.ts +79 -35
- package/src/controller/package.controller.spec.ts +179 -0
- package/src/controller/package.controller.ts +60 -73
- package/src/controller/watch-mode.controller.ts +176 -46
- package/src/dto/package.dto.ts +16 -1
- package/src/errors.spec.ts +33 -0
- package/src/errors.ts +18 -0
- package/src/health.spec.ts +34 -1
- package/src/instrumentation.ts +33 -17
- package/src/materialization_metrics.ts +66 -0
- package/src/mcp/error_messages.spec.ts +35 -0
- package/src/mcp/error_messages.ts +14 -1
- package/src/mcp/handler_utils.ts +12 -0
- package/src/package_load/package_load_pool.ts +7 -1
- package/src/package_load/package_load_worker.ts +44 -4
- package/src/package_load/protocol.ts +7 -1
- package/src/runtime/publisher.js +318 -0
- package/src/server-old.ts +7 -149
- package/src/server.ts +488 -190
- package/src/service/authorize_integration.spec.ts +163 -2
- package/src/service/compile_authorize.spec.ts +85 -0
- package/src/service/environment.ts +270 -12
- package/src/service/environment_store.spec.ts +0 -81
- package/src/service/environment_store.ts +142 -34
- package/src/service/explore_visibility.spec.ts +434 -0
- package/src/service/exports_probe.spec.ts +107 -0
- package/src/service/manifest_loader.spec.ts +99 -0
- package/src/service/manifest_loader.ts +95 -0
- package/src/service/materialization_service.spec.ts +324 -512
- package/src/service/materialization_service.ts +816 -656
- package/src/service/model.ts +444 -13
- package/src/service/package.spec.ts +14 -2
- package/src/service/package.ts +271 -20
- package/src/service/package_rollback.spec.ts +190 -0
- package/src/service/package_worker_path.spec.ts +223 -0
- package/src/service/query_boundary.spec.ts +470 -0
- package/src/storage/DatabaseInterface.ts +35 -57
- package/src/storage/StorageManager.mock.ts +0 -9
- package/src/storage/StorageManager.ts +7 -290
- package/src/storage/duckdb/DuckDBConnection.ts +70 -124
- package/src/storage/duckdb/DuckDBRepository.ts +2 -35
- package/src/storage/duckdb/MaterializationRepository.ts +52 -27
- package/src/storage/duckdb/schema.ts +4 -20
- package/tests/fixtures/authorize-compile/model.malloy +9 -0
- package/tests/fixtures/authorize-compile/publisher.json +4 -0
- package/tests/fixtures/html-pages-nopublic/model.malloy +1 -0
- package/tests/fixtures/html-pages-nopublic/publisher.json +5 -0
- package/tests/fixtures/html-pages-test/data.csv +3 -0
- package/tests/fixtures/html-pages-test/public/assets/app.css +3 -0
- package/tests/fixtures/html-pages-test/public/data.json +1 -0
- package/tests/fixtures/html-pages-test/public/index.html +9 -0
- package/tests/fixtures/html-pages-test/public/sub/page2.html +9 -0
- package/tests/fixtures/html-pages-test/publisher.json +5 -0
- package/tests/fixtures/html-pages-test/report.malloy +1 -0
- package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +92 -0
- package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +138 -0
- package/tests/integration/html_pages/html_pages.integration.spec.ts +378 -0
- package/tests/integration/materialization/manifest_binding.integration.spec.ts +175 -0
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +277 -266
- package/tests/integration/watch-mode/watch_mode.integration.spec.ts +421 -0
- package/tests/unit/duckdb/attached_databases.test.ts +111 -0
- package/tests/unit/duckdb/duckdb_connection.test.ts +181 -0
- package/tests/unit/duckdb/legacy_schema_migration.test.ts +7 -4
- package/tests/unit/duckdb/repositories.test.ts +208 -0
- package/dist/app/assets/HomePage-CNFt_eUU.js +0 -1
- package/dist/app/assets/MaterializationsPage-B5goxVXW.js +0 -1
- package/dist/app/assets/ModelPage-Ba7Xh4lL.js +0 -1
- package/src/controller/manifest.controller.ts +0 -38
- package/src/service/manifest_service.spec.ts +0 -206
- package/src/service/manifest_service.ts +0 -117
- package/src/service/materialized_table_gc.spec.ts +0 -384
- package/src/service/materialized_table_gc.ts +0 -231
- package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
- package/src/storage/duckdb/ManifestRepository.ts +0 -120
- package/src/storage/duckdb/manifest_store.spec.ts +0 -133
- package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -155
- package/tests/unit/storage/StorageManager.test.ts +0 -166
|
@@ -11,12 +11,6 @@ import { EnvironmentStore } from "./environment_store";
|
|
|
11
11
|
|
|
12
12
|
type MockData = Record<string, unknown>;
|
|
13
13
|
|
|
14
|
-
const initializeDuckLakeCalls: Array<{
|
|
15
|
-
environmentId: string;
|
|
16
|
-
environmentName: string;
|
|
17
|
-
config: { catalogUrl: string; dataPath: string };
|
|
18
|
-
}> = [];
|
|
19
|
-
|
|
20
14
|
mock.module("../storage/StorageManager", () => {
|
|
21
15
|
return {
|
|
22
16
|
StorageManager: class MockStorageManager {
|
|
@@ -24,18 +18,6 @@ mock.module("../storage/StorageManager", () => {
|
|
|
24
18
|
return;
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
async initializeDuckLakeForEnvironment(
|
|
28
|
-
environmentId: string,
|
|
29
|
-
environmentName: string,
|
|
30
|
-
config: { catalogUrl: string; dataPath: string },
|
|
31
|
-
): Promise<void> {
|
|
32
|
-
initializeDuckLakeCalls.push({
|
|
33
|
-
environmentId,
|
|
34
|
-
environmentName,
|
|
35
|
-
config,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
21
|
getRepository() {
|
|
40
22
|
return {
|
|
41
23
|
// ===== PROJECT METHODS =====
|
|
@@ -488,69 +470,6 @@ describe("EnvironmentStore Service", () => {
|
|
|
488
470
|
expect(readmeContent).toBe("Updated README content");
|
|
489
471
|
});
|
|
490
472
|
|
|
491
|
-
it("should propagate materializationStorage on addEnvironment for new environment", async () => {
|
|
492
|
-
writeFileSync(
|
|
493
|
-
path.join(serverRootPath, "publisher.config.json"),
|
|
494
|
-
JSON.stringify({ frozenConfig: false, environments: [] }),
|
|
495
|
-
);
|
|
496
|
-
|
|
497
|
-
await environmentStore.finishedInitialization;
|
|
498
|
-
|
|
499
|
-
const materializationStorage = {
|
|
500
|
-
catalogUrl:
|
|
501
|
-
"postgres:host=localhost port=5432 dbname=ducklake user=u password=p",
|
|
502
|
-
dataPath: "gs://test-bucket",
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
initializeDuckLakeCalls.length = 0;
|
|
506
|
-
const project = await environmentStore.addEnvironment({
|
|
507
|
-
name: projectName,
|
|
508
|
-
materializationStorage,
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
expect(project.metadata.materializationStorage).toEqual(
|
|
512
|
-
materializationStorage,
|
|
513
|
-
);
|
|
514
|
-
expect(initializeDuckLakeCalls).toHaveLength(1);
|
|
515
|
-
expect(initializeDuckLakeCalls[0].config).toEqual(materializationStorage);
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
it("should propagate materializationStorage on update", async () => {
|
|
519
|
-
const projectPath = path.join(serverRootPath, projectName);
|
|
520
|
-
mkdirSync(projectPath, { recursive: true });
|
|
521
|
-
writeFileSync(
|
|
522
|
-
path.join(projectPath, "publisher.json"),
|
|
523
|
-
JSON.stringify({ name: projectName, description: "Test package" }),
|
|
524
|
-
);
|
|
525
|
-
writeFileSync(
|
|
526
|
-
path.join(serverRootPath, "publisher.config.json"),
|
|
527
|
-
JSON.stringify({
|
|
528
|
-
frozenConfig: false,
|
|
529
|
-
environments: [
|
|
530
|
-
{
|
|
531
|
-
name: projectName,
|
|
532
|
-
packages: [{ name: projectName, location: projectPath }],
|
|
533
|
-
},
|
|
534
|
-
],
|
|
535
|
-
}),
|
|
536
|
-
);
|
|
537
|
-
|
|
538
|
-
await environmentStore.finishedInitialization;
|
|
539
|
-
const project = await environmentStore.getEnvironment(projectName);
|
|
540
|
-
|
|
541
|
-
const materializationStorage = {
|
|
542
|
-
catalogUrl:
|
|
543
|
-
"postgres:host=localhost port=5432 dbname=ducklake user=u password=p",
|
|
544
|
-
dataPath: "gs://test-bucket",
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
await project.update({ name: projectName, materializationStorage });
|
|
548
|
-
|
|
549
|
-
expect(project.metadata.materializationStorage).toEqual(
|
|
550
|
-
materializationStorage,
|
|
551
|
-
);
|
|
552
|
-
});
|
|
553
|
-
|
|
554
473
|
it(
|
|
555
474
|
"should handle project reload",
|
|
556
475
|
async () => {
|
|
@@ -95,6 +95,26 @@ function validateEnvironmentAzureUrls(environment: ApiEnvironment): void {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Safely clear a prior mount target before (re)mounting at `targetPath`.
|
|
99
|
+
// Distinguishes symlinks from real dirs: fs.rm's recursive mode could otherwise
|
|
100
|
+
// traverse into a symlinked source and damage it, so unlink symlinks and rm real
|
|
101
|
+
// dirs. Both the in-place (symlink) and copy mount paths call this, so toggling
|
|
102
|
+
// watch mode across runs against the same publisher_data can't leave a stale
|
|
103
|
+
// entry that breaks the next mount (mkdir is a no-op on a symlink and fs.cp then
|
|
104
|
+
// throws ERR_FS_CP_DIR_TO_NON_DIR).
|
|
105
|
+
async function clearMountTarget(targetPath: string): Promise<void> {
|
|
106
|
+
try {
|
|
107
|
+
const stats = await fs.promises.lstat(targetPath);
|
|
108
|
+
if (stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
109
|
+
await fs.promises.rm(targetPath, { recursive: true, force: true });
|
|
110
|
+
} else {
|
|
111
|
+
await fs.promises.unlink(targetPath);
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// Nothing there, fine.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
98
118
|
export class EnvironmentStore {
|
|
99
119
|
public serverRootPath: string;
|
|
100
120
|
private environments: Map<string, Environment> = new Map();
|
|
@@ -112,10 +132,36 @@ export class EnvironmentStore {
|
|
|
112
132
|
// new Environments pick it up at construction.
|
|
113
133
|
private memoryGovernor: PackageMemoryGovernor | null = null;
|
|
114
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Set of environment names that should be loaded "in place" — i.e. the
|
|
137
|
+
* package directories under `publisher_data/<envName>/` are symlinks to
|
|
138
|
+
* the original local source directories instead of recursive copies.
|
|
139
|
+
*
|
|
140
|
+
* In-place mode powers the `npm run dev`-style live-reload story: edits
|
|
141
|
+
* to your source repo are visible to Publisher and trigger watch events
|
|
142
|
+
* that fan out via SSE to embedded HTML pages. Production mode (the
|
|
143
|
+
* default) keeps the safe copy semantics so the source repo and the
|
|
144
|
+
* served package are decoupled.
|
|
145
|
+
*
|
|
146
|
+
* Populated from the `PUBLISHER_WATCH` env var (comma-separated env
|
|
147
|
+
* names) at construction; can also be augmented via `markInPlace()` if
|
|
148
|
+
* a future feature wants to flip an environment to dev mode at runtime.
|
|
149
|
+
*
|
|
150
|
+
* Only LOCAL-DIR package locations are eligible for symlinking; remote
|
|
151
|
+
* sources (GitHub, GCS, S3) always copy regardless.
|
|
152
|
+
*/
|
|
153
|
+
private inPlaceEnvs = new Set<string>();
|
|
154
|
+
|
|
115
155
|
constructor(serverRootPath: string) {
|
|
116
156
|
this.serverRootPath = serverRootPath;
|
|
117
157
|
this.gcsClient = new Storage();
|
|
118
158
|
|
|
159
|
+
const watchEnvList = (process.env.PUBLISHER_WATCH || "")
|
|
160
|
+
.split(",")
|
|
161
|
+
.map((s) => s.trim())
|
|
162
|
+
.filter((s) => s.length > 0);
|
|
163
|
+
for (const envName of watchEnvList) this.inPlaceEnvs.add(envName);
|
|
164
|
+
|
|
119
165
|
const storageConfig: StorageConfig = {
|
|
120
166
|
type: "duckdb",
|
|
121
167
|
duckdb: {
|
|
@@ -127,6 +173,17 @@ export class EnvironmentStore {
|
|
|
127
173
|
this.finishedInitialization = this.initialize();
|
|
128
174
|
}
|
|
129
175
|
|
|
176
|
+
/** True if this env should mount package dirs in place (symlinks). */
|
|
177
|
+
public isInPlace(environmentName: string): boolean {
|
|
178
|
+
return this.inPlaceEnvs.has(environmentName);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Add an env to in-place mode at runtime. Caller is responsible for
|
|
182
|
+
* triggering a re-mount if the env was already loaded with copies. */
|
|
183
|
+
public markInPlace(environmentName: string): void {
|
|
184
|
+
this.inPlaceEnvs.add(environmentName);
|
|
185
|
+
}
|
|
186
|
+
|
|
130
187
|
/**
|
|
131
188
|
* Attach (or detach with `null`) the shared {@link PackageMemoryGovernor}.
|
|
132
189
|
* Propagated to every Environment so the back-pressure decision is
|
|
@@ -395,25 +452,6 @@ export class EnvironmentStore {
|
|
|
395
452
|
dbEnvironment = await repository.createEnvironment(environmentData);
|
|
396
453
|
}
|
|
397
454
|
|
|
398
|
-
// Initialize DuckLake manifest storage if configured on the environment.
|
|
399
|
-
const materializationStorage = environment.metadata
|
|
400
|
-
?.materializationStorage as
|
|
401
|
-
| { catalogUrl?: string; dataPath?: string }
|
|
402
|
-
| undefined;
|
|
403
|
-
if (
|
|
404
|
-
materializationStorage?.catalogUrl &&
|
|
405
|
-
materializationStorage?.dataPath
|
|
406
|
-
) {
|
|
407
|
-
await this.storageManager.initializeDuckLakeForEnvironment(
|
|
408
|
-
dbEnvironment.id,
|
|
409
|
-
dbEnvironment.name,
|
|
410
|
-
{
|
|
411
|
-
catalogUrl: materializationStorage.catalogUrl,
|
|
412
|
-
dataPath: materializationStorage.dataPath,
|
|
413
|
-
},
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
455
|
return dbEnvironment;
|
|
418
456
|
}
|
|
419
457
|
|
|
@@ -881,10 +919,6 @@ export class EnvironmentStore {
|
|
|
881
919
|
|
|
882
920
|
if (!newEnvironment.metadata) newEnvironment.metadata = {};
|
|
883
921
|
newEnvironment.metadata.location = absoluteEnvironmentPath;
|
|
884
|
-
if (environment.materializationStorage !== undefined) {
|
|
885
|
-
newEnvironment.metadata.materializationStorage =
|
|
886
|
-
environment.materializationStorage;
|
|
887
|
-
}
|
|
888
922
|
|
|
889
923
|
this.environments.set(environmentName, newEnvironment);
|
|
890
924
|
|
|
@@ -1202,7 +1236,15 @@ export class EnvironmentStore {
|
|
|
1202
1236
|
} else {
|
|
1203
1237
|
// For non-GitHub locations, use package name
|
|
1204
1238
|
if (this.isLocalPath(_package.location)) {
|
|
1205
|
-
|
|
1239
|
+
// Match the resolution rule used by
|
|
1240
|
+
// `downloadOrMountLocation` (line ~1352): relative
|
|
1241
|
+
// paths are anchored at `serverRootPath`. Without this
|
|
1242
|
+
// step the existing-source check below falls through
|
|
1243
|
+
// for any relative location, and the in-place mount
|
|
1244
|
+
// branch is unreachable.
|
|
1245
|
+
sourcePath = path.isAbsolute(_package.location)
|
|
1246
|
+
? _package.location
|
|
1247
|
+
: path.join(this.serverRootPath, _package.location);
|
|
1206
1248
|
} else {
|
|
1207
1249
|
sourcePath = safeJoinUnderRoot(
|
|
1208
1250
|
tempDownloadPath,
|
|
@@ -1217,18 +1259,84 @@ export class EnvironmentStore {
|
|
|
1217
1259
|
.catch(() => false);
|
|
1218
1260
|
|
|
1219
1261
|
if (sourceExists) {
|
|
1220
|
-
//
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1262
|
+
// In-place mount path (watch-mode-only, local-dir packages
|
|
1263
|
+
// only). Replace the recursive copy with a symlink so that
|
|
1264
|
+
// edits to the source repo are visible to Publisher and
|
|
1265
|
+
// can trigger live-reload events.
|
|
1266
|
+
//
|
|
1267
|
+
// Carefully: we MUST distinguish symlinks from real dirs
|
|
1268
|
+
// when removing the prior content, because `fs.rm`'s
|
|
1269
|
+
// recursive mode could in principle traverse into a
|
|
1270
|
+
// symlinked source dir and damage it. We lstat first and
|
|
1271
|
+
// call `unlink` for symlinks, `rm` for real directories.
|
|
1272
|
+
const isInPlace =
|
|
1273
|
+
this.inPlaceEnvs.has(environmentName) &&
|
|
1274
|
+
this.isLocalPath(_package.location);
|
|
1275
|
+
if (isInPlace) {
|
|
1276
|
+
await clearMountTarget(absolutePackagePath);
|
|
1277
|
+
const absoluteSourcePath = path.resolve(sourcePath);
|
|
1278
|
+
// On Windows a "dir" symlink needs elevation
|
|
1279
|
+
// (SeCreateSymbolicLinkPrivilege — admin or Developer
|
|
1280
|
+
// Mode); a junction does not and works for absolute
|
|
1281
|
+
// directory targets, so prefer it there. chokidar watches
|
|
1282
|
+
// through both.
|
|
1283
|
+
const linkType =
|
|
1284
|
+
process.platform === "win32" ? "junction" : "dir";
|
|
1285
|
+
try {
|
|
1286
|
+
await fs.promises.symlink(
|
|
1287
|
+
absoluteSourcePath,
|
|
1288
|
+
absolutePackagePath,
|
|
1289
|
+
linkType,
|
|
1290
|
+
);
|
|
1291
|
+
logger.info(
|
|
1292
|
+
`In-place mount (watch mode): linked package "${packageDir}" -> "${absoluteSourcePath}"`,
|
|
1293
|
+
);
|
|
1294
|
+
} catch (linkError) {
|
|
1295
|
+
// Degrade gracefully instead of failing the env load:
|
|
1296
|
+
// copy the package so it still serves. Live reload
|
|
1297
|
+
// won't work for it (source edits aren't seen), but the
|
|
1298
|
+
// environment comes up. Hit e.g. on locked-down Windows
|
|
1299
|
+
// where even junction creation is denied.
|
|
1300
|
+
const code =
|
|
1301
|
+
(linkError as NodeJS.ErrnoException)?.code ??
|
|
1302
|
+
String(linkError);
|
|
1303
|
+
logger.warn(
|
|
1304
|
+
`In-place mount failed for package "${packageDir}" (${code}); falling back to a copy. Source-edit live reload is disabled for this package.`,
|
|
1305
|
+
);
|
|
1306
|
+
await clearMountTarget(absolutePackagePath);
|
|
1307
|
+
await fs.promises.mkdir(absolutePackagePath, {
|
|
1308
|
+
recursive: true,
|
|
1309
|
+
});
|
|
1310
|
+
await fs.promises.cp(sourcePath, absolutePackagePath, {
|
|
1311
|
+
recursive: true,
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
} else {
|
|
1315
|
+
if (
|
|
1316
|
+
this.inPlaceEnvs.has(environmentName) &&
|
|
1317
|
+
!this.isLocalPath(_package.location)
|
|
1318
|
+
) {
|
|
1319
|
+
logger.warn(
|
|
1320
|
+
`Watch mode: package "${packageDir}" has remote location "${_package.location}" — falling back to copy. Source-edit live reload won't work for this package; clone the source locally and use a local-dir location to enable it.`,
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
// Copy the specific directory. Clear any stale mount target
|
|
1324
|
+
// first (e.g. a symlink left by a prior --watch-env run), or
|
|
1325
|
+
// fs.cp would throw ERR_FS_CP_DIR_TO_NON_DIR onto it.
|
|
1326
|
+
await clearMountTarget(absolutePackagePath);
|
|
1327
|
+
await fs.promises.mkdir(absolutePackagePath, {
|
|
1328
|
+
recursive: true,
|
|
1329
|
+
});
|
|
1330
|
+
await fs.promises.cp(sourcePath, absolutePackagePath, {
|
|
1331
|
+
recursive: true,
|
|
1332
|
+
});
|
|
1333
|
+
logger.info(
|
|
1334
|
+
`Extracted package "${packageDir}" from ${groupedLocation.startsWith("https://github.com/") && _package.location.includes("/tree/") ? "GitHub subdirectory" : "shared download"}`,
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1230
1337
|
} else {
|
|
1231
1338
|
// If source doesn't exist, copy the entire download as the package
|
|
1339
|
+
await clearMountTarget(absolutePackagePath);
|
|
1232
1340
|
await fs.promises.mkdir(absolutePackagePath, {
|
|
1233
1341
|
recursive: true,
|
|
1234
1342
|
});
|