@pgpmjs/core 7.17.2 → 7.19.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.
@@ -38,3 +38,29 @@ export declare function materializeApplyModule(options: MaterializeApplyOptions)
38
38
  export declare function resolveEffectiveModulePath(moduleName: string, modulePath: string, moduleMap: ModuleMap, workspacePath: string): Promise<string>;
39
39
  /** Test seam: clear the per-process materialization cache. */
40
40
  export declare function clearApplyMaterializationCache(): void;
41
+ export interface MaterializeWorkspaceTargetOptions {
42
+ /** Absolute workspace root path. */
43
+ workspacePath: string;
44
+ /** The workspace module map (proxies already synthesized in). */
45
+ moduleMap: ModuleMap;
46
+ /** Name of the apply-proxy module to materialize. */
47
+ target: string;
48
+ /** Directory to write the plain, deployable module into. */
49
+ outDir: string;
50
+ }
51
+ export interface MaterializeWorkspaceTargetResult {
52
+ /** The deployable module directory the bundle was written to. */
53
+ outDir: string;
54
+ /** The transpiled, content-addressed bundle that was materialized. */
55
+ bundle: MigrationBundle;
56
+ /** The resolved apply spec the materialization was driven by. */
57
+ spec: ResolvedApplySpec;
58
+ }
59
+ /**
60
+ * Resolve an apply-proxy module by name in a workspace and materialize it to a
61
+ * chosen directory as a plain, deployable module (transforms baked in — no
62
+ * `pgpm.apply.json`). Shared by the `pgpm materialize` command and tests; the
63
+ * counterpart to {@link resolveEffectiveModulePath}, but with an explicit,
64
+ * stable `outDir` and no per-process cache.
65
+ */
66
+ export declare function materializeWorkspaceTarget(options: MaterializeWorkspaceTargetOptions): Promise<MaterializeWorkspaceTargetResult>;
@@ -3,15 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.materializeApplyModule = materializeApplyModule;
4
4
  exports.resolveEffectiveModulePath = resolveEffectiveModulePath;
5
5
  exports.clearApplyMaterializationCache = clearApplyMaterializationCache;
6
+ exports.materializeWorkspaceTarget = materializeWorkspaceTarget;
7
+ const bundle_1 = require("@pgpmjs/bundle");
8
+ const slice_1 = require("@pgpmjs/slice");
9
+ const transform_1 = require("@pgpmjs/transform");
6
10
  const fs_1 = require("fs");
7
11
  const os_1 = require("os");
8
12
  const path_1 = require("path");
9
- const bundle_1 = require("@pgpmjs/bundle");
10
- const transform_1 = require("@pgpmjs/transform");
11
- const slice_1 = require("@pgpmjs/slice");
12
13
  const apply_spec_1 = require("./apply-spec");
13
14
  const profile_1 = require("./profile");
14
15
  const reuse_1 = require("./reuse");
16
+ const types_1 = require("./types");
15
17
  // Functions that take a bare schema name as a string-literal first argument.
16
18
  // The AST pass only remaps dotted literals ('schema.object'); these helpers
17
19
  // (ubiquitous in verify scripts) name the schema alone, so a string-level
