@pgpmjs/core 7.0.0 → 7.2.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.
Files changed (102) hide show
  1. package/bundle/apply.d.ts +93 -0
  2. package/bundle/apply.js +137 -0
  3. package/bundle/index.d.ts +12 -0
  4. package/bundle/index.js +31 -0
  5. package/core/class/pgpm.d.ts +2 -2
  6. package/core/class/pgpm.js +6 -6
  7. package/esm/bundle/apply.js +131 -0
  8. package/esm/bundle/index.js +15 -0
  9. package/esm/core/class/pgpm.js +6 -6
  10. package/esm/index.js +7 -3
  11. package/esm/migrate/client.js +1 -1
  12. package/esm/migrate/utils/hash.js +3 -6
  13. package/esm/modules/modules.js +1 -1
  14. package/esm/packaging/package.js +40 -24
  15. package/esm/rebundle/index.js +4 -0
  16. package/esm/rebundle/module.js +111 -0
  17. package/esm/rebundle/rebundle.js +170 -0
  18. package/esm/rebundle/workspace.js +170 -0
  19. package/esm/refactor/rename.js +5 -27
  20. package/esm/resolution/deps.js +2 -2
  21. package/esm/resolution/resolve.js +2 -2
  22. package/esm/slice/slice.js +1 -1
  23. package/esm/workspace/minimal.js +73 -0
  24. package/index.d.ts +6 -2
  25. package/index.js +8 -4
  26. package/migrate/client.js +1 -1
  27. package/migrate/utils/hash.d.ts +2 -4
  28. package/migrate/utils/hash.js +4 -8
  29. package/modules/modules.d.ts +1 -1
  30. package/modules/modules.js +1 -1
  31. package/package.json +4 -2
  32. package/packaging/package.d.ts +23 -1
  33. package/packaging/package.js +42 -25
  34. package/rebundle/index.d.ts +4 -0
  35. package/{files/sql-scripts → rebundle}/index.js +4 -2
  36. package/rebundle/module.d.ts +23 -0
  37. package/rebundle/module.js +116 -0
  38. package/rebundle/rebundle.d.ts +26 -0
  39. package/rebundle/rebundle.js +175 -0
  40. package/rebundle/types.d.ts +185 -0
  41. package/rebundle/workspace.d.ts +15 -0
  42. package/rebundle/workspace.js +173 -0
  43. package/refactor/rename.d.ts +2 -8
  44. package/refactor/rename.js +6 -29
  45. package/resolution/deps.d.ts +1 -1
  46. package/resolution/deps.js +2 -2
  47. package/resolution/resolve.js +2 -2
  48. package/slice/slice.d.ts +1 -1
  49. package/slice/slice.js +1 -1
  50. package/slice/types.d.ts +1 -1
  51. package/workspace/minimal.d.ts +64 -0
  52. package/workspace/minimal.js +82 -0
  53. package/esm/files/extension/index.js +0 -3
  54. package/esm/files/extension/reader.js +0 -79
  55. package/esm/files/extension/writer.js +0 -99
  56. package/esm/files/index.js +0 -6
  57. package/esm/files/plan/generator.js +0 -82
  58. package/esm/files/plan/index.js +0 -5
  59. package/esm/files/plan/parser.js +0 -297
  60. package/esm/files/plan/validators.js +0 -181
  61. package/esm/files/plan/writer.js +0 -142
  62. package/esm/files/sql/header.js +0 -259
  63. package/esm/files/sql/index.js +0 -2
  64. package/esm/files/sql/writer.js +0 -107
  65. package/esm/files/sql-scripts/index.js +0 -2
  66. package/esm/files/sql-scripts/reader.js +0 -19
  67. package/esm/files/types/index.js +0 -1
  68. package/esm/utils/author.js +0 -11
  69. package/files/extension/index.d.ts +0 -2
  70. package/files/extension/index.js +0 -19
  71. package/files/extension/reader.d.ts +0 -24
  72. package/files/extension/reader.js +0 -86
  73. package/files/extension/writer.d.ts +0 -48
  74. package/files/extension/writer.js +0 -107
  75. package/files/index.d.ts +0 -5
  76. package/files/index.js +0 -22
  77. package/files/plan/generator.d.ts +0 -23
  78. package/files/plan/generator.js +0 -90
  79. package/files/plan/index.d.ts +0 -4
  80. package/files/plan/index.js +0 -21
  81. package/files/plan/parser.d.ts +0 -28
  82. package/files/plan/parser.js +0 -304
  83. package/files/plan/validators.d.ts +0 -52
  84. package/files/plan/validators.js +0 -187
  85. package/files/plan/writer.d.ts +0 -27
  86. package/files/plan/writer.js +0 -152
  87. package/files/sql/header.d.ts +0 -114
  88. package/files/sql/header.js +0 -266
  89. package/files/sql/index.d.ts +0 -2
  90. package/files/sql/index.js +0 -18
  91. package/files/sql/writer.d.ts +0 -12
  92. package/files/sql/writer.js +0 -114
  93. package/files/sql-scripts/index.d.ts +0 -1
  94. package/files/sql-scripts/reader.d.ts +0 -8
  95. package/files/sql-scripts/reader.js +0 -23
  96. package/files/types/index.d.ts +0 -46
  97. package/files/types/index.js +0 -17
  98. package/files/types/package.d.ts +0 -20
  99. package/utils/author.d.ts +0 -5
  100. package/utils/author.js +0 -14
  101. /package/esm/{files/types/package.js → rebundle/types.js} +0 -0
  102. /package/{files/types/package.js → rebundle/types.js} +0 -0
