@pgpmjs/core 7.13.0 → 7.15.0
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/apply/apply-spec.d.ts +14 -0
- package/apply/apply-spec.js +165 -0
- package/apply/index.d.ts +4 -0
- package/apply/index.js +20 -0
- package/apply/materialize.d.ts +40 -0
- package/apply/materialize.js +134 -0
- package/apply/reuse.d.ts +52 -0
- package/apply/reuse.js +226 -0
- package/apply/types.d.ts +172 -0
- package/apply/types.js +17 -0
- package/bundle/artifact.d.ts +35 -0
- package/bundle/artifact.js +96 -0
- package/bundle/deploy-bundled.d.ts +71 -0
- package/bundle/deploy-bundled.js +177 -0
- package/bundle/index.d.ts +2 -0
- package/bundle/index.js +4 -0
- package/core/class/pgpm.d.ts +20 -0
- package/core/class/pgpm.js +109 -62
- package/esm/apply/apply-spec.js +160 -0
- package/esm/apply/index.js +4 -0
- package/esm/apply/materialize.js +129 -0
- package/esm/apply/reuse.js +221 -0
- package/esm/apply/types.js +14 -0
- package/esm/bundle/artifact.js +89 -0
- package/esm/bundle/deploy-bundled.js +170 -0
- package/esm/bundle/index.js +4 -0
- package/esm/core/class/pgpm.js +109 -62
- package/esm/index.js +1 -0
- package/esm/packaging/package.js +8 -1
- package/esm/projects/deploy.js +30 -57
- package/esm/projects/revert.js +10 -3
- package/esm/projects/verify.js +10 -3
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +10 -9
- package/packaging/package.d.ts +7 -1
- package/packaging/package.js +8 -1
- package/projects/deploy.js +30 -57
- package/projects/revert.js +10 -3
- package/projects/verify.js +10 -3
package/esm/core/class/pgpm.js
CHANGED
|
@@ -9,6 +9,10 @@ import { parse } from 'parse-package-name';
|
|
|
9
9
|
import path, { dirname, resolve } from 'path';
|
|
10
10
|
import { getPgPool } from 'pg-cache';
|
|
11
11
|
import yanse from 'yanse';
|
|
12
|
+
import { resolveEffectiveModulePath } from '../../apply/materialize';
|
|
13
|
+
import { hasApplySpec, readApplySpec } from '../../apply/apply-spec';
|
|
14
|
+
import { isReuseSpec, resolveSharedModuleName } from '../../apply/reuse';
|
|
15
|
+
import { APPLY_SPEC_FILE } from '../../apply/types';
|
|
12
16
|
import { getAvailableExtensions } from '../../extensions/extensions';
|
|
13
17
|
import { generatePlan, writePlan, writePlanFile } from '@pgpmjs/ast/files';
|
|
14
18
|
import { getExtensionInfo, getExtensionName, getInstalledExtensions, parseControlFile, writeExtensions } from '@pgpmjs/ast/files';
|
|
@@ -18,7 +22,7 @@ import { parsePlanFile } from '@pgpmjs/ast/files/plan/parser';
|
|
|
18
22
|
import { isValidChangeName, isValidTagName, parseReference } from '@pgpmjs/ast/files/plan/validators';
|
|
19
23
|
import { PgpmMigrate } from '../../migrate/client';
|
|
20
24
|
import { getExtensionsAndModules, getExtensionsAndModulesChanges, latestChange, latestChangeAndVersion } from '../../modules/modules';
|
|
21
|
-
import {
|
|
25
|
+
import { deployModuleFast, isBundledDeployResult } from '../../bundle/deploy-bundled';
|
|
22
26
|
import { syncModuleVersions } from '../../packaging/sync-versions';
|
|
23
27
|
import { resolveDependencies, resolveExtensionDependencies } from '../../resolution/deps';
|
|
24
28
|
import { movePath } from '../../utils/fs';
|
|
@@ -242,11 +246,61 @@ export class PgpmPackage {
|
|
|
242
246
|
}
|
|
243
247
|
});
|
|
244
248
|
// Parse the selected control files
|
|
245
|
-
|
|
249
|
+
const moduleMap = Array.from(selectedFiles.entries()).reduce((acc, [moduleName, file]) => {
|
|
246
250
|
const module = parseControlFile(file, this.workspacePath);
|
|
247
251
|
acc[moduleName] = module;
|
|
248
252
|
return acc;
|
|
249
253
|
}, {});
|
|
254
|
+
return this.addApplyModules(moduleMap, packageDirs);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Discover apply-spec proxy modules (`pgpm.apply.json`) and synthesize
|
|
258
|
+
* module-map entries for them so they participate in dependency resolution
|
|
259
|
+
* by name like any other module. A proxy needs no `.control` or `pgpm.plan`;
|
|
260
|
+
* its `requires` default to the source module's requires. Control-file
|
|
261
|
+
* modules win name collisions.
|
|
262
|
+
*/
|
|
263
|
+
addApplyModules(moduleMap, packageDirs) {
|
|
264
|
+
const specFiles = [...new Set(packageDirs.flatMap(dir => glob.sync(globPattern(dir, `**/${APPLY_SPEC_FILE}`))))].filter((file) => !/node_modules/.test(file)).sort((a, b) => a.localeCompare(b));
|
|
265
|
+
for (const file of specFiles) {
|
|
266
|
+
let spec;
|
|
267
|
+
try {
|
|
268
|
+
spec = readApplySpec(path.dirname(file));
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
console.warn(`Skipping invalid apply spec ${file}: ${err instanceof Error ? err.message : err}`);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const name = spec.name;
|
|
275
|
+
if (moduleMap[name])
|
|
276
|
+
continue;
|
|
277
|
+
const source = moduleMap[spec.source.module];
|
|
278
|
+
const relPath = path
|
|
279
|
+
.dirname(path.relative(this.workspacePath, file))
|
|
280
|
+
.replace(/\\/g, '/');
|
|
281
|
+
const baseRequires = spec.requires ?? source?.requires ?? [];
|
|
282
|
+
const version = spec.version ?? source?.version ?? '0.0.1';
|
|
283
|
+
if (isReuseSpec(spec)) {
|
|
284
|
+
// A reuse proxy expands into two modules that both materialize from
|
|
285
|
+
// this one directory: a shared module (deployed once, reused across
|
|
286
|
+
// tenants) and the per-tenant module that `requires` it. Multiple
|
|
287
|
+
// tenant proxies over the same source/shared/seed config resolve to
|
|
288
|
+
// the same deterministic shared name, so it is added at most once.
|
|
289
|
+
const sharedName = resolveSharedModuleName(spec);
|
|
290
|
+
if (!moduleMap[sharedName]) {
|
|
291
|
+
moduleMap[sharedName] = { path: relPath, requires: baseRequires, version };
|
|
292
|
+
}
|
|
293
|
+
moduleMap[name] = {
|
|
294
|
+
path: relPath,
|
|
295
|
+
requires: [...baseRequires, sharedName],
|
|
296
|
+
version
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
moduleMap[name] = { path: relPath, requires: baseRequires, version };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return moduleMap;
|
|
250
304
|
}
|
|
251
305
|
getModuleMap() {
|
|
252
306
|
if (!this.workspacePath)
|
|
@@ -1196,6 +1250,38 @@ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join(
|
|
|
1196
1250
|
external: external
|
|
1197
1251
|
};
|
|
1198
1252
|
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Dependency closure for a named module. Proxy (apply-spec) modules have no
|
|
1255
|
+
* pgpm.plan, so they resolve straight off the workspace module map instead
|
|
1256
|
+
* of instantiating a module-rooted package.
|
|
1257
|
+
*/
|
|
1258
|
+
getModuleExtensionsFor(name) {
|
|
1259
|
+
const modules = this.getModuleMap();
|
|
1260
|
+
if (!modules[name]) {
|
|
1261
|
+
throw errors.MODULE_NOT_FOUND({ name });
|
|
1262
|
+
}
|
|
1263
|
+
const modulePath = resolve(this.workspacePath, modules[name].path);
|
|
1264
|
+
if (hasApplySpec(modulePath)) {
|
|
1265
|
+
return resolveExtensionDependencies(name, modules);
|
|
1266
|
+
}
|
|
1267
|
+
return this.getModuleProject(name).getModuleExtensions();
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Directory the migration engine should run against for a named module:
|
|
1271
|
+
* the module's own directory, or — for proxy (apply-spec) modules — the
|
|
1272
|
+
* materialized transpiled instance.
|
|
1273
|
+
*/
|
|
1274
|
+
async getEffectiveModulePathFor(name) {
|
|
1275
|
+
const modules = this.getModuleMap();
|
|
1276
|
+
if (!modules[name]) {
|
|
1277
|
+
throw errors.MODULE_NOT_FOUND({ name });
|
|
1278
|
+
}
|
|
1279
|
+
const modulePath = resolve(this.workspacePath, modules[name].path);
|
|
1280
|
+
if (hasApplySpec(modulePath)) {
|
|
1281
|
+
return resolveEffectiveModulePath(name, modulePath, modules, this.workspacePath);
|
|
1282
|
+
}
|
|
1283
|
+
return this.getModuleProject(name).getModulePath();
|
|
1284
|
+
}
|
|
1199
1285
|
parsePackageTarget(target) {
|
|
1200
1286
|
let name;
|
|
1201
1287
|
let toChange;
|
|
@@ -1227,12 +1313,6 @@ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join(
|
|
|
1227
1313
|
const log = new Logger('deploy');
|
|
1228
1314
|
const { name, toChange } = this.parsePackageTarget(target);
|
|
1229
1315
|
if (recursive) {
|
|
1230
|
-
// Cache for fast deployment
|
|
1231
|
-
const deployFastCache = {};
|
|
1232
|
-
const getCacheKey = (pg, name, database) => {
|
|
1233
|
-
const { host, port, user } = pg ?? {};
|
|
1234
|
-
return `${host}:${port}:${user}:${database}:${name}`;
|
|
1235
|
-
};
|
|
1236
1316
|
const modules = this.getModuleMap();
|
|
1237
1317
|
let extensions;
|
|
1238
1318
|
if (name === null) {
|
|
@@ -1240,8 +1320,7 @@ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join(
|
|
|
1240
1320
|
extensions = await this.resolveWorkspaceExtensionDependencies();
|
|
1241
1321
|
}
|
|
1242
1322
|
else {
|
|
1243
|
-
|
|
1244
|
-
extensions = moduleProject.getModuleExtensions();
|
|
1323
|
+
extensions = this.getModuleExtensionsFor(name);
|
|
1245
1324
|
}
|
|
1246
1325
|
const pgPool = getPgPool(opts.pg);
|
|
1247
1326
|
const targetDescription = name === null ? 'all modules' : name;
|
|
@@ -1254,52 +1333,24 @@ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join(
|
|
|
1254
1333
|
await pgPool.query(msg);
|
|
1255
1334
|
}
|
|
1256
1335
|
else {
|
|
1257
|
-
const modulePath = resolve(this.workspacePath, modules[extension].path);
|
|
1336
|
+
const modulePath = await resolveEffectiveModulePath(extension, resolve(this.workspacePath, modules[extension].path), modules, this.workspacePath);
|
|
1258
1337
|
log.info(`📂 Deploying local module: ${extension}`);
|
|
1259
|
-
if (opts.deployment.fast) {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1338
|
+
if (opts.deployment.fast || opts.deployment.bundled) {
|
|
1339
|
+
// Fast strategy: one-shot execution plus a bulk-written
|
|
1340
|
+
// pgpm_migrate ledger, from the module's verified bundle artifact
|
|
1341
|
+
// when it has one and from `deploy/` when it does not.
|
|
1342
|
+
const outcome = await deployModuleFast(modulePath, {
|
|
1343
|
+
config: opts.pg,
|
|
1344
|
+
logOnly: opts.deployment.logOnly,
|
|
1345
|
+
useTransaction: opts.deployment.useTx,
|
|
1346
|
+
hashMethod: opts.deployment.hashMethod
|
|
1347
|
+
});
|
|
1348
|
+
if (isBundledDeployResult(outcome)) {
|
|
1265
1349
|
continue;
|
|
1266
1350
|
}
|
|
1267
|
-
|
|
1268
|
-
try {
|
|
1269
|
-
pkg = await packageModule(localProject.modulePath, {
|
|
1270
|
-
usePlan: opts.deployment.usePlan,
|
|
1271
|
-
extension: false
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
catch (err) {
|
|
1275
|
-
const errorLines = [];
|
|
1276
|
-
errorLines.push(`❌ Failed to package module "${extension}" at path: ${modulePath}`);
|
|
1277
|
-
errorLines.push(` Module Path: ${modulePath}`);
|
|
1278
|
-
errorLines.push(` Workspace Path: ${this.workspacePath}`);
|
|
1279
|
-
errorLines.push(` Error Code: ${err.code || 'N/A'}`);
|
|
1280
|
-
errorLines.push(` Error Message: ${err.message || 'Unknown error'}`);
|
|
1281
|
-
if (err.code === 'ENOENT') {
|
|
1282
|
-
errorLines.push('💡 Hint: File or directory not found. Check if the module path is correct.');
|
|
1283
|
-
}
|
|
1284
|
-
else if (err.code === 'EACCES') {
|
|
1285
|
-
errorLines.push('💡 Hint: Permission denied. Check file permissions.');
|
|
1286
|
-
}
|
|
1287
|
-
else if (err.message && err.message.includes('pgpm.plan')) {
|
|
1288
|
-
errorLines.push('💡 Hint: pgpm.plan file issue. Check if the plan file exists and is valid.');
|
|
1289
|
-
}
|
|
1290
|
-
log.error(errorLines.join('\n'));
|
|
1291
|
-
console.error(err);
|
|
1292
|
-
throw errors.DEPLOYMENT_FAILED({
|
|
1293
|
-
type: 'Deployment',
|
|
1294
|
-
module: extension
|
|
1295
|
-
});
|
|
1296
|
-
}
|
|
1297
|
-
await pgPool.query(pkg.sql);
|
|
1298
|
-
if (opts.deployment.cache) {
|
|
1299
|
-
deployFastCache[cacheKey] = pkg;
|
|
1300
|
-
}
|
|
1351
|
+
log.info(`↩️ Fast deploy unavailable for ${extension} (${outcome}); using per-change path.`);
|
|
1301
1352
|
}
|
|
1302
|
-
|
|
1353
|
+
{
|
|
1303
1354
|
try {
|
|
1304
1355
|
const client = new PgpmMigrate(opts.pg);
|
|
1305
1356
|
// Only apply toChange to the target module, not its dependencies
|
|
@@ -1335,8 +1386,7 @@ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join(
|
|
|
1335
1386
|
if (name === null) {
|
|
1336
1387
|
throw errors.WORKSPACE_OPERATION_ERROR({ operation: 'deployment' });
|
|
1337
1388
|
}
|
|
1338
|
-
const
|
|
1339
|
-
const modulePath = moduleProject.getModulePath();
|
|
1389
|
+
const modulePath = await this.getEffectiveModulePathFor(name);
|
|
1340
1390
|
if (!modulePath) {
|
|
1341
1391
|
throw errors.PATH_NOT_FOUND({ path: name, type: 'module' });
|
|
1342
1392
|
}
|
|
@@ -1403,7 +1453,7 @@ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join(
|
|
|
1403
1453
|
}
|
|
1404
1454
|
}
|
|
1405
1455
|
else {
|
|
1406
|
-
const modulePath = resolve(this.workspacePath, modules[extension].path);
|
|
1456
|
+
const modulePath = await resolveEffectiveModulePath(extension, resolve(this.workspacePath, modules[extension].path), modules, this.workspacePath);
|
|
1407
1457
|
log.info(`📂 Reverting local module: ${extension}`);
|
|
1408
1458
|
try {
|
|
1409
1459
|
const client = new PgpmMigrate(opts.pg);
|
|
@@ -1436,8 +1486,7 @@ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join(
|
|
|
1436
1486
|
if (name === null) {
|
|
1437
1487
|
throw errors.WORKSPACE_OPERATION_ERROR({ operation: 'revert' });
|
|
1438
1488
|
}
|
|
1439
|
-
const
|
|
1440
|
-
const modulePath = moduleProject.getModulePath();
|
|
1489
|
+
const modulePath = await this.getEffectiveModulePathFor(name);
|
|
1441
1490
|
if (!modulePath) {
|
|
1442
1491
|
throw errors.PATH_NOT_FOUND({ path: name, type: 'module' });
|
|
1443
1492
|
}
|
|
@@ -1464,8 +1513,7 @@ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join(
|
|
|
1464
1513
|
extensions = await this.resolveWorkspaceExtensionDependencies();
|
|
1465
1514
|
}
|
|
1466
1515
|
else {
|
|
1467
|
-
|
|
1468
|
-
extensions = moduleProject.getModuleExtensions();
|
|
1516
|
+
extensions = this.getModuleExtensionsFor(name);
|
|
1469
1517
|
}
|
|
1470
1518
|
const pgPool = getPgPool(opts.pg);
|
|
1471
1519
|
const targetDescription = name === null ? 'all modules' : name;
|
|
@@ -1478,7 +1526,7 @@ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join(
|
|
|
1478
1526
|
await pgPool.query(query, [extension]);
|
|
1479
1527
|
}
|
|
1480
1528
|
else {
|
|
1481
|
-
const modulePath = resolve(this.workspacePath, modules[extension].path);
|
|
1529
|
+
const modulePath = await resolveEffectiveModulePath(extension, resolve(this.workspacePath, modules[extension].path), modules, this.workspacePath);
|
|
1482
1530
|
log.info(`📂 Verifying local module: ${extension}`);
|
|
1483
1531
|
try {
|
|
1484
1532
|
const client = new PgpmMigrate(opts.pg);
|
|
@@ -1510,8 +1558,7 @@ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join(
|
|
|
1510
1558
|
if (name === null) {
|
|
1511
1559
|
throw errors.WORKSPACE_OPERATION_ERROR({ operation: 'verification' });
|
|
1512
1560
|
}
|
|
1513
|
-
const
|
|
1514
|
-
const modulePath = moduleProject.getModulePath();
|
|
1561
|
+
const modulePath = await this.getEffectiveModulePathFor(name);
|
|
1515
1562
|
if (!modulePath) {
|
|
1516
1563
|
throw errors.PATH_NOT_FOUND({ path: name, type: 'module' });
|
|
1517
1564
|
}
|
package/esm/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export * from './core/boilerplate-scanner';
|
|
|
16
16
|
// Re-export the module AST layer (moved to the @pgpmjs/ast leaf package)
|
|
17
17
|
export * from '@pgpmjs/ast/files';
|
|
18
18
|
export * from '@pgpmjs/ast/module';
|
|
19
|
+
export * from './apply';
|
|
19
20
|
export * from './bundle';
|
|
20
21
|
export * from './refactor';
|
|
21
22
|
export { cleanSql } from './migrate/clean';
|
package/esm/packaging/package.js
CHANGED
|
@@ -4,6 +4,7 @@ import { relative } from 'path';
|
|
|
4
4
|
import { deparse } from 'pgsql-deparser';
|
|
5
5
|
import { parse } from 'pgsql-parser';
|
|
6
6
|
import { getExtensionName } from '@pgpmjs/ast/files';
|
|
7
|
+
import { writeBundleArtifact } from '../bundle/artifact';
|
|
7
8
|
import { resolve, resolveWithPlan } from '../resolution/resolve';
|
|
8
9
|
import { transformProps } from './transform';
|
|
9
10
|
const log = new Logger('package');
|
|
@@ -78,7 +79,7 @@ export const packageModule = async (packageDir, { usePlan = true, extension = tr
|
|
|
78
79
|
throw e;
|
|
79
80
|
}
|
|
80
81
|
};
|
|
81
|
-
export const writePackage = async ({ version, extension = true, usePlan = true, packageDir, outputDiff = false, }) => {
|
|
82
|
+
export const writePackage = async ({ version, extension = true, usePlan = true, packageDir, outputDiff = false, bundle = true, }) => {
|
|
82
83
|
const pkgPath = `${packageDir}/package.json`;
|
|
83
84
|
const pkg = require(pkgPath);
|
|
84
85
|
const extname = await getExtensionName(packageDir);
|
|
@@ -115,4 +116,10 @@ export const writePackage = async ({ version, extension = true, usePlan = true,
|
|
|
115
116
|
const writePath = `${outPath}/${sqlFileName}`;
|
|
116
117
|
writeFileSync(writePath, sql);
|
|
117
118
|
log.success(`${relative(packageDir, writePath)} written`);
|
|
119
|
+
if (bundle) {
|
|
120
|
+
const bundlePath = await writeBundleArtifact(packageDir, version, {
|
|
121
|
+
createdWith: '@pgpmjs/core'
|
|
122
|
+
});
|
|
123
|
+
log.success(`${relative(packageDir, bundlePath)} written`);
|
|
124
|
+
}
|
|
118
125
|
};
|
package/esm/projects/deploy.js
CHANGED
|
@@ -4,15 +4,12 @@ import { errors } from '@pgpmjs/types';
|
|
|
4
4
|
import { resolve } from 'path';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { getPgPool } from 'pg-cache';
|
|
7
|
+
import { resolveEffectiveModulePath } from '../apply/materialize';
|
|
8
|
+
import { hasApplySpec } from '../apply/apply-spec';
|
|
9
|
+
import { deployModuleFast, isBundledDeployResult } from '../bundle/deploy-bundled';
|
|
7
10
|
import { PgpmPackage } from '../core/class/pgpm';
|
|
8
11
|
import { PgpmMigrate } from '../migrate/client';
|
|
9
|
-
import {
|
|
10
|
-
// Cache for fast deployment
|
|
11
|
-
const deployFastCache = {};
|
|
12
|
-
const getCacheKey = (pg, name, database) => {
|
|
13
|
-
const { host, port, user } = pg ?? {};
|
|
14
|
-
return `${host}:${port}:${user}:${database}:${name}`;
|
|
15
|
-
};
|
|
12
|
+
import { resolveExtensionDependencies } from '../resolution/deps';
|
|
16
13
|
const log = new Logger('deploy');
|
|
17
14
|
export const deployProject = async (opts, name, database, pkg, toChange) => {
|
|
18
15
|
const mergedOpts = getEnvOptions(opts);
|
|
@@ -23,9 +20,12 @@ export const deployProject = async (opts, name, database, pkg, toChange) => {
|
|
|
23
20
|
throw errors.MODULE_NOT_FOUND({ name });
|
|
24
21
|
}
|
|
25
22
|
const modulePath = path.resolve(pkg.workspacePath, modules[name].path);
|
|
26
|
-
const moduleProject = new PgpmPackage(modulePath, { extensionsDir: pkg.extensionsDir });
|
|
27
23
|
log.info(`📦 Resolving dependencies for ${name}...`);
|
|
28
|
-
|
|
24
|
+
// Proxy (apply-spec) modules have no pgpm.plan, so resolve straight off the
|
|
25
|
+
// workspace module map instead of instantiating a module-rooted package.
|
|
26
|
+
const extensions = hasApplySpec(modulePath)
|
|
27
|
+
? resolveExtensionDependencies(name, modules)
|
|
28
|
+
: new PgpmPackage(modulePath, { extensionsDir: pkg.extensionsDir }).getModuleExtensions();
|
|
29
29
|
const pgPool = getPgPool({ ...opts.pg, database });
|
|
30
30
|
log.success(`🚀 Starting deployment to database ${database}...`);
|
|
31
31
|
for (const extension of extensions.resolved) {
|
|
@@ -37,59 +37,32 @@ export const deployProject = async (opts, name, database, pkg, toChange) => {
|
|
|
37
37
|
await pgPool.query(msg);
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
|
-
const
|
|
40
|
+
const sourcePath = resolve(pkg.workspacePath, modules[extension].path);
|
|
41
|
+
const modulePath = await resolveEffectiveModulePath(extension, sourcePath, modules, pkg.workspacePath);
|
|
41
42
|
log.info(`📂 Deploying local module: ${extension}`);
|
|
42
43
|
log.debug(`→ Path: ${modulePath}`);
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
if (modulePath !== sourcePath) {
|
|
45
|
+
log.info(`🔁 Applying transpiled module (spec in ${sourcePath})`);
|
|
46
|
+
}
|
|
47
|
+
// The fast strategy is opt-in; everything else uses the per-change path.
|
|
48
|
+
if (mergedOpts.deployment.fast || mergedOpts.deployment.bundled) {
|
|
49
|
+
// Fast strategy: execute the module in one shot AND record the
|
|
50
|
+
// migration ledger, from the pre-built bundle artifact when one
|
|
51
|
+
// verifies and from `deploy/` otherwise. If the semantics cannot be
|
|
52
|
+
// honoured, fall through to the per-change migration path rather than
|
|
53
|
+
// deploying without a ledger.
|
|
54
|
+
const outcome = await deployModuleFast(modulePath, {
|
|
55
|
+
config: { ...mergedOpts.pg, database },
|
|
56
|
+
logOnly: mergedOpts.deployment.logOnly,
|
|
57
|
+
useTransaction: mergedOpts.deployment.useTx,
|
|
58
|
+
hashMethod: mergedOpts.deployment.hashMethod
|
|
59
|
+
});
|
|
60
|
+
if (isBundledDeployResult(outcome)) {
|
|
50
61
|
continue;
|
|
51
62
|
}
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
modulePackage = await packageModule(localProject.modulePath, {
|
|
55
|
-
usePlan: mergedOpts.deployment.usePlan,
|
|
56
|
-
extension: false
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
// Build comprehensive error message
|
|
61
|
-
const errorLines = [];
|
|
62
|
-
errorLines.push(`❌ Failed to package module "${extension}" at path: ${modulePath}`);
|
|
63
|
-
errorLines.push(` Module Path: ${modulePath}`);
|
|
64
|
-
errorLines.push(` Workspace Path: ${pkg.workspacePath}`);
|
|
65
|
-
errorLines.push(` Error Code: ${err.code || 'N/A'}`);
|
|
66
|
-
errorLines.push(` Error Message: ${err.message || 'Unknown error'}`);
|
|
67
|
-
// Provide debugging hints
|
|
68
|
-
if (err.code === 'ENOENT') {
|
|
69
|
-
errorLines.push('💡 Hint: File or directory not found. Check if the module path is correct.');
|
|
70
|
-
}
|
|
71
|
-
else if (err.code === 'EACCES') {
|
|
72
|
-
errorLines.push('💡 Hint: Permission denied. Check file permissions.');
|
|
73
|
-
}
|
|
74
|
-
else if (err.message && err.message.includes('pgpm.plan')) {
|
|
75
|
-
errorLines.push('💡 Hint: pgpm.plan file issue. Check if the plan file exists and is valid.');
|
|
76
|
-
}
|
|
77
|
-
// Log the consolidated error message
|
|
78
|
-
log.error(errorLines.join('\n'));
|
|
79
|
-
console.error(err); // Preserve full stack trace
|
|
80
|
-
throw errors.DEPLOYMENT_FAILED({
|
|
81
|
-
type: 'Deployment',
|
|
82
|
-
module: extension
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
log.debug(`→ Command: sqitch deploy db:pg:${database}`);
|
|
86
|
-
log.debug(`> ${modulePackage.sql}`);
|
|
87
|
-
await pgPool.query(modulePackage.sql);
|
|
88
|
-
if (mergedOpts.deployment.cache) {
|
|
89
|
-
deployFastCache[cacheKey] = modulePackage;
|
|
90
|
-
}
|
|
63
|
+
log.info(`↩️ Fast deploy unavailable for ${extension} (${outcome}); using per-change path.`);
|
|
91
64
|
}
|
|
92
|
-
|
|
65
|
+
{
|
|
93
66
|
// Use new migration system
|
|
94
67
|
log.debug(`→ Command: constructive migrate deploy db:pg:${database}`);
|
|
95
68
|
try {
|
package/esm/projects/revert.js
CHANGED
|
@@ -3,8 +3,11 @@ import { errors } from '@pgpmjs/types';
|
|
|
3
3
|
import { resolve } from 'path';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { getPgPool } from 'pg-cache';
|
|
6
|
+
import { resolveEffectiveModulePath } from '../apply/materialize';
|
|
7
|
+
import { hasApplySpec } from '../apply/apply-spec';
|
|
6
8
|
import { PgpmPackage } from '../core/class/pgpm';
|
|
7
9
|
import { PgpmMigrate } from '../migrate/client';
|
|
10
|
+
import { resolveExtensionDependencies } from '../resolution/deps';
|
|
8
11
|
const log = new Logger('revert');
|
|
9
12
|
export const revertProject = async (opts, name, database, pkg, options) => {
|
|
10
13
|
log.info(`🔍 Gathering modules from ${pkg.workspacePath}...`);
|
|
@@ -14,9 +17,12 @@ export const revertProject = async (opts, name, database, pkg, options) => {
|
|
|
14
17
|
throw errors.MODULE_NOT_FOUND({ name });
|
|
15
18
|
}
|
|
16
19
|
const modulePath = path.resolve(pkg.workspacePath, modules[name].path);
|
|
17
|
-
const moduleProject = new PgpmPackage(modulePath, { extensionsDir: pkg.extensionsDir });
|
|
18
20
|
log.info(`📦 Resolving dependencies for ${name}...`);
|
|
19
|
-
|
|
21
|
+
// Proxy (apply-spec) modules have no pgpm.plan, so resolve straight off the
|
|
22
|
+
// workspace module map instead of instantiating a module-rooted package.
|
|
23
|
+
const extensions = hasApplySpec(modulePath)
|
|
24
|
+
? resolveExtensionDependencies(name, modules)
|
|
25
|
+
: new PgpmPackage(modulePath, { extensionsDir: pkg.extensionsDir }).getModuleExtensions();
|
|
20
26
|
const pgPool = getPgPool({
|
|
21
27
|
...opts.pg,
|
|
22
28
|
database
|
|
@@ -42,7 +48,8 @@ export const revertProject = async (opts, name, database, pkg, options) => {
|
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
50
|
else {
|
|
45
|
-
const
|
|
51
|
+
const sourcePath = resolve(pkg.workspacePath, modules[extension].path);
|
|
52
|
+
const modulePath = await resolveEffectiveModulePath(extension, sourcePath, modules, pkg.workspacePath);
|
|
46
53
|
log.info(`📂 Reverting local module: ${extension}`);
|
|
47
54
|
log.debug(`→ Path: ${modulePath}`);
|
|
48
55
|
// Use new migration system
|
package/esm/projects/verify.js
CHANGED
|
@@ -3,8 +3,11 @@ import { errors } from '@pgpmjs/types';
|
|
|
3
3
|
import { resolve } from 'path';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { getPgPool } from 'pg-cache';
|
|
6
|
+
import { resolveEffectiveModulePath } from '../apply/materialize';
|
|
7
|
+
import { hasApplySpec } from '../apply/apply-spec';
|
|
6
8
|
import { PgpmPackage } from '../core/class/pgpm';
|
|
7
9
|
import { PgpmMigrate } from '../migrate/client';
|
|
10
|
+
import { resolveExtensionDependencies } from '../resolution/deps';
|
|
8
11
|
const log = new Logger('verify');
|
|
9
12
|
export const verifyProject = async (opts, name, database, pkg, options) => {
|
|
10
13
|
log.info(`🔍 Gathering modules from ${pkg.workspacePath}...`);
|
|
@@ -14,9 +17,12 @@ export const verifyProject = async (opts, name, database, pkg, options) => {
|
|
|
14
17
|
throw errors.MODULE_NOT_FOUND({ name });
|
|
15
18
|
}
|
|
16
19
|
const modulePath = path.resolve(pkg.workspacePath, modules[name].path);
|
|
17
|
-
const moduleProject = new PgpmPackage(modulePath, { extensionsDir: pkg.extensionsDir });
|
|
18
20
|
log.info(`📦 Resolving dependencies for ${name}...`);
|
|
19
|
-
|
|
21
|
+
// Proxy (apply-spec) modules have no pgpm.plan, so resolve straight off the
|
|
22
|
+
// workspace module map instead of instantiating a module-rooted package.
|
|
23
|
+
const extensions = hasApplySpec(modulePath)
|
|
24
|
+
? resolveExtensionDependencies(name, modules)
|
|
25
|
+
: new PgpmPackage(modulePath, { extensionsDir: pkg.extensionsDir }).getModuleExtensions();
|
|
20
26
|
const pgPool = getPgPool({
|
|
21
27
|
...opts.pg,
|
|
22
28
|
database
|
|
@@ -31,7 +37,8 @@ export const verifyProject = async (opts, name, database, pkg, options) => {
|
|
|
31
37
|
await pgPool.query(query, [extension]);
|
|
32
38
|
}
|
|
33
39
|
else {
|
|
34
|
-
const
|
|
40
|
+
const sourcePath = resolve(pkg.workspacePath, modules[extension].path);
|
|
41
|
+
const modulePath = await resolveEffectiveModulePath(extension, sourcePath, modules, pkg.workspacePath);
|
|
35
42
|
log.info(`📂 Verifying local module: ${extension}`);
|
|
36
43
|
log.debug(`→ Path: ${modulePath}`);
|
|
37
44
|
log.debug(`→ Command: constructive migrate verify db:pg:${database}`);
|
package/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from './core/boilerplate-types';
|
|
|
15
15
|
export * from './core/boilerplate-scanner';
|
|
16
16
|
export * from '@pgpmjs/ast/files';
|
|
17
17
|
export * from '@pgpmjs/ast/module';
|
|
18
|
+
export * from './apply';
|
|
18
19
|
export * from './bundle';
|
|
19
20
|
export * from './refactor';
|
|
20
21
|
export { cleanSql } from './migrate/clean';
|
package/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __exportStar(require("./core/boilerplate-scanner"), exports);
|
|
|
33
33
|
// Re-export the module AST layer (moved to the @pgpmjs/ast leaf package)
|
|
34
34
|
__exportStar(require("@pgpmjs/ast/files"), exports);
|
|
35
35
|
__exportStar(require("@pgpmjs/ast/module"), exports);
|
|
36
|
+
__exportStar(require("./apply"), exports);
|
|
36
37
|
__exportStar(require("./bundle"), exports);
|
|
37
38
|
__exportStar(require("./refactor"), exports);
|
|
38
39
|
var clean_1 = require("./migrate/clean");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgpmjs/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.15.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "PGPM Package and Migration Tools",
|
|
6
6
|
"main": "index.js",
|
|
@@ -48,23 +48,24 @@
|
|
|
48
48
|
"makage": "^0.3.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@pgpmjs/ast": "^0.9.
|
|
52
|
-
"@pgpmjs/bundle": "^0.
|
|
53
|
-
"@pgpmjs/env": "^2.
|
|
51
|
+
"@pgpmjs/ast": "^0.9.2",
|
|
52
|
+
"@pgpmjs/bundle": "^0.12.0",
|
|
53
|
+
"@pgpmjs/env": "^2.38.0",
|
|
54
54
|
"@pgpmjs/logger": "^2.22.0",
|
|
55
|
-
"@pgpmjs/server-utils": "^3.23.
|
|
56
|
-
"@pgpmjs/slice": "^0.
|
|
57
|
-
"@pgpmjs/
|
|
55
|
+
"@pgpmjs/server-utils": "^3.23.1",
|
|
56
|
+
"@pgpmjs/slice": "^0.10.1",
|
|
57
|
+
"@pgpmjs/transform": "^0.11.0",
|
|
58
|
+
"@pgpmjs/types": "^2.46.0",
|
|
58
59
|
"csv-to-pg": "^4.8.0",
|
|
59
60
|
"genomic": "^5.6.2",
|
|
60
61
|
"glob": "^13.0.6",
|
|
61
62
|
"parse-package-name": "^1.0.0",
|
|
62
63
|
"pg": "^8.21.0",
|
|
63
|
-
"pg-cache": "^3.23.
|
|
64
|
+
"pg-cache": "^3.23.1",
|
|
64
65
|
"pg-env": "^1.26.0",
|
|
65
66
|
"pgsql-deparser": "^18.1.1",
|
|
66
67
|
"pgsql-parser": "^18.1.1",
|
|
67
68
|
"yanse": "^0.2.1"
|
|
68
69
|
},
|
|
69
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "5ef8341033a8fbd85841b770e996e468dd15333b"
|
|
70
71
|
}
|
package/packaging/package.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ interface PackageModuleOptions {
|
|
|
9
9
|
interface WritePackageOptions extends PackageModuleOptions {
|
|
10
10
|
version: string;
|
|
11
11
|
packageDir: string;
|
|
12
|
+
/**
|
|
13
|
+
* Also emit the content-addressed bundle artifact
|
|
14
|
+
* `sql/<name>--<version>.bundle.tar.gz` (default true). It is what
|
|
15
|
+
* `deploy --bundled` consumes.
|
|
16
|
+
*/
|
|
17
|
+
bundle?: boolean;
|
|
12
18
|
}
|
|
13
19
|
export interface MergeSqlOptions {
|
|
14
20
|
/** Strip `TransactionStmt`/`CreateExtensionStmt` before deparsing (extension mode) */
|
|
@@ -38,5 +44,5 @@ export declare const packageModule: (packageDir: string, { usePlan, extension, p
|
|
|
38
44
|
tree1?: string;
|
|
39
45
|
tree2?: string;
|
|
40
46
|
}>;
|
|
41
|
-
export declare const writePackage: ({ version, extension, usePlan, packageDir, outputDiff, }: WritePackageOptions) => Promise<void>;
|
|
47
|
+
export declare const writePackage: ({ version, extension, usePlan, packageDir, outputDiff, bundle, }: WritePackageOptions) => Promise<void>;
|
|
42
48
|
export {};
|
package/packaging/package.js
CHANGED
|
@@ -7,6 +7,7 @@ const path_1 = require("path");
|
|
|
7
7
|
const pgsql_deparser_1 = require("pgsql-deparser");
|
|
8
8
|
const pgsql_parser_1 = require("pgsql-parser");
|
|
9
9
|
const files_1 = require("@pgpmjs/ast/files");
|
|
10
|
+
const artifact_1 = require("../bundle/artifact");
|
|
10
11
|
const resolve_1 = require("../resolution/resolve");
|
|
11
12
|
const transform_1 = require("./transform");
|
|
12
13
|
const log = new logger_1.Logger('package');
|
|
@@ -84,7 +85,7 @@ const packageModule = async (packageDir, { usePlan = true, extension = true, pre
|
|
|
84
85
|
}
|
|
85
86
|
};
|
|
86
87
|
exports.packageModule = packageModule;
|
|
87
|
-
const writePackage = async ({ version, extension = true, usePlan = true, packageDir, outputDiff = false, }) => {
|
|
88
|
+
const writePackage = async ({ version, extension = true, usePlan = true, packageDir, outputDiff = false, bundle = true, }) => {
|
|
88
89
|
const pkgPath = `${packageDir}/package.json`;
|
|
89
90
|
const pkg = require(pkgPath);
|
|
90
91
|
const extname = await (0, files_1.getExtensionName)(packageDir);
|
|
@@ -121,5 +122,11 @@ const writePackage = async ({ version, extension = true, usePlan = true, package
|
|
|
121
122
|
const writePath = `${outPath}/${sqlFileName}`;
|
|
122
123
|
(0, fs_1.writeFileSync)(writePath, sql);
|
|
123
124
|
log.success(`${(0, path_1.relative)(packageDir, writePath)} written`);
|
|
125
|
+
if (bundle) {
|
|
126
|
+
const bundlePath = await (0, artifact_1.writeBundleArtifact)(packageDir, version, {
|
|
127
|
+
createdWith: '@pgpmjs/core'
|
|
128
|
+
});
|
|
129
|
+
log.success(`${(0, path_1.relative)(packageDir, bundlePath)} written`);
|
|
130
|
+
}
|
|
124
131
|
};
|
|
125
132
|
exports.writePackage = writePackage;
|