@pgpmjs/core 6.32.2 → 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/files/sql/header.js +259 -0
- package/esm/files/sql/index.js +1 -0
- package/esm/index.js +1 -0
- package/esm/refactor/index.js +1 -0
- package/esm/refactor/rename.js +200 -0
- package/esm/resolution/deps.js +26 -63
- package/esm/slice/slice.js +11 -3
- package/files/sql/header.d.ts +114 -0
- package/files/sql/header.js +266 -0
- package/files/sql/index.d.ts +1 -0
- package/files/sql/index.js +1 -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/resolution/deps.js +26 -63
- package/slice/slice.d.ts +2 -2
- package/slice/slice.js +11 -3
- package/slice/types.d.ts +15 -0
|
@@ -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
|
+
}
|
package/resolution/deps.js
CHANGED
|
@@ -6,6 +6,7 @@ const glob_1 = require("glob");
|
|
|
6
6
|
const path_1 = require("path");
|
|
7
7
|
const pgpm_1 = require("../core/class/pgpm");
|
|
8
8
|
const parser_1 = require("../files/plan/parser");
|
|
9
|
+
const header_1 = require("../files/sql/header");
|
|
9
10
|
const types_1 = require("@pgpmjs/types");
|
|
10
11
|
/**
|
|
11
12
|
* Core dependency resolution algorithm that handles circular dependency detection
|
|
@@ -337,84 +338,46 @@ const resolveDependencies = (packageDir, extname, options = {}) => {
|
|
|
337
338
|
const files = (0, glob_1.sync)(`${packageDir}/deploy/**/*.sql`).sort((a, b) => a.localeCompare(b));
|
|
338
339
|
for (const file of files) {
|
|
339
340
|
const data = (0, fs_1.readFileSync)(file, 'utf-8');
|
|
340
|
-
const lines = data.split('\n');
|
|
341
341
|
const key = '/' + (0, path_1.relative)(packageDir, file);
|
|
342
342
|
deps[key] = [];
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
if (
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
// Full resolution: replace tag with actual change
|
|
362
|
-
const resolvedDep = `${projectName}:${taggedChange}`;
|
|
363
|
-
deps[key].push(resolvedDep);
|
|
364
|
-
}
|
|
365
|
-
else if (tagResolution === 'internal') {
|
|
366
|
-
// Internal resolution: keep tag in deps but track mapping
|
|
367
|
-
tagMappings[dep] = `${projectName}:${taggedChange}`;
|
|
368
|
-
deps[key].push(dep);
|
|
369
|
-
}
|
|
343
|
+
const { requires } = (0, header_1.scanDeployScript)(data, { key, extname, makeKey });
|
|
344
|
+
for (const dep of requires) {
|
|
345
|
+
// For 'preserve' mode, just add the dependency as-is (like original getDeps)
|
|
346
|
+
if (tagResolution === 'preserve') {
|
|
347
|
+
deps[key].push(dep);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
// For other modes, handle tag resolution
|
|
351
|
+
if (dep.includes('@')) {
|
|
352
|
+
const match = dep.match(/^([^:]+):@(.+)$/);
|
|
353
|
+
if (match) {
|
|
354
|
+
const [, projectName, tagName] = match;
|
|
355
|
+
const taggedChange = resolveTagToChange(projectName, tagName);
|
|
356
|
+
if (taggedChange) {
|
|
357
|
+
if (tagResolution === 'resolve') {
|
|
358
|
+
// Full resolution: replace tag with actual change
|
|
359
|
+
const resolvedDep = `${projectName}:${taggedChange}`;
|
|
360
|
+
deps[key].push(resolvedDep);
|
|
370
361
|
}
|
|
371
|
-
else {
|
|
372
|
-
//
|
|
362
|
+
else if (tagResolution === 'internal') {
|
|
363
|
+
// Internal resolution: keep tag in deps but track mapping
|
|
364
|
+
tagMappings[dep] = `${projectName}:${taggedChange}`;
|
|
373
365
|
deps[key].push(dep);
|
|
374
366
|
}
|
|
375
367
|
}
|
|
376
368
|
else {
|
|
377
|
-
//
|
|
369
|
+
// Could not resolve tag, keep it as is
|
|
378
370
|
deps[key].push(dep);
|
|
379
371
|
}
|
|
380
372
|
}
|
|
381
373
|
else {
|
|
382
|
-
//
|
|
374
|
+
// Invalid tag format, keep as is
|
|
383
375
|
deps[key].push(dep);
|
|
384
376
|
}
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
// Handle deploy statements - exactly as in original
|
|
388
|
-
let m2;
|
|
389
|
-
let keyToTest;
|
|
390
|
-
if (/:/.test(line)) {
|
|
391
|
-
m2 = line.match(/^-- Deploy ([^:]*):([\w\/]+)(?:\s+to\s+pg)?/);
|
|
392
|
-
if (m2) {
|
|
393
|
-
const actualProject = m2[1];
|
|
394
|
-
keyToTest = m2[2];
|
|
395
|
-
if (extname !== actualProject) {
|
|
396
|
-
throw new Error(`Mismatched project name in deploy file:
|
|
397
|
-
Expected project: ${extname}
|
|
398
|
-
Found in line : ${actualProject}
|
|
399
|
-
Line : ${line}`);
|
|
400
|
-
}
|
|
401
|
-
const expectedKey = makeKey(keyToTest);
|
|
402
|
-
if (key !== expectedKey) {
|
|
403
|
-
throw new Error(`Deployment script path or internal name mismatch:
|
|
404
|
-
Expected key : ${key}
|
|
405
|
-
Found in line : ${expectedKey}
|
|
406
|
-
Line : ${line}`);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
377
|
}
|
|
410
378
|
else {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
keyToTest = m2[1].trim();
|
|
414
|
-
if (key !== makeKey(keyToTest)) {
|
|
415
|
-
throw new Error('deployment script in wrong place or is named wrong internally\n' + line);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
379
|
+
// Not a tag, keep as is
|
|
380
|
+
deps[key].push(dep);
|
|
418
381
|
}
|
|
419
382
|
}
|
|
420
383
|
}
|
package/slice/slice.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Change, Tag, ExtendedPlanFile } from '../files/types';
|
|
2
|
-
import { SliceConfig, SliceResult, DependencyGraph, PackageOutput, GroupingStrategy, PatternStrategy } from './types';
|
|
2
|
+
import { SliceConfig, SliceResult, DependencyGraph, PackageOutput, GroupingStrategy, PatternStrategy, CrossPackageDepMode } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Build a dependency graph from a parsed plan file
|
|
5
5
|
*/
|
|
@@ -52,7 +52,7 @@ export declare function generateControlContent(pkgName: string, deps: Set<string
|
|
|
52
52
|
/**
|
|
53
53
|
* Generate a single package output
|
|
54
54
|
*/
|
|
55
|
-
export declare function generateSinglePackage(pkgName: string, changes: Set<string>, graph: DependencyGraph, changeToPackage: Map<string, string>, pkgDeps: Set<string>,
|
|
55
|
+
export declare function generateSinglePackage(pkgName: string, changes: Set<string>, graph: DependencyGraph, changeToPackage: Map<string, string>, pkgDeps: Set<string>, crossPackageDeps?: boolean | CrossPackageDepMode): PackageOutput;
|
|
56
56
|
/**
|
|
57
57
|
* Main slicing function
|
|
58
58
|
*/
|
package/slice/slice.js
CHANGED
|
@@ -356,7 +356,9 @@ function generateControlContent(pkgName, deps) {
|
|
|
356
356
|
/**
|
|
357
357
|
* Generate a single package output
|
|
358
358
|
*/
|
|
359
|
-
function generateSinglePackage(pkgName, changes, graph, changeToPackage, pkgDeps,
|
|
359
|
+
function generateSinglePackage(pkgName, changes, graph, changeToPackage, pkgDeps, crossPackageDeps = 'change') {
|
|
360
|
+
// Back-compat: earlier signature took useTagsForCrossPackageDeps: boolean
|
|
361
|
+
const crossPackageDepMode = crossPackageDeps === true ? 'tag' : crossPackageDeps === false ? 'change' : crossPackageDeps;
|
|
360
362
|
// Sort changes in topological order within package
|
|
361
363
|
const sortedChanges = topologicalSortWithinPackage(changes, graph);
|
|
362
364
|
// Build plan entries with updated dependencies
|
|
@@ -377,7 +379,13 @@ function generateSinglePackage(pkgName, changes, graph, changeToPackage, pkgDeps
|
|
|
377
379
|
}
|
|
378
380
|
else {
|
|
379
381
|
// Cross-package dependency
|
|
380
|
-
if (
|
|
382
|
+
if (crossPackageDepMode === 'control-only') {
|
|
383
|
+
// Dropped from the plan line; carried by the control file's
|
|
384
|
+
// `requires` (extension install ordering deploys the whole
|
|
385
|
+
// dependency package first)
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (crossPackageDepMode === 'tag') {
|
|
381
389
|
// Find latest tag in the dependency package
|
|
382
390
|
const depPkgChanges = [...graph.nodes.keys()]
|
|
383
391
|
.filter(c => changeToPackage.get(c) === depPkg);
|
|
@@ -477,7 +485,7 @@ function slicePlan(config) {
|
|
|
477
485
|
const changes = assignments.get(pkgName);
|
|
478
486
|
if (!changes || changes.size === 0)
|
|
479
487
|
continue;
|
|
480
|
-
const pkgOutput = generateSinglePackage(pkgName, changes, graph, changeToPackage, packageDeps.get(pkgName) || new Set(), config.useTagsForCrossPackageDeps
|
|
488
|
+
const pkgOutput = generateSinglePackage(pkgName, changes, graph, changeToPackage, packageDeps.get(pkgName) || new Set(), config.crossPackageDepMode ?? (config.useTagsForCrossPackageDeps ? 'tag' : 'change'));
|
|
481
489
|
packages.push(pkgOutput);
|
|
482
490
|
// Check for heavy cross-package dependencies
|
|
483
491
|
let crossDeps = 0;
|
package/slice/types.d.ts
CHANGED
|
@@ -13,11 +13,26 @@ export interface SliceConfig {
|
|
|
13
13
|
defaultPackage?: string;
|
|
14
14
|
/** Whether to use tags for cross-package deps */
|
|
15
15
|
useTagsForCrossPackageDeps?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* How cross-package dependencies are recorded in sliced plans:
|
|
18
|
+
* - 'change' (default): per-change refs (`{package}:{change}`)
|
|
19
|
+
* - 'tag': per-change tag refs (`{package}:@tag`) when available
|
|
20
|
+
* (equivalent to `useTagsForCrossPackageDeps: true`)
|
|
21
|
+
* - 'control-only': drop per-change cross-package refs from plan lines
|
|
22
|
+
* entirely; the dependency is carried only by the control file's
|
|
23
|
+
* `requires` (extension install ordering deploys the whole dependency
|
|
24
|
+
* package first, which subsumes any per-change ordering constraint)
|
|
25
|
+
*/
|
|
26
|
+
crossPackageDepMode?: CrossPackageDepMode;
|
|
16
27
|
/** Minimum changes per package (merge smaller groups) */
|
|
17
28
|
minChangesPerPackage?: number;
|
|
18
29
|
/** Author for generated plan files */
|
|
19
30
|
author?: string;
|
|
20
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* How cross-package dependencies are represented in sliced plan files
|
|
34
|
+
*/
|
|
35
|
+
export type CrossPackageDepMode = 'change' | 'tag' | 'control-only';
|
|
21
36
|
/**
|
|
22
37
|
* Grouping strategy for slicing
|
|
23
38
|
*/
|