@@ -0,0 +1,185 @@
1
+ import { Change } from '@pgpmjs/ast/files/types';
2
+ import { SliceWarning } from '../slice/types';
3
+ /**
4
+ * Strategy for rebundling a module's changes into fewer, larger chunks.
5
+ *
6
+ * Rebundling walks the source plan in order and greedily accumulates
7
+ * consecutive changes into a chunk until a boundary is hit. Because chunks
8
+ * are contiguous runs of the source plan, the concatenated deploy/verify/
9
+ * revert output is byte-identical to the original module — the granularity
10
+ * dial never reorders statements, it only decides where the chunk seams fall.
11
+ */
12
+ export interface RebundleStrategy {
13
+ /**
14
+ * How chunk boundaries are chosen:
15
+ * - 'folder' (default): start a new chunk when the folder-derived key
16
+ * changes (uses the same path extraction as slice's folder strategy)
17
+ * - 'none': one chunk for the whole module, subject only to `maxChunkSize`
18
+ * - 'category': start a new chunk when the caller-supplied `categoryOf` key
19
+ * changes — the facts/classifier-driven seam. Lets the seams follow meaning
20
+ * (e.g. security, roles, admin) instead of paths. `categoryOf` is required;
21
+ * for changes it returns `undefined` for, the folder key is used as a
22
+ * fallback so partial classification degrades gracefully.
23
+ */
24
+ boundary?: 'folder' | 'none' | 'category';
25
+ /**
26
+ * Seam key for a change under `boundary: 'category'`. Consecutive changes
27
+ * sharing a key merge into one chunk (same greedy, contiguity-preserving rule
28
+ * as the folder boundary — so the byte-identical invariant is unaffected).
29
+ * pgpm-core stays classifier-agnostic: callers derive the category however
30
+ * they like (e.g. from `classifyStatements` facts) and pass it in here.
31
+ */
32
+ categoryOf?: (changeName: string) => string | undefined;
33
+ /** Depth in the change path used to derive the folder key (default: 1) */
34
+ depth?: number;
35
+ /** Path prefix stripped before deriving the folder key (default: 'schemas') */
36
+ prefixToStrip?: string;
37
+ /**
38
+ * Maximum number of member changes per chunk. When reached, the current
39
+ * chunk is sealed and a new one begins even if the boundary key is unchanged.
40
+ * Undefined or 0 means unlimited.
41
+ */
42
+ maxChunkSize?: number;
43
+ }
44
+ /**
45
+ * A single rebundled chunk: a contiguous run of source-plan changes that will
46
+ * be merged into one migration. Member ordering is preserved in all three
47
+ * directions the pgpm engine cares about.
48
+ */
49
+ export interface Chunk {
50
+ /** Deterministic chunk identifier */
51
+ name: string;
52
+ /** Member change names in deploy order (== source plan order) */
53
+ deploy: string[];
54
+ /** Member change names in verify order (same as deploy) */
55
+ verify: string[];
56
+ /** Member change names in revert order (reverse of deploy) */
57
+ revert: string[];
58
+ /** Other chunk names this chunk depends on (always earlier in deployOrder) */
59
+ dependencies: string[];
60
+ }
61
+ /**
62
+ * Result of computing a rebundle plan for a module.
63
+ */
64
+ export interface RebundleResult {
65
+ /** Chunks in deploy order */
66
+ chunks: Chunk[];
67
+ /** Chunk names in deploy order */
68
+ deployOrder: string[];
69
+ /** Warnings encountered while rebundling */
70
+ warnings: SliceWarning[];
71
+ /** The source changes (in plan order) that were rebundled */
72
+ sourceChanges: Change[];
73
+ /** The source module's project name (from the plan `%project` header) */
74
+ project: string;
75
+ }
76
+ /**
77
+ * Options for materializing a rebundled module to disk.
78
+ */
79
+ export interface RebundleModuleOptions extends RebundleStrategy {
80
+ /** Directory to write the rebundled module into */
81
+ outputDir: string;
82
+ /** Overwrite an existing, non-empty output directory (default: false) */
83
+ overwrite?: boolean;
84
+ /** Deparse pretty-printing (default: true) */
85
+ pretty?: boolean;
86
+ /** Function body delimiter used during deparse (default: '$EOFCODE$') */
87
+ functionDelimiter?: string;
88
+ }
89
+ /**
90
+ * How cross-chunk dependencies are represented when chunks are emitted as
91
+ * separate modules in a workspace.
92
+ *
93
+ * - `change`: also record the fine-grained plan cross-reference (`dep:dep`) in
94
+ * addition to the control `requires`.
95
+ * - `control-only`: carry the dependency solely via the control-file `requires`,
96
+ * dropping the per-change plan reference. Extension install ordering deploys
97
+ * all of the dependency before any of the dependent, which is strictly
98
+ * stronger than the fine edge — the right mode for publishing chunks as
99
+ * independent, versioned module forks.
100
+ */
101
+ export type CrossChunkDepMode = 'change' | 'control-only';
102
+ /**
103
+ * Options for materializing a rebundled workspace (one module per chunk).
104
+ */
105
+ export interface RebundleWorkspaceOptions extends RebundleStrategy {
106
+ /** Directory to write the workspace into (packages land in `packages/<chunk>/`) */
107
+ outputDir: string;
108
+ /** Overwrite an existing, non-empty output directory (default: false) */
109
+ overwrite?: boolean;
110
+ /** Deparse pretty-printing (default: true) */
111
+ pretty?: boolean;
112
+ /** Function body delimiter used during deparse (default: '$EOFCODE$') */
113
+ functionDelimiter?: string;
114
+ /** How cross-chunk deps are represented (default: 'control-only') */
115
+ crossChunkDepMode?: CrossChunkDepMode;
116
+ /**
117
+ * When `true`, scaffold each emitted module (and the workspace root) from the
118
+ * real `pgpm init` boilerplate template (`scaffoldTemplate`) before writing
119
+ * the merged pgpm files on top — so the output carries the full developer
120
+ * shell (README/LICENSE/jest/tests, workspace `pnpm-workspace.yaml`/`lerna.json`
121
+ * /tsconfig/lint/CI) rather than the minimal deployable set.
122
+ *
123
+ * Defaults to `false` (the lightweight, network-free minimal writer). Setting
124
+ * it `true` fetches the boilerplate repo (network), matching `pgpm init`.
125
+ */
126
+ initScaffold?: boolean;
127
+ /** Template repo for `initScaffold` (defaults to the pgpm boilerplates repo) */
128
+ templateRepo?: string;
129
+ /** Branch/tag for the `initScaffold` template repo */
130
+ templateBranch?: string;
131
+ /**
132
+ * License for the emitted module package.json (and the `pgpm init` answers
133
+ * when `initScaffold` is set). Defaults to 'CONSTRUCTIVE'.
134
+ */
135
+ license?: string;
136
+ }
137
+ /**
138
+ * A single emitted chunk-module within a rebundled workspace.
139
+ */
140
+ export interface RebundlePackage {
141
+ /** Chunk/module name (also the extension name) */
142
+ name: string;
143
+ /** Package directory, relative to the workspace outputDir */
144
+ dir: string;
145
+ /** Names of other chunk-modules this one depends on */
146
+ dependencies: string[];
147
+ }
148
+ /**
149
+ * Result of materializing a rebundled workspace.
150
+ */
151
+ export interface RebundleWorkspaceResult extends RebundleResult {
152
+ /** Directory the workspace was written to */
153
+ outputDir: string;
154
+ /** The emitted chunk-modules, in deploy order */
155
+ packages: RebundlePackage[];
156
+ /** The dep representation used */
157
+ crossChunkDepMode: CrossChunkDepMode;
158
+ /**
159
+ * Byte-identical gate: the merged concatenation of all package deploy scripts
160
+ * (in workspace deploy order) equals the merged original module deploy output.
161
+ */
162
+ invariant: {
163
+ ok: boolean;
164
+ };
165
+ }
166
+ /**
167
+ * Result of materializing a rebundled module.
168
+ */
169
+ export interface RebundleModuleResult extends RebundleResult {
170
+ /** Directory the rebundled module was written to */
171
+ outputDir: string;
172
+ /** Per-chunk merged file paths, relative to outputDir */
173
+ written: {
174
+ chunk: string;
175
+ deploy: string;
176
+ revert: string;
177
+ verify: string;
178
+ }[];
179
+ /** Byte-identical gate result: packageModule(source) === packageModule(output) */
180
+ invariant: {
181
+ ok: boolean;
182
+ sourceDiff: boolean;
183
+ outputDiff: boolean;
184
+ };
185
+ }
@@ -0,0 +1,15 @@
1
+ import { RebundleWorkspaceOptions, RebundleWorkspaceResult } from './types';
2
+ /**
3
+ * Materialize a rebundled workspace: emit each chunk as its own tiny module
4
+ * (one merged change) under `packages/<chunk>/`, carrying cross-chunk deps via
5
+ * the control-file `requires` (and optionally a plan cross-reference).
6
+ *
7
+ * This is the "modular workspace" carve — the same monolith sliced into
8
+ * independently publishable module forks (e.g. a users module, an admin module).
9
+ * The emitted directory is a real, discoverable pgpm workspace: it reuses
10
+ * `writeMinimalWorkspace`/`writeMinimalModule` (the minimal deployable file set
11
+ * pgpm recognizes — `pgpm.json`, `pgpm.plan`, `<name>.control`, `Makefile`,
12
+ * `package.json`), `rebundlePlan` for the chunk model, and `mergeChunkScript`
13
+ * for the per-chunk merge.
14
+ */
15
+ export declare function rebundleWorkspace(sourceDir: string, options: RebundleWorkspaceOptions): Promise<RebundleWorkspaceResult>;
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rebundleWorkspace = rebundleWorkspace;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const template_scaffold_1 = require("../core/template-scaffold");
7
+ const parser_1 = require("@pgpmjs/ast/files/plan/parser");
8
+ const package_1 = require("../packaging/package");
9
+ const resolve_1 = require("../resolution/resolve");
10
+ const minimal_1 = require("../workspace/minimal");
11
+ const module_1 = require("./module");
12
+ const rebundle_1 = require("./rebundle");
13
+ /**
14
+ * Materialize a rebundled workspace: emit each chunk as its own tiny module
15
+ * (one merged change) under `packages/<chunk>/`, carrying cross-chunk deps via
16
+ * the control-file `requires` (and optionally a plan cross-reference).
17
+ *
18
+ * This is the "modular workspace" carve — the same monolith sliced into
19
+ * independently publishable module forks (e.g. a users module, an admin module).
20
+ * The emitted directory is a real, discoverable pgpm workspace: it reuses
21
+ * `writeMinimalWorkspace`/`writeMinimalModule` (the minimal deployable file set
22
+ * pgpm recognizes — `pgpm.json`, `pgpm.plan`, `<name>.control`, `Makefile`,
23
+ * `package.json`), `rebundlePlan` for the chunk model, and `mergeChunkScript`
24
+ * for the per-chunk merge.
25
+ */
26
+ async function rebundleWorkspace(sourceDir, options) {
27
+ const { outputDir, overwrite = false, pretty = true, functionDelimiter = '$EOFCODE$', crossChunkDepMode = 'control-only', initScaffold = false, templateRepo = template_scaffold_1.DEFAULT_TEMPLATE_REPO, templateBranch, license = 'CONSTRUCTIVE', ...strategy } = options;
28
+ if ((0, fs_1.existsSync)(outputDir) && (0, fs_1.readdirSync)(outputDir).length > 0 && !overwrite) {
29
+ throw new Error(`Output directory is not empty: ${outputDir}. Pass overwrite: true to replace.`);
30
+ }
31
+ const plan = (0, rebundle_1.rebundlePlan)(sourceDir, strategy);
32
+ // Remap tags onto their sealing chunk (each tag anchored a chunk boundary).
33
+ const parsed = (0, parser_1.parsePlanFile)((0, path_1.join)(sourceDir, 'pgpm.plan'));
34
+ const sourceTags = parsed.data?.tags ?? [];
35
+ const memberToChunk = new Map();
36
+ for (const chunk of plan.chunks) {
37
+ for (const member of chunk.deploy)
38
+ memberToChunk.set(member, chunk.name);
39
+ }
40
+ const remappedTags = sourceTags.map(tag => ({
41
+ ...tag,
42
+ change: memberToChunk.get(tag.change) ?? tag.change,
43
+ }));
44
+ if (initScaffold) {
45
+ // Lay down the full `pgpm init` workspace shell (pgpm.json, pnpm-workspace,
46
+ // lerna.json, tsconfig, lint/format configs, CI); the merged pgpm files are
47
+ // written on top per module below.
48
+ await (0, template_scaffold_1.scaffoldTemplate)({
49
+ fromPath: 'workspace',
50
+ outputDir,
51
+ templateRepo,
52
+ branch: templateBranch,
53
+ answers: workspaceAnswers(outputDir, license),
54
+ noTty: true,
55
+ });
56
+ }
57
+ else {
58
+ (0, minimal_1.writeMinimalWorkspace)(outputDir, { packages: ['packages/*'] });
59
+ }
60
+ const byDeployOrder = [...plan.chunks].sort((a, b) => plan.deployOrder.indexOf(a.name) - plan.deployOrder.indexOf(b.name));
61
+ const packages = [];
62
+ for (const chunk of byDeployOrder) {
63
+ const pkgRel = (0, path_1.join)('packages', chunk.name);
64
+ const pkgDir = (0, path_1.join)(outputDir, pkgRel);
65
+ if (initScaffold) {
66
+ // Scaffold the module's developer shell (package.json, jest.config.js,
67
+ // README, LICENSE, __tests__); writeMinimalModule overwrites the pgpm
68
+ // files (pgpm.plan, .control, Makefile, deploy/revert/verify) below.
69
+ await (0, template_scaffold_1.scaffoldTemplate)({
70
+ fromPath: 'module',
71
+ outputDir: pkgDir,
72
+ templateRepo,
73
+ branch: templateBranch,
74
+ answers: moduleAnswers(chunk.name, license),
75
+ noTty: true,
76
+ });
77
+ }
78
+ const scripts = {
79
+ [chunk.name]: {
80
+ deploy: await (0, module_1.mergeChunkScript)(sourceDir, chunk, 'deploy', { pretty, functionDelimiter }),
81
+ revert: await (0, module_1.mergeChunkScript)(sourceDir, chunk, 'revert', { pretty, functionDelimiter }),
82
+ verify: await (0, module_1.mergeChunkScript)(sourceDir, chunk, 'verify', { pretty, functionDelimiter }),
83
+ },
84
+ };
85
+ // Control `requires` always carries the module-level dependency; in
86
+ // 'change' mode the plan additionally records the fine-grained cross-ref.
87
+ const deps = chunk.dependencies;
88
+ const planChange = {
89
+ name: chunk.name,
90
+ dependencies: planChangeDeps(deps, crossChunkDepMode),
91
+ };
92
+ (0, minimal_1.writeMinimalModule)(pkgDir, {
93
+ name: chunk.name,
94
+ changes: [planChange],
95
+ scripts,
96
+ tags: remappedTags.filter(t => t.change === chunk.name),
97
+ requires: deps,
98
+ license,
99
+ overwrite: true,
100
+ // When scaffolded, keep the boilerplate's richer package.json.
101
+ writePackageJson: !initScaffold,
102
+ });
103
+ packages.push({ name: chunk.name, dir: pkgRel, dependencies: [...deps] });
104
+ }
105
+ const invariant = { ok: await checkWorkspaceInvariant(sourceDir, outputDir, packages, { pretty, functionDelimiter }) };
106
+ return { ...plan, outputDir, packages, crossChunkDepMode, invariant };
107
+ }
108
+ /**
109
+ * In 'change' mode, express each cross-chunk dep as a plan cross-reference
110
+ * (`<dep>:<dep>`, since each chunk-module has a single change named after it);
111
+ * in 'control-only' mode the plan carries no deps — the control `requires`
112
+ * governs ordering.
113
+ */
114
+ function planChangeDeps(deps, mode) {
115
+ if (mode === 'control-only')
116
+ return [];
117
+ return deps.map(dep => `${dep}:${dep}`);
118
+ }
119
+ /**
120
+ * Non-interactive answers for the `pgpm init` workspace template. Values are
121
+ * deterministic placeholders (the generated pgpm files are the real payload);
122
+ * the workspace name derives from the output directory.
123
+ */
124
+ function workspaceAnswers(outputDir, license) {
125
+ const name = (0, path_1.basename)(outputDir) || 'rebundled-workspace';
126
+ return {
127
+ name,
128
+ fullName: 'Constructive',
129
+ email: 'noreply@constructive.io',
130
+ moduleName: name,
131
+ username: 'constructive-io',
132
+ repoName: name,
133
+ license,
134
+ };
135
+ }
136
+ /** Non-interactive answers for the `pgpm init` module template. */
137
+ function moduleAnswers(name, license) {
138
+ return {
139
+ name,
140
+ moduleName: name,
141
+ moduleDesc: `${name} module`,
142
+ description: `${name} module`,
143
+ repoName: name,
144
+ fullName: 'Constructive',
145
+ email: 'noreply@constructive.io',
146
+ username: 'constructive-io',
147
+ access: 'public',
148
+ license,
149
+ };
150
+ }
151
+ /**
152
+ * Byte-identical gate for a workspace: concatenate every package's deploy
153
+ * script in workspace deploy order, merge it, and compare against the merged
154
+ * original module deploy output. Both go through the same `mergeSqlStatements`
155
+ * pipeline, so equality proves the carve preserved the deployed statements and
156
+ * their order.
157
+ */
158
+ async function checkWorkspaceInvariant(sourceDir, outputDir, packages, opts) {
159
+ const concatenated = packages
160
+ .map(p => (0, fs_1.readFileSync)((0, path_1.join)(outputDir, p.dir, 'deploy', `${p.name}.sql`), 'utf-8'))
161
+ .join('\n');
162
+ const workspaceMerged = await (0, package_1.mergeSqlStatements)(concatenated, {
163
+ stripTransactions: true,
164
+ pretty: opts.pretty,
165
+ functionDelimiter: opts.functionDelimiter,
166
+ });
167
+ const sourceMerged = await (0, package_1.mergeSqlStatements)((0, resolve_1.resolveWithPlan)(sourceDir, 'deploy'), {
168
+ stripTransactions: true,
169
+ pretty: opts.pretty,
170
+ functionDelimiter: opts.functionDelimiter,
171
+ });
172
+ return workspaceMerged.sql === sourceMerged.sql;
173
+ }
@@ -1,3 +1,5 @@
1
+ import { renameInPlanContent } from '@pgpmjs/ast/plan-rename';
2
+ export { renameInPlanContent };
1
3
  /**
2
4
  * Options for {@link renameChanges}.
3
5
  */