@@ -35,11 +37,13 @@ const schemaNameLiteralPass = (content, schemaMapping) => {
35
37
  function makeSubsystemStripper(source, spec, instanceName) {
36
38
  const selector = { schemas: spec.exclude.schemas };
37
39
  const rebinds = (0, transform_1.buildSchemaRouter)({ schemaMap: spec.schemas, routes: spec.route });
38
- const fullDeploySql = source.changes
39
- .map(c => c.deploy?.sql ?? '')
40
- .filter(Boolean)
41
- .join('\n');
42
- const analysis = (0, slice_1.excludeSubsystem)(fullDeploySql, selector, { rebinds });
40
+ // One parse per deploy script; the analysis runs over all programs together
41
+ // (cross-change references into the subsystem are caught by construction)
42
+ // and returns per-change drop/strip/prune decisions from the same pass.
43
+ const programs = source.changes
44
+ .filter(c => c.deploy)
45
+ .map(c => [c.name, (0, transform_1.parseSqlProgram)(c.deploy.sql)]);
46
+ const analysis = (0, slice_1.excludeSubsystemPrograms)(programs, selector, { rebinds });
43
47
  if (analysis.unsatisfied.length > 0) {
44
48
  const detail = [
45
49
  ...new Set(analysis.unsatisfied.map(u => `${u.object.schema}.${u.object.name}${u.fk ? ' (foreign key target)' : ''}`))
@@ -49,26 +53,26 @@ function makeSubsystemStripper(source, spec, instanceName) {
49
53
  `${detail} with no route/rebind target. Add "route" entries substituting each ` +
50
54
  `referenced object, or keep the subsystem.`);
51
55
  }
52
- // A change whose deploy consists entirely of subsystem statements (plus
53
- // transaction control) is dropped *as a change* — removed from the plan and
54
- // deploy order rather than emitted as an empty script (empty scripts collide
55
- // on the deploy ledger's script-hash uniqueness). Its verify/revert scripts
56
- // target dropped objects the classifier can't always see (bare DROPs, catalog
57
- // probes), which is exactly why the whole change is dropped, not just blanked.
56
+ // A fully-excluded change is dropped *as a change* — removed from the plan
57
+ // and deploy order rather than emitted as an empty script (empty scripts
58
+ // collide on the deploy ledger's script-hash uniqueness). Its verify/revert
59
+ // scripts target dropped objects the classifier can't always see (bare
60
+ // DROPs, catalog probes), which is exactly why the whole change is dropped,
61
+ // not just blanked.
58
62
  const excludedChanges = new Set();
59
- for (const change of source.changes) {
60
- if (!change.deploy)
61
- continue;
62
- const stripped = (0, slice_1.stripSubsystemSql)(change.deploy.sql, selector);
63
- const survivors = stripped.result.kept.filter(i => !stripped.dropped.includes(i) && stripped.result.statements[i].nodeTag !== 'TransactionStmt');
64
- if (stripped.dropped.length > 0 && survivors.length === 0) {
65
- excludedChanges.add(change.name);
66
- }
63
+ const strippedDeploys = new Map();
64
+ for (const [name, program] of programs) {
65
+ const exclusion = analysis.programs.get(name);
66
+ if (exclusion.fullyExcluded)
67
+ excludedChanges.add(name);
68
+ else
69
+ strippedDeploys.set(program.source, exclusion.sql);
67
70
  }
68
71
  // Surviving changes may still contain stray subsystem statements (a change
69
72
  // that both creates a survivor and touches the excluded subsystem); strip
70
- // those in place. Fully-excluded changes are dropped via `excludedChanges`.
71
- const strip = (sql) => (0, slice_1.stripSubsystemSql)(sql, selector).sql;
73
+ // those in place. Verify/revert scripts are separate sources, stripped on
74
+ // demand with the same selector.
75
+ const strip = (sql) => strippedDeploys.get(sql) ?? (0, slice_1.stripSubsystemSql)(sql, selector).sql;
72
76
  return { strip, excludedChanges };
73
77
  }
74
78
  /**
@@ -198,3 +202,43 @@ async function resolveEffectiveModulePath(moduleName, modulePath, moduleMap, wor
198
202
  function clearApplyMaterializationCache() {
199
203
  materializedCache.clear();
200
204
  }
205
+ /**
206
+ * Resolve an apply-proxy module by name in a workspace and materialize it to a
207
+ * chosen directory as a plain, deployable module (transforms baked in — no
208
+ * `pgpm.apply.json`). Shared by the `pgpm materialize` command and tests; the
209
+ * counterpart to {@link resolveEffectiveModulePath}, but with an explicit,
210
+ * stable `outDir` and no per-process cache.
211
+ */
212
+ async function materializeWorkspaceTarget(options) {
213
+ const { workspacePath, moduleMap, target, outDir } = options;
214
+ const module = moduleMap[target];
215
+ if (!module) {
216
+ const proxies = Object.keys(moduleMap).sort();
217
+ throw new Error(`Module "${target}" not found in the workspace.` +
218
+ (proxies.length ? ` Available modules: ${proxies.join(', ')}.` : ''));
219
+ }
220
+ const modulePath = (0, path_1.resolve)(workspacePath, module.path);
221
+ if (!(0, apply_spec_1.hasApplySpec)(modulePath)) {
222
+ throw new Error(`Module "${target}" is not an apply proxy (no ${types_1.APPLY_SPEC_FILE} in ${modulePath}). ` +
223
+ `Only apply-proxy modules can be materialized.`);
224
+ }
225
+ const spec = (0, profile_1.resolveEffectiveApplySpec)((0, apply_spec_1.readApplySpec)(modulePath), (0, profile_1.loadWorkspaceRoutingProfile)(workspacePath));
226
+ if ((0, reuse_1.isReuseSpec)(spec)) {
227
+ throw new Error(`Apply proxy "${target}" is a reuse proxy (shared + per-tenant). ` +
228
+ `Reuse proxies are not yet supported by \`pgpm materialize\`.`);
229
+ }
230
+ const sourceModule = moduleMap[spec.source.module];
231
+ if (!sourceModule) {
232
+ throw new Error(`Apply spec in "${target}" references source module "${spec.source.module}", ` +
233
+ `which was not found in the workspace. Run \`pgpm install\` to install it` +
234
+ (spec.source.package ? ` (${spec.source.package})` : '') +
235
+ `.`);
236
+ }
237
+ const sourceDir = (0, path_1.resolve)(workspacePath, sourceModule.path);
238
+ const { outDir: writtenDir, bundle } = await materializeApplyModule({
239
+ sourceDir,
240
+ spec,
241
+ outDir
242
+ });
243
+ return { outDir: writtenDir, bundle, spec };
244
+ }
@@ -27,6 +27,18 @@ export declare function buildExecutableBundle(moduleDir: string, options?: Creat
27
27
  * Emit a module's stored bundle artifact into `sql/`, returning its path.
28
28
  */
29
29
  export declare function writeBundleArtifact(moduleDir: string, version: string, options?: CreateBundleOptions): Promise<string>;
30
+ /**
31
+ * Whether a stored bundle still describes what is on disk.
32
+ *
33
+ * `verifyBundle` only proves an artifact is internally consistent — it cannot
34
+ * tell that `deploy/` moved on since the artifact was written. A committed
35
+ * artifact plus a rebased branch is exactly that case, and using it would
36
+ * deploy the stale SQL while recording hashes for it, silently.
37
+ *
38
+ * The check is deliberately I/O-only (read + sha256, no parse), so it costs a
39
+ * fraction of the parse/deparse work the artifact exists to avoid.
40
+ */
41
+ export declare function bundleMatchesModule(moduleDir: string, bundle: MigrationBundle): boolean;
30
42
  /**
31
43
  * Read a module's stored bundle artifact, or `null` when it has none or the
32
44
  * archive cannot be read. Never throws — a broken artifact must degrade to the
@@ -4,9 +4,11 @@ exports.bundleArtifactFileName = bundleArtifactFileName;
4
4
  exports.resolveBundleArtifactPath = resolveBundleArtifactPath;
5
5
  exports.buildExecutableBundle = buildExecutableBundle;
6
6
  exports.writeBundleArtifact = writeBundleArtifact;
7
+ exports.bundleMatchesModule = bundleMatchesModule;
7
8
  exports.readBundleArtifact = readBundleArtifact;
8
9
  const fs_1 = require("fs");
9
10
  const path_1 = require("path");
11
+ const ast_1 = require("@pgpmjs/ast");
10
12
  const files_1 = require("@pgpmjs/ast/files");
11
13
  const bundle_1 = require("@pgpmjs/bundle");
12
14
  const clean_1 = require("../migrate/clean");
@@ -78,6 +80,34 @@ async function writeBundleArtifact(moduleDir, version, options) {
78
80
  (0, bundle_1.writeBundleArchiveFile)(bundle, outPath);
79
81
  return outPath;
80
82
  }
83
+ /**
84
+ * Whether a stored bundle still describes what is on disk.
85
+ *
86
+ * `verifyBundle` only proves an artifact is internally consistent — it cannot
87
+ * tell that `deploy/` moved on since the artifact was written. A committed
88
+ * artifact plus a rebased branch is exactly that case, and using it would
89
+ * deploy the stale SQL while recording hashes for it, silently.
90
+ *
91
+ * The check is deliberately I/O-only (read + sha256, no parse), so it costs a
92
+ * fraction of the parse/deparse work the artifact exists to avoid.
93
+ */
94
+ function bundleMatchesModule(moduleDir, bundle) {
95
+ const planPath = (0, path_1.join)(moduleDir, 'pgpm.plan');
96
+ if (!(0, fs_1.existsSync)(planPath))
97
+ return false;
98
+ if ((0, fs_1.readFileSync)(planPath, 'utf-8') !== bundle.plan)
99
+ return false;
100
+ for (const change of bundle.changes) {
101
+ if (!change.deploy)
102
+ continue;
103
+ const deployPath = (0, path_1.join)(moduleDir, 'deploy', `${change.name}.sql`);
104
+ if (!(0, fs_1.existsSync)(deployPath))
105
+ return false;
106
+ if ((0, ast_1.hashString)((0, fs_1.readFileSync)(deployPath, 'utf-8')) !== change.deploy.digest)
107
+ return false;
108
+ }
109
+ return true;
110
+ }
81
111
  /**
82
112
  * Read a module's stored bundle artifact, or `null` when it has none or the
83
113
  * archive cannot be read. Never throws — a broken artifact must degrade to the
@@ -47,11 +47,17 @@ async function loadExecutableBundle(moduleDir) {
47
47
  const stored = (0, artifact_1.readBundleArtifact)(moduleDir);
48
48
  if (stored) {
49
49
  const issues = (0, bundle_1.verifyBundle)(stored);
50
- if (issues.length === 0) {
50
+ if (issues.length > 0) {
51
+ log.warn(`⚠️ Bundle artifact for ${stored.manifest.name} failed verification ` +
52
+ `(${issues.map(i => i.kind).join(', ')}); rebuilding from deploy/.`);
53
+ }
54
+ else if (!(0, artifact_1.bundleMatchesModule)(moduleDir, stored)) {
55
+ log.warn(`⚠️ Bundle artifact for ${stored.manifest.name} is stale (deploy/ has ` +
56
+ `changed since it was packaged); rebuilding from deploy/.`);
57
+ }
58
+ else {
51
59
  return { bundle: stored, source: 'artifact' };
52
60
  }
53
- log.warn(`⚠️ Bundle artifact for ${stored.manifest.name} failed verification ` +
54
- `(${issues.map(i => i.kind).join(', ')}); rebuilding from deploy/.`);
55
61
  }
56
62
  return { bundle: await (0, artifact_1.buildExecutableBundle)(moduleDir), source: 'packaged' };
57
63
  }
@@ -1,12 +1,13 @@
1
+ import { bundleFromModule, materializeBundle, transpileBundle, verifyBundle } from '@pgpmjs/bundle';
2
+ import { excludeSubsystemPrograms, stripSubsystemSql } from '@pgpmjs/slice';
3
+ import { buildSchemaRouter, loadModule, makeSchemaTranspiler, parseSqlProgram } from '@pgpmjs/transform';
1
4
  import { mkdtempSync } from 'fs';
2
5
  import { tmpdir } from 'os';
3
6
  import { join, resolve } from 'path';
4
- import { bundleFromModule, materializeBundle, transpileBundle, verifyBundle } from '@pgpmjs/bundle';
5
- import { buildSchemaRouter, loadModule, makeSchemaTranspiler } from '@pgpmjs/transform';
6
- import { excludeSubsystem, stripSubsystemSql } from '@pgpmjs/slice';
7
7
  import { hasApplySpec, readApplySpec } from './apply-spec';
8
8
  import { loadWorkspaceRoutingProfile, resolveEffectiveApplySpec } from './profile';
9
9
  import { isReuseSpec, materializeReuseModule, resolveSharedModuleName } from './reuse';
10
+ import { APPLY_SPEC_FILE } from './types';
10
11
  // Functions that take a bare schema name as a string-literal first argument.
11
12
  // The AST pass only remaps dotted literals ('schema.object'); these helpers
12
13
  // (ubiquitous in verify scripts) name the schema alone, so a string-level
@@ -30,11 +31,13 @@ const schemaNameLiteralPass = (content, schemaMapping) => {
30
31
  function makeSubsystemStripper(source, spec, instanceName) {
31
32
  const selector = { schemas: spec.exclude.schemas };
32
33
  const rebinds = buildSchemaRouter({ schemaMap: spec.schemas, routes: spec.route });
33
- const fullDeploySql = source.changes
34
- .map(c => c.deploy?.sql ?? '')
35
- .filter(Boolean)
36
- .join('\n');
37
- const analysis = excludeSubsystem(fullDeploySql, selector, { rebinds });
34
+ // One parse per deploy script; the analysis runs over all programs together
35
+ // (cross-change references into the subsystem are caught by construction)
36
+ // and returns per-change drop/strip/prune decisions from the same pass.
37
+ const programs = source.changes
38
+ .filter(c => c.deploy)
39
+ .map(c => [c.name, parseSqlProgram(c.deploy.sql)]);
40
+ const analysis = excludeSubsystemPrograms(programs, selector, { rebinds });
38
41
  if (analysis.unsatisfied.length > 0) {
39
42
  const detail = [
40
43
  ...new Set(analysis.unsatisfied.map(u => `${u.object.schema}.${u.object.name}${u.fk ? ' (foreign key target)' : ''}`))
@@ -44,26 +47,26 @@ function makeSubsystemStripper(source, spec, instanceName) {
44
47
  `${detail} with no route/rebind target. Add "route" entries substituting each ` +
45
48
  `referenced object, or keep the subsystem.`);
46
49
  }
47
- // A change whose deploy consists entirely of subsystem statements (plus
48
- // transaction control) is dropped *as a change* — removed from the plan and
49
- // deploy order rather than emitted as an empty script (empty scripts collide
50
- // on the deploy ledger's script-hash uniqueness). Its verify/revert scripts
51
- // target dropped objects the classifier can't always see (bare DROPs, catalog
52
- // probes), which is exactly why the whole change is dropped, not just blanked.
50
+ // A fully-excluded change is dropped *as a change* — removed from the plan
51
+ // and deploy order rather than emitted as an empty script (empty scripts
52
+ // collide on the deploy ledger's script-hash uniqueness). Its verify/revert
53
+ // scripts target dropped objects the classifier can't always see (bare
54
+ // DROPs, catalog probes), which is exactly why the whole change is dropped,
55
+ // not just blanked.
53
56
  const excludedChanges = new Set();
54
- for (const change of source.changes) {
55
- if (!change.deploy)
56
- continue;
57
- const stripped = stripSubsystemSql(change.deploy.sql, selector);
58
- const survivors = stripped.result.kept.filter(i => !stripped.dropped.includes(i) && stripped.result.statements[i].nodeTag !== 'TransactionStmt');
59
- if (stripped.dropped.length > 0 && survivors.length === 0) {
60
- excludedChanges.add(change.name);
61
- }
57
+ const strippedDeploys = new Map();
58
+ for (const [name, program] of programs) {
59
+ const exclusion = analysis.programs.get(name);
60
+ if (exclusion.fullyExcluded)
61
+ excludedChanges.add(name);
62
+ else
63
+ strippedDeploys.set(program.source, exclusion.sql);
62
64
  }
63
65
  // Surviving changes may still contain stray subsystem statements (a change
64
66
  // that both creates a survivor and touches the excluded subsystem); strip
65
- // those in place. Fully-excluded changes are dropped via `excludedChanges`.
66
- const strip = (sql) => stripSubsystemSql(sql, selector).sql;
67
+ // those in place. Verify/revert scripts are separate sources, stripped on
68
+ // demand with the same selector.
69
+ const strip = (sql) => strippedDeploys.get(sql) ?? stripSubsystemSql(sql, selector).sql;
67
70
  return { strip, excludedChanges };
68
71
  }
69
72
  /**
@@ -193,3 +196,43 @@ export async function resolveEffectiveModulePath(moduleName, modulePath, moduleM
193
196
  export function clearApplyMaterializationCache() {
194
197
  materializedCache.clear();
195
198
  }
199
+ /**
200
+ * Resolve an apply-proxy module by name in a workspace and materialize it to a
201
+ * chosen directory as a plain, deployable module (transforms baked in — no
202
+ * `pgpm.apply.json`). Shared by the `pgpm materialize` command and tests; the
203
+ * counterpart to {@link resolveEffectiveModulePath}, but with an explicit,
204
+ * stable `outDir` and no per-process cache.
205
+ */
206
+ export async function materializeWorkspaceTarget(options) {
207
+ const { workspacePath, moduleMap, target, outDir } = options;
208
+ const module = moduleMap[target];
209
+ if (!module) {
210
+ const proxies = Object.keys(moduleMap).sort();
211
+ throw new Error(`Module "${target}" not found in the workspace.` +
212
+ (proxies.length ? ` Available modules: ${proxies.join(', ')}.` : ''));
213
+ }
214
+ const modulePath = resolve(workspacePath, module.path);
215
+ if (!hasApplySpec(modulePath)) {
216
+ throw new Error(`Module "${target}" is not an apply proxy (no ${APPLY_SPEC_FILE} in ${modulePath}). ` +
217
+ `Only apply-proxy modules can be materialized.`);
218
+ }
219
+ const spec = resolveEffectiveApplySpec(readApplySpec(modulePath), loadWorkspaceRoutingProfile(workspacePath));
220
+ if (isReuseSpec(spec)) {
221
+ throw new Error(`Apply proxy "${target}" is a reuse proxy (shared + per-tenant). ` +
222
+ `Reuse proxies are not yet supported by \`pgpm materialize\`.`);
223
+ }
224
+ const sourceModule = moduleMap[spec.source.module];
225
+ if (!sourceModule) {
226
+ throw new Error(`Apply spec in "${target}" references source module "${spec.source.module}", ` +
227
+ `which was not found in the workspace. Run \`pgpm install\` to install it` +
228
+ (spec.source.package ? ` (${spec.source.package})` : '') +
229
+ `.`);
230
+ }
231
+ const sourceDir = resolve(workspacePath, sourceModule.path);
232
+ const { outDir: writtenDir, bundle } = await materializeApplyModule({
233
+ sourceDir,
234
+ spec,
235
+ outDir
236
+ });
237
+ return { outDir: writtenDir, bundle, spec };
238
+ }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync } from 'fs';
2
2
  import { join } from 'path';
3
+ import { hashString } from '@pgpmjs/ast';
3
4
  import { getExtensionName, parseControlContent } from '@pgpmjs/ast/files';
4
5
  import { BUNDLE_ARCHIVE_EXTENSION, bundleFromModule, readBundleArchiveFile, withExecutableSql, writeBundleArchiveFile } from '@pgpmjs/bundle';
5
6
  import { cleanSql } from '../migrate/clean';
@@ -71,6 +72,34 @@ export async function writeBundleArtifact(moduleDir, version, options) {
71
72
  writeBundleArchiveFile(bundle, outPath);
72
73
  return outPath;
73
74
  }
75
+ /**
76
+ * Whether a stored bundle still describes what is on disk.
77
+ *
78
+ * `verifyBundle` only proves an artifact is internally consistent — it cannot
79
+ * tell that `deploy/` moved on since the artifact was written. A committed
80
+ * artifact plus a rebased branch is exactly that case, and using it would
81
+ * deploy the stale SQL while recording hashes for it, silently.
82
+ *
83
+ * The check is deliberately I/O-only (read + sha256, no parse), so it costs a
84
+ * fraction of the parse/deparse work the artifact exists to avoid.
85
+ */
86
+ export function bundleMatchesModule(moduleDir, bundle) {
87
+ const planPath = join(moduleDir, 'pgpm.plan');
88
+ if (!existsSync(planPath))
89
+ return false;
90
+ if (readFileSync(planPath, 'utf-8') !== bundle.plan)
91
+ return false;
92
+ for (const change of bundle.changes) {
93
+ if (!change.deploy)
94
+ continue;
95
+ const deployPath = join(moduleDir, 'deploy', `${change.name}.sql`);
96
+ if (!existsSync(deployPath))
97
+ return false;
98
+ if (hashString(readFileSync(deployPath, 'utf-8')) !== change.deploy.digest)
99
+ return false;
100
+ }
101
+ return true;
102
+ }
74
103
  /**
75
104
  * Read a module's stored bundle artifact, or `null` when it has none or the
76
105
  * archive cannot be read. Never throws — a broken artifact must degrade to the
@@ -2,7 +2,7 @@ import { Logger } from '@pgpmjs/logger';
2
2
  import { verifyBundle } from '@pgpmjs/bundle';
3
3
  import { getPgPool } from 'pg-cache';
4
4
  import { PgpmMigrate } from '../migrate/client';
5
- import { buildExecutableBundle, readBundleArtifact } from './artifact';
5
+ import { bundleMatchesModule, buildExecutableBundle, readBundleArtifact } from './artifact';
6
6
  const log = new Logger('deploy-fast');
7
7
  /** Thrown when the ledger already holds a change with different content. */
8
8
  export class BundledDeployConflictError extends Error {
@@ -40,11 +40,17 @@ async function loadExecutableBundle(moduleDir) {
40
40
  const stored = readBundleArtifact(moduleDir);
41
41
  if (stored) {
42
42
  const issues = verifyBundle(stored);
43
- if (issues.length === 0) {
43
+ if (issues.length > 0) {
44
+ log.warn(`⚠️ Bundle artifact for ${stored.manifest.name} failed verification ` +
45
+ `(${issues.map(i => i.kind).join(', ')}); rebuilding from deploy/.`);
46
+ }
47
+ else if (!bundleMatchesModule(moduleDir, stored)) {
48
+ log.warn(`⚠️ Bundle artifact for ${stored.manifest.name} is stale (deploy/ has ` +
49
+ `changed since it was packaged); rebuilding from deploy/.`);
50
+ }
51
+ else {
44
52
  return { bundle: stored, source: 'artifact' };
45
53
  }
46
- log.warn(`⚠️ Bundle artifact for ${stored.manifest.name} failed verification ` +
47
- `(${issues.map(i => i.kind).join(', ')}); rebuilding from deploy/.`);
48
54
  }
49
55
  return { bundle: await buildExecutableBundle(moduleDir), source: 'packaged' };
50
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/core",
3
- "version": "7.17.2",
3
+ "version": "7.19.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PGPM Package and Migration Tools",
6
6
  "main": "index.js",
@@ -48,24 +48,24 @@
48
48
  "makage": "^0.3.0"
49
49
  },
50
50
  "dependencies": {
51
- "@pgpmjs/ast": "^0.9.5",
52
- "@pgpmjs/bundle": "^0.12.4",
53
- "@pgpmjs/env": "^2.39.2",
51
+ "@pgpmjs/ast": "^0.9.6",
52
+ "@pgpmjs/bundle": "^0.12.5",
53
+ "@pgpmjs/env": "^2.39.3",
54
54
  "@pgpmjs/logger": "^2.22.1",
55
- "@pgpmjs/server-utils": "^3.23.4",
56
- "@pgpmjs/slice": "^0.11.2",
57
- "@pgpmjs/transform": "^0.12.2",
58
- "@pgpmjs/types": "^2.48.1",
55
+ "@pgpmjs/server-utils": "^3.23.5",
56
+ "@pgpmjs/slice": "^0.12.1",
57
+ "@pgpmjs/transform": "^0.13.1",
58
+ "@pgpmjs/types": "^2.48.2",
59
59
  "csv-to-pg": "^4.8.1",
60
60
  "genomic": "^5.6.2",
61
61
  "glob": "^13.0.6",
62
62
  "parse-package-name": "^1.0.0",
63
63
  "pg": "^8.21.0",
64
- "pg-cache": "^3.23.4",
64
+ "pg-cache": "^3.23.5",
65
65
  "pg-env": "^1.26.1",
66
66
  "pgsql-deparser": "^18.1.1",
67
67
  "pgsql-parser": "^18.1.1",
68
68
  "yanse": "^0.2.1"
69
69
  },
70
- "gitHead": "a430897efa1488ec24fc636f0d5023207bba7ba7"
70
+ "gitHead": "947334d361e9f4c48d451f1c7312b2d705ea154e"
71
71
  }