@pgpmjs/core 6.33.0 → 6.34.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/esm/index.js +1 -0
- package/esm/refactor/index.js +1 -0
- package/esm/refactor/rename.js +200 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +2 -2
- package/refactor/index.d.ts +1 -0
- package/refactor/index.js +17 -0
- package/refactor/rename.d.ts +59 -0
- package/refactor/rename.js +206 -0
package/esm/index.js
CHANGED
|
@@ -13,6 +13,7 @@ export * from './core/boilerplate-types';
|
|
|
13
13
|
export * from './core/boilerplate-scanner';
|
|
14
14
|
// Export package-files functionality (now integrated into core)
|
|
15
15
|
export * from './files';
|
|
16
|
+
export * from './refactor';
|
|
16
17
|
export { cleanSql } from './migrate/clean';
|
|
17
18
|
export { PgpmMigrate } from './migrate/client';
|
|
18
19
|
export { PgpmInit } from './init/client';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './rename';
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmdirSync, statSync, writeFileSync } from 'fs';
|
|
2
|
+
import { dirname, join, relative, sep } from 'path';
|
|
3
|
+
import { parsePlanFileSimple } from '../files/plan/parser';
|
|
4
|
+
import { parsePgpmHeader, renameInHeader, writePgpmScript } from '../files/sql/header';
|
|
5
|
+
const SCRIPT_DIRS = ['deploy', 'revert', 'verify'];
|
|
6
|
+
function escapeRe(str) {
|
|
7
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
8
|
+
}
|
|
9
|
+
function findSqlFiles(dir) {
|
|
10
|
+
const files = [];
|
|
11
|
+
if (!existsSync(dir))
|
|
12
|
+
return files;
|
|
13
|
+
const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const fullPath = join(dir, entry.name);
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
files.push(...findSqlFiles(fullPath));
|
|
18
|
+
}
|
|
19
|
+
else if (entry.isFile() && entry.name.endsWith('.sql')) {
|
|
20
|
+
files.push(fullPath);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
function removeEmptyDirs(dir, stopAt) {
|
|
26
|
+
let current = dir;
|
|
27
|
+
while (current !== stopAt && current.startsWith(stopAt)) {
|
|
28
|
+
if (!existsSync(current) || readdirSync(current).length > 0)
|
|
29
|
+
return;
|
|
30
|
+
rmdirSync(current);
|
|
31
|
+
current = dirname(current);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate a rename map against a module directory: every source change must
|
|
36
|
+
* exist in the plan, no target may collide with an existing change, and no
|
|
37
|
+
* two sources may map to the same target.
|
|
38
|
+
*
|
|
39
|
+
* @throws on any violation.
|
|
40
|
+
*/
|
|
41
|
+
export function validateRenames(moduleDir, renames) {
|
|
42
|
+
const planPath = join(moduleDir, 'pgpm.plan');
|
|
43
|
+
const plan = parsePlanFileSimple(planPath);
|
|
44
|
+
const changeNames = new Set(plan.changes.map(c => c.name));
|
|
45
|
+
const targets = new Set();
|
|
46
|
+
for (const [from, to] of renames) {
|
|
47
|
+
if (from === to) {
|
|
48
|
+
throw new Error(`Rename maps change to itself: ${from}`);
|
|
49
|
+
}
|
|
50
|
+
if (!changeNames.has(from)) {
|
|
51
|
+
throw new Error(`Cannot rename unknown change: ${from}`);
|
|
52
|
+
}
|
|
53
|
+
if (changeNames.has(to) && !renames.has(to)) {
|
|
54
|
+
throw new Error(`Rename target already exists in plan: ${to}`);
|
|
55
|
+
}
|
|
56
|
+
if (targets.has(to)) {
|
|
57
|
+
throw new Error(`Multiple changes rename to the same target: ${to}`);
|
|
58
|
+
}
|
|
59
|
+
targets.add(to);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Rewrite change names in a pgpm.plan's content according to a rename map.
|
|
64
|
+
* Textual and format-preserving: only the change-name token at the start of a
|
|
65
|
+
* change line, plain (same-package) dependency refs inside `[...]`, and tag
|
|
66
|
+
* lines' change field are rewritten; timestamps, planners, comments,
|
|
67
|
+
* cross-package refs, and tag refs are untouched.
|
|
68
|
+
*/
|
|
69
|
+
export function renameInPlanContent(content, renames) {
|
|
70
|
+
const froms = Array.from(renames.keys()).sort((a, b) => b.length - a.length);
|
|
71
|
+
return content
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map(line => {
|
|
74
|
+
const trimmed = line.trim();
|
|
75
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('%'))
|
|
76
|
+
return line;
|
|
77
|
+
let result = line;
|
|
78
|
+
for (const from of froms) {
|
|
79
|
+
const to = renames.get(from);
|
|
80
|
+
const re = new RegExp(`(^|[\\s\\[])${escapeRe(from)}(?=$|[\\s\\]@])`, 'g');
|
|
81
|
+
result = result.replace(re, `$1${to}`);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
})
|
|
85
|
+
.join('\n');
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Atomically rename/move changes within a pgpm module: the coupled 4-way
|
|
89
|
+
* rewrite of (1) deploy/revert/verify script files on disk, (2) the moved
|
|
90
|
+
* scripts' own header identity, (3) `-- requires:` headers in every script
|
|
91
|
+
* that references a renamed change, and (4) the pgpm.plan.
|
|
92
|
+
*
|
|
93
|
+
* SQL statement bodies are NOT touched: schema-name rewrites inside SQL are a
|
|
94
|
+
* separate AST-level concern for callers to compose (e.g. transform passes).
|
|
95
|
+
*/
|
|
96
|
+
export function renameChanges(moduleDir, renames, options = {}) {
|
|
97
|
+
const dryRun = options.dryRun ?? false;
|
|
98
|
+
const result = {
|
|
99
|
+
filesMoved: [],
|
|
100
|
+
headersRewritten: [],
|
|
101
|
+
planRewritten: false
|
|
102
|
+
};
|
|
103
|
+
if (dryRun)
|
|
104
|
+
result.dryRunReport = [];
|
|
105
|
+
if (renames.size === 0)
|
|
106
|
+
return result;
|
|
107
|
+
validateRenames(moduleDir, renames);
|
|
108
|
+
// 1. Move script files
|
|
109
|
+
for (const scriptDir of SCRIPT_DIRS) {
|
|
110
|
+
for (const [from, to] of renames) {
|
|
111
|
+
const oldPath = join(moduleDir, scriptDir, `${from}.sql`);
|
|
112
|
+
if (!existsSync(oldPath) || !statSync(oldPath).isFile())
|
|
113
|
+
continue;
|
|
114
|
+
const newPath = join(moduleDir, scriptDir, `${to}.sql`);
|
|
115
|
+
if (existsSync(newPath)) {
|
|
116
|
+
throw new Error(`Rename target file already exists: ${relative(moduleDir, newPath)}`);
|
|
117
|
+
}
|
|
118
|
+
if (dryRun) {
|
|
119
|
+
result.dryRunReport.push(`Would move: ${relative(moduleDir, oldPath)} -> ${relative(moduleDir, newPath)}`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
mkdirSync(dirname(newPath), { recursive: true });
|
|
123
|
+
renameSync(oldPath, newPath);
|
|
124
|
+
removeEmptyDirs(dirname(oldPath), join(moduleDir, scriptDir));
|
|
125
|
+
}
|
|
126
|
+
result.filesMoved.push({
|
|
127
|
+
from: [scriptDir, `${from}.sql`].join(sep),
|
|
128
|
+
to: [scriptDir, `${to}.sql`].join(sep)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// 2 + 3. Rewrite headers in every script (identity of moved files,
|
|
133
|
+
// requires of dependents)
|
|
134
|
+
for (const scriptDir of SCRIPT_DIRS) {
|
|
135
|
+
const dir = join(moduleDir, scriptDir);
|
|
136
|
+
for (const file of findSqlFiles(dir)) {
|
|
137
|
+
const content = readFileSync(file, 'utf-8');
|
|
138
|
+
const script = parsePgpmHeader(content);
|
|
139
|
+
const count = renameInHeader(script, renames);
|
|
140
|
+
if (count === 0)
|
|
141
|
+
continue;
|
|
142
|
+
const rewritten = writePgpmScript(script);
|
|
143
|
+
if (rewritten === content)
|
|
144
|
+
continue;
|
|
145
|
+
if (dryRun) {
|
|
146
|
+
result.dryRunReport.push(`Would rewrite header: ${relative(moduleDir, file)}`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
writeFileSync(file, rewritten);
|
|
150
|
+
}
|
|
151
|
+
result.headersRewritten.push(relative(moduleDir, file));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// In dry-run mode files were not moved, so dependents' headers are found at
|
|
155
|
+
// their original paths above; the moved files' own headers are still
|
|
156
|
+
// reported through the same scan.
|
|
157
|
+
// 4. Rewrite pgpm.plan
|
|
158
|
+
const planPath = join(moduleDir, 'pgpm.plan');
|
|
159
|
+
const planContent = readFileSync(planPath, 'utf-8');
|
|
160
|
+
const newPlanContent = renameInPlanContent(planContent, renames);
|
|
161
|
+
if (newPlanContent !== planContent) {
|
|
162
|
+
if (dryRun) {
|
|
163
|
+
result.dryRunReport.push('Would rewrite: pgpm.plan');
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
writeFileSync(planPath, newPlanContent);
|
|
167
|
+
}
|
|
168
|
+
result.planRewritten = true;
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Derive a change rename map from a schema mapping (old schema name -> new
|
|
174
|
+
* schema name) by rewriting the `schemas/<name>/` path segment of every plan
|
|
175
|
+
* change that lives under a renamed schema.
|
|
176
|
+
*
|
|
177
|
+
* This is the module-level counterpart of directory-based schema renames:
|
|
178
|
+
* `schemas/my-app/tables/users` with mapping `my-app -> my_app` yields
|
|
179
|
+
* `schemas/my_app/tables/users`.
|
|
180
|
+
*/
|
|
181
|
+
export function deriveRenamesFromSchemaMapping(moduleDir, schemaMapping) {
|
|
182
|
+
const renames = new Map();
|
|
183
|
+
const planPath = join(moduleDir, 'pgpm.plan');
|
|
184
|
+
const plan = parsePlanFileSimple(planPath);
|
|
185
|
+
const schemas = Array.from(schemaMapping.keys()).sort((a, b) => b.length - a.length);
|
|
186
|
+
for (const change of plan.changes) {
|
|
187
|
+
let renamed = change.name;
|
|
188
|
+
for (const schema of schemas) {
|
|
189
|
+
const to = schemaMapping.get(schema);
|
|
190
|
+
if (!to || to === schema)
|
|
191
|
+
continue;
|
|
192
|
+
const re = new RegExp(`(^|/)(schemas/)${escapeRe(schema)}(/|$)`, 'g');
|
|
193
|
+
renamed = renamed.replace(re, `$1$2${to}$3`);
|
|
194
|
+
}
|
|
195
|
+
if (renamed !== change.name) {
|
|
196
|
+
renames.set(change.name, renamed);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return renames;
|
|
200
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './core/template-scaffold';
|
|
|
12
12
|
export * from './core/boilerplate-types';
|
|
13
13
|
export * from './core/boilerplate-scanner';
|
|
14
14
|
export * from './files';
|
|
15
|
+
export * from './refactor';
|
|
15
16
|
export { cleanSql } from './migrate/clean';
|
|
16
17
|
export { PgpmMigrate } from './migrate/client';
|
|
17
18
|
export { PgpmInit } from './init/client';
|
package/index.js
CHANGED
|
@@ -30,6 +30,7 @@ __exportStar(require("./core/boilerplate-types"), exports);
|
|
|
30
30
|
__exportStar(require("./core/boilerplate-scanner"), exports);
|
|
31
31
|
// Export package-files functionality (now integrated into core)
|
|
32
32
|
__exportStar(require("./files"), exports);
|
|
33
|
+
__exportStar(require("./refactor"), exports);
|
|
33
34
|
var clean_1 = require("./migrate/clean");
|
|
34
35
|
Object.defineProperty(exports, "cleanSql", { enumerable: true, get: function () { return clean_1.cleanSql; } });
|
|
35
36
|
var client_1 = require("./migrate/client");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgpmjs/core",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.34.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "PGPM Package and Migration Tools",
|
|
6
6
|
"main": "index.js",
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"pgsql-parser": "^17.9.17",
|
|
65
65
|
"yanse": "^0.2.1"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "08b12923f099191b2d1592b9c897f9f04b4950aa"
|
|
68
68
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './rename';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./rename"), exports);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for {@link renameChanges}.
|
|
3
|
+
*/
|
|
4
|
+
export interface RenameChangesOptions {
|
|
5
|
+
/** Report planned operations without touching the filesystem. Default: false */
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Result of a {@link renameChanges} run.
|
|
10
|
+
*/
|
|
11
|
+
export interface RenameChangesResult {
|
|
12
|
+
/** deploy/revert/verify script files moved (old path -> new path, module-relative). */
|
|
13
|
+
filesMoved: Array<{
|
|
14
|
+
from: string;
|
|
15
|
+
to: string;
|
|
16
|
+
}>;
|
|
17
|
+
/** Script files whose headers were rewritten (identity and/or requires). */
|
|
18
|
+
headersRewritten: string[];
|
|
19
|
+
/** Whether pgpm.plan was rewritten. */
|
|
20
|
+
planRewritten: boolean;
|
|
21
|
+
/** Dry-run report lines (only populated when dryRun). */
|
|
22
|
+
dryRunReport?: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validate a rename map against a module directory: every source change must
|
|
26
|
+
* exist in the plan, no target may collide with an existing change, and no
|
|
27
|
+
* two sources may map to the same target.
|
|
28
|
+
*
|
|
29
|
+
* @throws on any violation.
|
|
30
|
+
*/
|
|
31
|
+
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
|
+
/**
|
|
41
|
+
* Atomically rename/move changes within a pgpm module: the coupled 4-way
|
|
42
|
+
* rewrite of (1) deploy/revert/verify script files on disk, (2) the moved
|
|
43
|
+
* scripts' own header identity, (3) `-- requires:` headers in every script
|
|
44
|
+
* that references a renamed change, and (4) the pgpm.plan.
|
|
45
|
+
*
|
|
46
|
+
* SQL statement bodies are NOT touched: schema-name rewrites inside SQL are a
|
|
47
|
+
* separate AST-level concern for callers to compose (e.g. transform passes).
|
|
48
|
+
*/
|
|
49
|
+
export declare function renameChanges(moduleDir: string, renames: Map<string, string>, options?: RenameChangesOptions): RenameChangesResult;
|
|
50
|
+
/**
|
|
51
|
+
* Derive a change rename map from a schema mapping (old schema name -> new
|
|
52
|
+
* schema name) by rewriting the `schemas/<name>/` path segment of every plan
|
|
53
|
+
* change that lives under a renamed schema.
|
|
54
|
+
*
|
|
55
|
+
* This is the module-level counterpart of directory-based schema renames:
|
|
56
|
+
* `schemas/my-app/tables/users` with mapping `my-app -> my_app` yields
|
|
57
|
+
* `schemas/my_app/tables/users`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function deriveRenamesFromSchemaMapping(moduleDir: string, schemaMapping: Map<string, string>): Map<string, string>;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateRenames = validateRenames;
|
|
4
|
+
exports.renameInPlanContent = renameInPlanContent;
|
|
5
|
+
exports.renameChanges = renameChanges;
|
|
6
|
+
exports.deriveRenamesFromSchemaMapping = deriveRenamesFromSchemaMapping;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const parser_1 = require("../files/plan/parser");
|
|
10
|
+
const header_1 = require("../files/sql/header");
|
|
11
|
+
const SCRIPT_DIRS = ['deploy', 'revert', 'verify'];
|
|
12
|
+
function escapeRe(str) {
|
|
13
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
14
|
+
}
|
|
15
|
+
function findSqlFiles(dir) {
|
|
16
|
+
const files = [];
|
|
17
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
18
|
+
return files;
|
|
19
|
+
const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
files.push(...findSqlFiles(fullPath));
|
|
24
|
+
}
|
|
25
|
+
else if (entry.isFile() && entry.name.endsWith('.sql')) {
|
|
26
|
+
files.push(fullPath);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return files;
|
|
30
|
+
}
|
|
31
|
+
function removeEmptyDirs(dir, stopAt) {
|
|
32
|
+
let current = dir;
|
|
33
|
+
while (current !== stopAt && current.startsWith(stopAt)) {
|
|
34
|
+
if (!(0, fs_1.existsSync)(current) || (0, fs_1.readdirSync)(current).length > 0)
|
|
35
|
+
return;
|
|
36
|
+
(0, fs_1.rmdirSync)(current);
|
|
37
|
+
current = (0, path_1.dirname)(current);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Validate a rename map against a module directory: every source change must
|
|
42
|
+
* exist in the plan, no target may collide with an existing change, and no
|
|
43
|
+
* two sources may map to the same target.
|
|
44
|
+
*
|
|
45
|
+
* @throws on any violation.
|
|
46
|
+
*/
|
|
47
|
+
function validateRenames(moduleDir, renames) {
|
|
48
|
+
const planPath = (0, path_1.join)(moduleDir, 'pgpm.plan');
|
|
49
|
+
const plan = (0, parser_1.parsePlanFileSimple)(planPath);
|
|
50
|
+
const changeNames = new Set(plan.changes.map(c => c.name));
|
|
51
|
+
const targets = new Set();
|
|
52
|
+
for (const [from, to] of renames) {
|
|
53
|
+
if (from === to) {
|
|
54
|
+
throw new Error(`Rename maps change to itself: ${from}`);
|
|
55
|
+
}
|
|
56
|
+
if (!changeNames.has(from)) {
|
|
57
|
+
throw new Error(`Cannot rename unknown change: ${from}`);
|
|
58
|
+
}
|
|
59
|
+
if (changeNames.has(to) && !renames.has(to)) {
|
|
60
|
+
throw new Error(`Rename target already exists in plan: ${to}`);
|
|
61
|
+
}
|
|
62
|
+
if (targets.has(to)) {
|
|
63
|
+
throw new Error(`Multiple changes rename to the same target: ${to}`);
|
|
64
|
+
}
|
|
65
|
+
targets.add(to);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
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
|
+
/**
|
|
94
|
+
* Atomically rename/move changes within a pgpm module: the coupled 4-way
|
|
95
|
+
* rewrite of (1) deploy/revert/verify script files on disk, (2) the moved
|
|
96
|
+
* scripts' own header identity, (3) `-- requires:` headers in every script
|
|
97
|
+
* that references a renamed change, and (4) the pgpm.plan.
|
|
98
|
+
*
|
|
99
|
+
* SQL statement bodies are NOT touched: schema-name rewrites inside SQL are a
|
|
100
|
+
* separate AST-level concern for callers to compose (e.g. transform passes).
|
|
101
|
+
*/
|
|
102
|
+
function renameChanges(moduleDir, renames, options = {}) {
|
|
103
|
+
const dryRun = options.dryRun ?? false;
|
|
104
|
+
const result = {
|
|
105
|
+
filesMoved: [],
|
|
106
|
+
headersRewritten: [],
|
|
107
|
+
planRewritten: false
|
|
108
|
+
};
|
|
109
|
+
if (dryRun)
|
|
110
|
+
result.dryRunReport = [];
|
|
111
|
+
if (renames.size === 0)
|
|
112
|
+
return result;
|
|
113
|
+
validateRenames(moduleDir, renames);
|
|
114
|
+
// 1. Move script files
|
|
115
|
+
for (const scriptDir of SCRIPT_DIRS) {
|
|
116
|
+
for (const [from, to] of renames) {
|
|
117
|
+
const oldPath = (0, path_1.join)(moduleDir, scriptDir, `${from}.sql`);
|
|
118
|
+
if (!(0, fs_1.existsSync)(oldPath) || !(0, fs_1.statSync)(oldPath).isFile())
|
|
119
|
+
continue;
|
|
120
|
+
const newPath = (0, path_1.join)(moduleDir, scriptDir, `${to}.sql`);
|
|
121
|
+
if ((0, fs_1.existsSync)(newPath)) {
|
|
122
|
+
throw new Error(`Rename target file already exists: ${(0, path_1.relative)(moduleDir, newPath)}`);
|
|
123
|
+
}
|
|
124
|
+
if (dryRun) {
|
|
125
|
+
result.dryRunReport.push(`Would move: ${(0, path_1.relative)(moduleDir, oldPath)} -> ${(0, path_1.relative)(moduleDir, newPath)}`);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(newPath), { recursive: true });
|
|
129
|
+
(0, fs_1.renameSync)(oldPath, newPath);
|
|
130
|
+
removeEmptyDirs((0, path_1.dirname)(oldPath), (0, path_1.join)(moduleDir, scriptDir));
|
|
131
|
+
}
|
|
132
|
+
result.filesMoved.push({
|
|
133
|
+
from: [scriptDir, `${from}.sql`].join(path_1.sep),
|
|
134
|
+
to: [scriptDir, `${to}.sql`].join(path_1.sep)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// 2 + 3. Rewrite headers in every script (identity of moved files,
|
|
139
|
+
// requires of dependents)
|
|
140
|
+
for (const scriptDir of SCRIPT_DIRS) {
|
|
141
|
+
const dir = (0, path_1.join)(moduleDir, scriptDir);
|
|
142
|
+
for (const file of findSqlFiles(dir)) {
|
|
143
|
+
const content = (0, fs_1.readFileSync)(file, 'utf-8');
|
|
144
|
+
const script = (0, header_1.parsePgpmHeader)(content);
|
|
145
|
+
const count = (0, header_1.renameInHeader)(script, renames);
|
|
146
|
+
if (count === 0)
|
|
147
|
+
continue;
|
|
148
|
+
const rewritten = (0, header_1.writePgpmScript)(script);
|
|
149
|
+
if (rewritten === content)
|
|
150
|
+
continue;
|
|
151
|
+
if (dryRun) {
|
|
152
|
+
result.dryRunReport.push(`Would rewrite header: ${(0, path_1.relative)(moduleDir, file)}`);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
(0, fs_1.writeFileSync)(file, rewritten);
|
|
156
|
+
}
|
|
157
|
+
result.headersRewritten.push((0, path_1.relative)(moduleDir, file));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// In dry-run mode files were not moved, so dependents' headers are found at
|
|
161
|
+
// their original paths above; the moved files' own headers are still
|
|
162
|
+
// reported through the same scan.
|
|
163
|
+
// 4. Rewrite pgpm.plan
|
|
164
|
+
const planPath = (0, path_1.join)(moduleDir, 'pgpm.plan');
|
|
165
|
+
const planContent = (0, fs_1.readFileSync)(planPath, 'utf-8');
|
|
166
|
+
const newPlanContent = renameInPlanContent(planContent, renames);
|
|
167
|
+
if (newPlanContent !== planContent) {
|
|
168
|
+
if (dryRun) {
|
|
169
|
+
result.dryRunReport.push('Would rewrite: pgpm.plan');
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
(0, fs_1.writeFileSync)(planPath, newPlanContent);
|
|
173
|
+
}
|
|
174
|
+
result.planRewritten = true;
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Derive a change rename map from a schema mapping (old schema name -> new
|
|
180
|
+
* schema name) by rewriting the `schemas/<name>/` path segment of every plan
|
|
181
|
+
* change that lives under a renamed schema.
|
|
182
|
+
*
|
|
183
|
+
* This is the module-level counterpart of directory-based schema renames:
|
|
184
|
+
* `schemas/my-app/tables/users` with mapping `my-app -> my_app` yields
|
|
185
|
+
* `schemas/my_app/tables/users`.
|
|
186
|
+
*/
|
|
187
|
+
function deriveRenamesFromSchemaMapping(moduleDir, schemaMapping) {
|
|
188
|
+
const renames = new Map();
|
|
189
|
+
const planPath = (0, path_1.join)(moduleDir, 'pgpm.plan');
|
|
190
|
+
const plan = (0, parser_1.parsePlanFileSimple)(planPath);
|
|
191
|
+
const schemas = Array.from(schemaMapping.keys()).sort((a, b) => b.length - a.length);
|
|
192
|
+
for (const change of plan.changes) {
|
|
193
|
+
let renamed = change.name;
|
|
194
|
+
for (const schema of schemas) {
|
|
195
|
+
const to = schemaMapping.get(schema);
|
|
196
|
+
if (!to || to === schema)
|
|
197
|
+
continue;
|
|
198
|
+
const re = new RegExp(`(^|/)(schemas/)${escapeRe(schema)}(/|$)`, 'g');
|
|
199
|
+
renamed = renamed.replace(re, `$1$2${to}$3`);
|
|
200
|
+
}
|
|
201
|
+
if (renamed !== change.name) {
|
|
202
|
+
renames.set(change.name, renamed);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return renames;
|
|
206
|
+
}
|