@@ -29,14 +31,6 @@ export interface RenameChangesResult {
29
31
  * @throws on any violation.
30
32
  */
31
33
  export declare function validateRenames(moduleDir: string, renames: Map<string, string>): void;
32
- /**
33
- * Rewrite change names in a pgpm.plan's content according to a rename map.
34
- * Textual and format-preserving: only the change-name token at the start of a
35
- * change line, plain (same-package) dependency refs inside `[...]`, and tag
36
- * lines' change field are rewritten; timestamps, planners, comments,
37
- * cross-package refs, and tag refs are untouched.
38
- */
39
- export declare function renameInPlanContent(content: string, renames: Map<string, string>): string;
40
34
  /**
41
35
  * Atomically rename/move changes within a pgpm module: the coupled 4-way
42
36
  * rewrite of (1) deploy/revert/verify script files on disk, (2) the moved
@@ -1,13 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renameInPlanContent = void 0;
3
4
  exports.validateRenames = validateRenames;
4
- exports.renameInPlanContent = renameInPlanContent;
5
5
  exports.renameChanges = renameChanges;
6
6
  exports.deriveRenamesFromSchemaMapping = deriveRenamesFromSchemaMapping;
7
7
  const fs_1 = require("fs");
8
8
  const path_1 = require("path");
9
- const parser_1 = require("../files/plan/parser");
10
- const header_1 = require("../files/sql/header");
9
+ const parser_1 = require("@pgpmjs/ast/files/plan/parser");
10
+ const header_1 = require("@pgpmjs/ast/files/sql/header");
11
+ const plan_rename_1 = require("@pgpmjs/ast/plan-rename");
12
+ Object.defineProperty(exports, "renameInPlanContent", { enumerable: true, get: function () { return plan_rename_1.renameInPlanContent; } });
11
13
  const SCRIPT_DIRS = ['deploy', 'revert', 'verify'];
12
14
  function escapeRe(str) {
13
15
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -65,31 +67,6 @@ function validateRenames(moduleDir, renames) {
65
67
  targets.add(to);
66
68
  }
67
69
  }
68
- /**
69
- * Rewrite change names in a pgpm.plan's content according to a rename map.
70
- * Textual and format-preserving: only the change-name token at the start of a
71
- * change line, plain (same-package) dependency refs inside `[...]`, and tag
72
- * lines' change field are rewritten; timestamps, planners, comments,
73
- * cross-package refs, and tag refs are untouched.
74
- */
75
- function renameInPlanContent(content, renames) {
76
- const froms = Array.from(renames.keys()).sort((a, b) => b.length - a.length);
77
- return content
78
- .split('\n')
79
- .map(line => {
80
- const trimmed = line.trim();
81
- if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('%'))
82
- return line;
83
- let result = line;
84
- for (const from of froms) {
85
- const to = renames.get(from);
86
- const re = new RegExp(`(^|[\\s\\[])${escapeRe(from)}(?=$|[\\s\\]@])`, 'g');
87
- result = result.replace(re, `$1${to}`);
88
- }
89
- return result;
90
- })
91
- .join('\n');
92
- }
93
70
  /**
94
71
  * Atomically rename/move changes within a pgpm module: the coupled 4-way
95
72
  * rewrite of (1) deploy/revert/verify script files on disk, (2) the moved
@@ -163,7 +140,7 @@ function renameChanges(moduleDir, renames, options = {}) {
163
140
  // 4. Rewrite pgpm.plan
164
141
  const planPath = (0, path_1.join)(moduleDir, 'pgpm.plan');
165
142
  const planContent = (0, fs_1.readFileSync)(planPath, 'utf-8');
166
- const newPlanContent = renameInPlanContent(planContent, renames);
143
+ const newPlanContent = (0, plan_rename_1.renameInPlanContent)(planContent, renames);
167
144
  if (newPlanContent !== planContent) {
168
145
  if (dryRun) {
169
146
  result.dryRunReport.push('Would rewrite: pgpm.plan');
@@ -1,4 +1,4 @@
1
- import { ExtendedPlanFile } from '../files/types';
1
+ import { ExtendedPlanFile } from '@pgpmjs/ast/files/types';
2
2
  /**
3
3
  * Represents a dependency graph where keys are module identifiers
4
4
  * and values are arrays of their direct dependencies
@@ -5,8 +5,8 @@ const fs_1 = require("fs");
5
5
  const glob_1 = require("glob");
6
6
  const path_1 = require("path");
7
7
  const pgpm_1 = require("../core/class/pgpm");
8
- const parser_1 = require("../files/plan/parser");
9
- const header_1 = require("../files/sql/header");
8
+ const parser_1 = require("@pgpmjs/ast/files/plan/parser");
9
+ const header_1 = require("@pgpmjs/ast/files/sql/header");
10
10
  const types_1 = require("@pgpmjs/types");
11
11
  /**
12
12
  * Core dependency resolution algorithm that handles circular dependency detection
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveTagToChangeName = exports.resolveWithPlan = exports.resolve = void 0;
4
4
  const fs_1 = require("fs");
5
- const files_1 = require("../files");
6
- const parser_1 = require("../files/plan/parser");
5
+ const files_1 = require("@pgpmjs/ast/files");
6
+ const parser_1 = require("@pgpmjs/ast/files/plan/parser");
7
7
  const deps_1 = require("./deps");
8
8
  const types_1 = require("@pgpmjs/types");
9
9
  /**
package/slice/slice.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Change, Tag, ExtendedPlanFile } from '../files/types';
1
+ import { Change, Tag, ExtendedPlanFile } from '@pgpmjs/ast/files/types';
2
2
  import { SliceConfig, SliceResult, DependencyGraph, PackageOutput, GroupingStrategy, PatternStrategy, CrossPackageDepMode } from './types';
3
3
  /**
4
4
  * Build a dependency graph from a parsed plan file
package/slice/slice.js CHANGED
@@ -14,7 +14,7 @@ exports.generatePlanContent = generatePlanContent;
14
14
  exports.generateControlContent = generateControlContent;
15
15
  exports.generateSinglePackage = generateSinglePackage;
16
16
  exports.slicePlan = slicePlan;
17
- const parser_1 = require("../files/plan/parser");
17
+ const parser_1 = require("@pgpmjs/ast/files/plan/parser");
18
18
  const minimatch_1 = require("minimatch");
19
19
  /**
20
20
  * Build a dependency graph from a parsed plan file
package/slice/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Change, Tag, ExtendedPlanFile } from '../files/types';
1
+ import { Change, Tag, ExtendedPlanFile } from '@pgpmjs/ast/files/types';
2
2
  /**
3
3
  * Configuration for slicing a plan into multiple packages
4
4
  */
@@ -0,0 +1,64 @@
1
+ import { Change, Tag } from '@pgpmjs/ast/files/types';
2
+ declare const SCRIPT_DIRS: readonly ["deploy", "revert", "verify"];
3
+ export type MinimalScriptDir = (typeof SCRIPT_DIRS)[number];
4
+ /**
5
+ * A single change's SQL scripts for a minimal module. `verify` is optional.
6
+ */
7
+ export interface MinimalChangeScripts {
8
+ deploy: string;
9
+ revert: string;
10
+ verify?: string;
11
+ }
12
+ export interface WriteMinimalModuleOptions {
13
+ /** Module (extension) name */
14
+ name: string;
15
+ /** Plan changes, in order. Each must have a matching entry in `scripts`. */
16
+ changes: Change[];
17
+ /** SQL scripts keyed by change name */
18
+ scripts: Record<string, MinimalChangeScripts>;
19
+ /** Tags to include in the plan */
20
+ tags?: Tag[];
21
+ /** Extensions/modules this module requires (control `requires`) */
22
+ requires?: string[];
23
+ /** package.json version (default '0.0.1') */
24
+ version?: string;
25
+ /** package.json license (default 'CONSTRUCTIVE') */
26
+ license?: string;
27
+ /** Overwrite an existing directory (default: false) */
28
+ overwrite?: boolean;
29
+ /**
30
+ * Write a minimal `package.json` (default: true). Set `false` to preserve an
31
+ * existing one — e.g. when a richer package.json was already scaffolded by
32
+ * `pgpm init` and only the pgpm files should be written on top.
33
+ */
34
+ writePackageJson?: boolean;
35
+ }
36
+ /**
37
+ * The minimal set of files pgpm needs to recognize and deploy a module:
38
+ * `pgpm.plan` (module marker + change order), `<name>.control` (extension
39
+ * metadata), `Makefile` (PGXS packaging), `package.json`, and the
40
+ * deploy/revert/verify scripts. Mirrors what `pgpm init` scaffolds, minus the
41
+ * network-fetched boilerplate (README/LICENSE/jest/tests/lint configs).
42
+ */
43
+ export declare function writeMinimalModule(moduleDir: string, options: WriteMinimalModuleOptions): void;
44
+ export interface WriteMinimalWorkspaceOptions {
45
+ /** Workspace package globs (default: ['packages/*']) */
46
+ packages?: string[];
47
+ /** Overwrite an existing pgpm.json (default: true) */
48
+ overwrite?: boolean;
49
+ }
50
+ /**
51
+ * Write the minimal workspace marker (`pgpm.json`) so pgpm discovers the
52
+ * directory as a workspace. This is what `getWorkspacePath` walks up to find.
53
+ */
54
+ export declare function writeMinimalWorkspace(workspaceDir: string, options?: WriteMinimalWorkspaceOptions): void;
55
+ /**
56
+ * Full extension control file, matching the shape `pgpm init` emits (comment,
57
+ * default_version, module_pathname, requires, relocatable, superuser).
58
+ */
59
+ export declare function generateModuleControl(name: string, requires?: string[], version?: string): string;
60
+ /**
61
+ * PGXS Makefile for `pgpm package`/`make install`.
62
+ */
63
+ export declare function generateModuleMakefile(name: string, version?: string): string;
64
+ export {};
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.writeMinimalModule = writeMinimalModule;
7
+ exports.writeMinimalWorkspace = writeMinimalWorkspace;
8
+ exports.generateModuleControl = generateModuleControl;
9
+ exports.generateModuleMakefile = generateModuleMakefile;
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const slice_1 = require("../slice/slice");
13
+ const SCRIPT_DIRS = ['deploy', 'revert', 'verify'];
14
+ /**
15
+ * The minimal set of files pgpm needs to recognize and deploy a module:
16
+ * `pgpm.plan` (module marker + change order), `<name>.control` (extension
17
+ * metadata), `Makefile` (PGXS packaging), `package.json`, and the
18
+ * deploy/revert/verify scripts. Mirrors what `pgpm init` scaffolds, minus the
19
+ * network-fetched boilerplate (README/LICENSE/jest/tests/lint configs).
20
+ */
21
+ function writeMinimalModule(moduleDir, options) {
22
+ const { name, changes, scripts, tags = [], requires = [], version = '0.0.1', license = 'CONSTRUCTIVE', overwrite = false, writePackageJson = true, } = options;
23
+ if (fs_1.default.existsSync(moduleDir) && fs_1.default.readdirSync(moduleDir).length > 0 && !overwrite) {
24
+ throw new Error(`Module directory is not empty: ${moduleDir}. Pass overwrite: true to replace.`);
25
+ }
26
+ for (const dir of SCRIPT_DIRS) {
27
+ fs_1.default.mkdirSync(path_1.default.join(moduleDir, dir), { recursive: true });
28
+ }
29
+ for (const change of changes) {
30
+ const changeScripts = scripts[change.name];
31
+ if (!changeScripts) {
32
+ throw new Error(`Missing scripts for change "${change.name}" in module "${name}"`);
33
+ }
34
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'deploy', `${change.name}.sql`), changeScripts.deploy);
35
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'revert', `${change.name}.sql`), changeScripts.revert);
36
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'verify', `${change.name}.sql`), changeScripts.verify ?? `-- Verify ${change.name}\n\nBEGIN;\n\nSELECT 1;\n\nROLLBACK;\n`);
37
+ }
38
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'pgpm.plan'), (0, slice_1.generatePlanContent)(name, changes, tags));
39
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, `${name}.control`), generateModuleControl(name, requires, version));
40
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'Makefile'), generateModuleMakefile(name, version));
41
+ if (writePackageJson) {
42
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'package.json'), JSON.stringify({ name, version, description: `${name} module`, license }, null, 2) + '\n');
43
+ }
44
+ }
45
+ /**
46
+ * Write the minimal workspace marker (`pgpm.json`) so pgpm discovers the
47
+ * directory as a workspace. This is what `getWorkspacePath` walks up to find.
48
+ */
49
+ function writeMinimalWorkspace(workspaceDir, options = {}) {
50
+ const { packages = ['packages/*'], overwrite = true } = options;
51
+ const configPath = path_1.default.join(workspaceDir, 'pgpm.json');
52
+ if (fs_1.default.existsSync(configPath) && !overwrite)
53
+ return;
54
+ fs_1.default.mkdirSync(workspaceDir, { recursive: true });
55
+ fs_1.default.writeFileSync(configPath, JSON.stringify({ packages }, null, 2) + '\n');
56
+ }
57
+ /**
58
+ * Full extension control file, matching the shape `pgpm init` emits (comment,
59
+ * default_version, module_pathname, requires, relocatable, superuser).
60
+ */
61
+ function generateModuleControl(name, requires = [], version = '0.0.1') {
62
+ let content = `# ${name} extension\n`;
63
+ content += `comment = '${name} extension'\n`;
64
+ content += `default_version = '${version}'\n`;
65
+ content += `module_pathname = '$libdir/${name}'\n`;
66
+ if (requires.length > 0) {
67
+ content += `requires = '${requires.join(',')}'\n`;
68
+ }
69
+ content += `relocatable = false\n`;
70
+ content += `superuser = false\n`;
71
+ return content;
72
+ }
73
+ /**
74
+ * PGXS Makefile for `pgpm package`/`make install`.
75
+ */
76
+ function generateModuleMakefile(name, version = '0.0.1') {
77
+ return (`EXTENSION = ${name}\n` +
78
+ `DATA = sql/${name}--${version}.sql\n\n` +
79
+ `PG_CONFIG = pg_config\n` +
80
+ `PGXS := $(shell $(PG_CONFIG) --pgxs)\n` +
81
+ `include $(PGXS)\n`);
82
+ }
@@ -1,3 +0,0 @@
1
- // Re-export all extension functionality
2
- export * from './reader';
3
- export * from './writer';