@pgpmjs/transform 0.18.0 → 0.20.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/bundle-driver.d.ts +1 -1
- package/catalog-check.d.ts +47 -0
- package/catalog-check.js +130 -0
- package/constraint-names.d.ts +36 -0
- package/constraint-names.js +75 -0
- package/esm/catalog-check.js +124 -0
- package/esm/constraint-names.js +71 -0
- package/esm/fixture-closure.js +1 -1
- package/esm/granularity-driver.js +84 -21
- package/esm/index.js +13 -6
- package/esm/module-emit.js +135 -0
- package/esm/module-source.js +54 -0
- package/esm/partition.js +89 -0
- package/esm/restructure.js +67 -0
- package/esm/semantic-diff-driver.js +124 -19
- package/esm/sub-object.js +80 -0
- package/fixture-closure.d.ts +1 -1
- package/fixture-closure.js +1 -1
- package/granularity-driver.d.ts +57 -0
- package/granularity-driver.js +86 -21
- package/index.d.ts +23 -10
- package/index.js +43 -16
- package/module-emit.d.ts +65 -0
- package/module-emit.js +175 -0
- package/module-source.d.ts +33 -0
- package/module-source.js +92 -0
- package/package.json +8 -7
- package/partition.d.ts +32 -0
- package/partition.js +127 -0
- package/restructure.d.ts +45 -0
- package/restructure.js +72 -0
- package/semantic-diff-driver.d.ts +3 -1
- package/semantic-diff-driver.js +124 -19
- package/sub-object.d.ts +31 -0
- package/sub-object.js +84 -0
package/fixture-closure.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CategoryProfile, ChangeCategory } from './categorize';
|
|
2
1
|
import { QualifiedName } from '@pgsql/transform';
|
|
2
|
+
import { CategoryProfile, ChangeCategory } from './categorize';
|
|
3
3
|
/**
|
|
4
4
|
* A change plus its deploy SQL. Optional plan `dependencies` (from `pgpm.plan`)
|
|
5
5
|
* are followed as authoritative prerequisites in addition to AST-derived edges.
|
package/fixture-closure.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveFixtureClosure = resolveFixtureClosure;
|
|
4
|
-
const categorize_1 = require("./categorize");
|
|
5
4
|
const transform_1 = require("@pgsql/transform");
|
|
5
|
+
const categorize_1 = require("./categorize");
|
|
6
6
|
function objectKey(schema, name) {
|
|
7
7
|
return `${schema ?? ''}\u0000${name}`;
|
|
8
8
|
}
|
package/granularity-driver.d.ts
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Granularity driver: restructure a pgpm module's deploy surface between the
|
|
3
|
+
* atomic, object, and consolidated shapes.
|
|
4
|
+
*
|
|
5
|
+
* The upstream pass (`restructureSql` in `@pgsql/transform`) rewrites one SQL
|
|
6
|
+
* script between equivalent shapes, guarded by the statement dependency
|
|
7
|
+
* graph. This driver lifts that to the pgpm change model: it flattens a
|
|
8
|
+
* module's deploy scripts in plan order into one program, restructures it to
|
|
9
|
+
* the target granularity, then re-slices the result into changes — one change
|
|
10
|
+
* per created object — with change dependencies recomputed from the statement
|
|
11
|
+
* graph. Like the other drivers in this package it is structurally typed on
|
|
12
|
+
* the bundle seams: no dependency on `@pgpmjs/bundle` or `@pgpmjs/core`.
|
|
13
|
+
*
|
|
14
|
+
* - `atomic` — the machine-emitted shape: bare CREATE TABLE plus one
|
|
15
|
+
* ALTER per column/constraint.
|
|
16
|
+
* - `object` — each table fully baked; cross-object statements
|
|
17
|
+
* (FKs, indexes, triggers, policies) stay separate.
|
|
18
|
+
* - `consolidated` — additionally inlines FKs proven safe by the graph.
|
|
19
|
+
*/
|
|
20
|
+
import type { ObjectIdentity as NamingIdentity } from '@pgpmjs/naming-spec';
|
|
1
21
|
import type { Granularity, StatementFacts } from '@pgsql/transform';
|
|
2
22
|
export type { Granularity } from '@pgsql/transform';
|
|
23
|
+
/**
|
|
24
|
+
* How statements are distributed across pgpm changes — orthogonal to
|
|
25
|
+
* `Granularity`, which shapes the SQL *within* a change.
|
|
26
|
+
*
|
|
27
|
+
* - `alteration` — one change per alteration: every `ADD COLUMN` /
|
|
28
|
+
* `ADD CONSTRAINT` becomes its own plan entry with its own
|
|
29
|
+
* deploy/revert/verify and graph-derived requires, so a
|
|
30
|
+
* single column can deploy or revert independently.
|
|
31
|
+
* - `object` — one change per created object (the default): a table's
|
|
32
|
+
* CREATE and all its ALTERs share one plan entry.
|
|
33
|
+
* - `single` — one change for the whole module: every statement lands in
|
|
34
|
+
* a single plan entry with one deploy/revert/verify.
|
|
35
|
+
*/
|
|
36
|
+
export type ChangeGranularity = 'alteration' | 'object' | 'single';
|
|
37
|
+
export declare const CHANGE_GRANULARITIES: readonly ChangeGranularity[];
|
|
38
|
+
export declare const isChangeGranularity: (value: string) => value is ChangeGranularity;
|
|
3
39
|
/** A change's deploy surface going into or out of the restructure. */
|
|
4
40
|
export interface GranularityChange {
|
|
5
41
|
/** Change name (plan token, e.g. `schemas/app/tables/users/table`). */
|
|
@@ -26,6 +62,27 @@ export interface RestructuredChange extends GranularityChange {
|
|
|
26
62
|
}
|
|
27
63
|
export interface RestructureModuleOptions {
|
|
28
64
|
granularity: Granularity;
|
|
65
|
+
/**
|
|
66
|
+
* Change-level distribution (default `object`). With `alteration`, every
|
|
67
|
+
* single-command `ALTER TABLE ADD COLUMN` / `ADD CONSTRAINT` in the
|
|
68
|
+
* restructured script becomes its own change, named by the sub-object's
|
|
69
|
+
* naming-spec path (`.../columns/{name}/column`,
|
|
70
|
+
* `.../constraints/{name}/constraint`); unnamed constraints are first
|
|
71
|
+
* given their Postgres default name so each change stays revertible.
|
|
72
|
+
* With `single`, the whole restructured script becomes one change.
|
|
73
|
+
*/
|
|
74
|
+
changeGranularity?: ChangeGranularity;
|
|
75
|
+
/**
|
|
76
|
+
* Plan token for the single change (used only with `changeGranularity:
|
|
77
|
+
* 'single'`). Defaults to `module/init`.
|
|
78
|
+
*/
|
|
79
|
+
singleChangeName?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Derive a change name for an alteration group from its sub-object
|
|
82
|
+
* identity (used only with `changeGranularity: 'alteration'`). Defaults to
|
|
83
|
+
* `pathFor` in `directory` style.
|
|
84
|
+
*/
|
|
85
|
+
subObjectName?: (identity: NamingIdentity) => string;
|
|
29
86
|
/**
|
|
30
87
|
* Derive a change name for a statement group from the facts of its primary
|
|
31
88
|
* (creating) statement. Defaults to {@link defaultChangeName}: naming spec
|
package/granularity-driver.js
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isChangeGranularity = exports.CHANGE_GRANULARITIES = void 0;
|
|
3
4
|
exports.defaultChangeName = defaultChangeName;
|
|
4
5
|
exports.restructureChanges = restructureChanges;
|
|
5
|
-
/**
|
|
6
|
-
* Granularity driver: restructure a pgpm module's deploy surface between the
|
|
7
|
-
* atomic, object, and consolidated shapes.
|
|
8
|
-
*
|
|
9
|
-
* The upstream pass (`restructureSql` in `@pgsql/transform`) rewrites one SQL
|
|
10
|
-
* script between equivalent shapes, guarded by the statement dependency
|
|
11
|
-
* graph. This driver lifts that to the pgpm change model: it flattens a
|
|
12
|
-
* module's deploy scripts in plan order into one program, restructures it to
|
|
13
|
-
* the target granularity, then re-slices the result into changes — one change
|
|
14
|
-
* per created object — with change dependencies recomputed from the statement
|
|
15
|
-
* graph. Like the other drivers in this package it is structurally typed on
|
|
16
|
-
* the bundle seams: no dependency on `@pgpmjs/bundle` or `@pgpmjs/core`.
|
|
17
|
-
*
|
|
18
|
-
* - `atomic` — the machine-emitted shape: bare CREATE TABLE plus one
|
|
19
|
-
* ALTER per column/constraint.
|
|
20
|
-
* - `object` — each table fully baked; cross-object statements
|
|
21
|
-
* (FKs, indexes, triggers, policies) stay separate.
|
|
22
|
-
* - `consolidated` — additionally inlines FKs proven safe by the graph.
|
|
23
|
-
*/
|
|
24
6
|
const naming_spec_1 = require("@pgpmjs/naming-spec");
|
|
25
7
|
const scripts_1 = require("@pgsql/scripts");
|
|
26
8
|
const transform_1 = require("@pgsql/transform");
|
|
9
|
+
const sub_object_1 = require("./sub-object");
|
|
10
|
+
exports.CHANGE_GRANULARITIES = ['alteration', 'object', 'single'];
|
|
11
|
+
const isChangeGranularity = (value) => exports.CHANGE_GRANULARITIES.includes(value);
|
|
12
|
+
exports.isChangeGranularity = isChangeGranularity;
|
|
27
13
|
const KIND_DIRS = {
|
|
28
14
|
table: 'tables',
|
|
29
15
|
view: 'views',
|
|
@@ -57,22 +43,50 @@ function defaultChangeName(facts) {
|
|
|
57
43
|
*/
|
|
58
44
|
function restructureChanges(changes, options) {
|
|
59
45
|
const nameFor = options.changeName ?? defaultChangeName;
|
|
46
|
+
const subNameFor = options.subObjectName ?? ((identity) => (0, naming_spec_1.pathFor)(identity));
|
|
47
|
+
const alteration = options.changeGranularity === 'alteration';
|
|
60
48
|
const flattened = changes
|
|
61
49
|
.map(c => c.deploy.trim())
|
|
62
50
|
.filter(Boolean)
|
|
63
51
|
.join('\n\n');
|
|
64
|
-
const
|
|
52
|
+
const restructured = (0, transform_1.restructureSql)(flattened, {
|
|
65
53
|
granularity: options.granularity
|
|
66
54
|
});
|
|
55
|
+
const warnings = restructured.warnings;
|
|
56
|
+
const sql = alteration ? (0, sub_object_1.nameUnnamedConstraints)(restructured.sql) : restructured.sql;
|
|
67
57
|
// Re-classify the emitted script; group statements by the object they
|
|
68
58
|
// target (creates[0]), so a table's CREATE and its remaining ALTERs land
|
|
69
59
|
// in the same change regardless of statement kind.
|
|
70
60
|
const facts = (0, transform_1.classifyStatements)(sql);
|
|
61
|
+
if (options.changeGranularity === 'single') {
|
|
62
|
+
const name = options.singleChangeName ?? 'module/init';
|
|
63
|
+
const inputNames = new Set(changes.map(c => c.name));
|
|
64
|
+
const dependencies = [...new Set(changes.flatMap(c => c.dependencies).filter(d => !inputNames.has(d)))].sort();
|
|
65
|
+
const statements = facts
|
|
66
|
+
.map(f => sql.slice(f.span.start, f.span.start + f.span.len).trim())
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
.map(text => (text.endsWith(';') ? text : `${text};`));
|
|
69
|
+
const revert = (0, scripts_1.revertFor)(facts);
|
|
70
|
+
const verify = (0, scripts_1.verifyFor)(facts);
|
|
71
|
+
for (const warning of [...revert.warnings, ...verify.warnings]) {
|
|
72
|
+
warnings.push(`${name}: ${warning}`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
changes: statements.length > 0
|
|
76
|
+
? [{ name, dependencies, deploy: statements.join('\n\n'), revert: revert.sql, verify: verify.sql }]
|
|
77
|
+
: [],
|
|
78
|
+
warnings
|
|
79
|
+
};
|
|
80
|
+
}
|
|
71
81
|
const graph = (0, transform_1.buildStatementGraph)(facts);
|
|
72
82
|
const groupOf = new Array(facts.length).fill(-1);
|
|
73
83
|
const groupKeys = [];
|
|
74
84
|
const groupFacts = [];
|
|
75
85
|
const groupKeyToIndex = new Map();
|
|
86
|
+
/** Sub-object path for alteration groups; null means name via `nameFor`. */
|
|
87
|
+
const groupSubName = [];
|
|
88
|
+
/** For alteration groups: the object-group key of the owning table. */
|
|
89
|
+
const groupOwnerKey = [];
|
|
76
90
|
facts.forEach((f, i) => {
|
|
77
91
|
const created = f.creates[0];
|
|
78
92
|
if (!created) {
|
|
@@ -82,12 +96,31 @@ function restructureChanges(changes, options) {
|
|
|
82
96
|
groupOf[i] = groupOf[i - 1];
|
|
83
97
|
return;
|
|
84
98
|
}
|
|
99
|
+
if (alteration) {
|
|
100
|
+
const sub = (0, sub_object_1.subObjectIdentityOf)(f);
|
|
101
|
+
if (sub) {
|
|
102
|
+
const key = `sub\u0000${sub.kind}\u0000${sub.schema ?? ''}\u0000${sub.table}\u0000${sub.name}`;
|
|
103
|
+
let g = groupKeyToIndex.get(key);
|
|
104
|
+
if (g === undefined) {
|
|
105
|
+
g = groupKeys.length;
|
|
106
|
+
groupKeys.push(key);
|
|
107
|
+
groupFacts.push(f);
|
|
108
|
+
groupSubName.push(subNameFor(sub));
|
|
109
|
+
groupOwnerKey.push(`${sub.schema ?? ''}.${sub.table}`);
|
|
110
|
+
groupKeyToIndex.set(key, g);
|
|
111
|
+
}
|
|
112
|
+
groupOf[i] = g;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
85
116
|
const key = `${created.schema ?? ''}.${created.name}`;
|
|
86
117
|
let g = groupKeyToIndex.get(key);
|
|
87
118
|
if (g === undefined) {
|
|
88
119
|
g = groupKeys.length;
|
|
89
120
|
groupKeys.push(key);
|
|
90
121
|
groupFacts.push(f);
|
|
122
|
+
groupSubName.push(null);
|
|
123
|
+
groupOwnerKey.push(null);
|
|
91
124
|
groupKeyToIndex.set(key, g);
|
|
92
125
|
}
|
|
93
126
|
else if (!(groupFacts[g].kind in KIND_DIRS) && groupFacts[g].kind !== 'schema' && (f.kind in KIND_DIRS || f.kind === 'schema')) {
|
|
@@ -96,7 +129,7 @@ function restructureChanges(changes, options) {
|
|
|
96
129
|
}
|
|
97
130
|
groupOf[i] = g;
|
|
98
131
|
});
|
|
99
|
-
const groupNames = groupFacts.map(nameFor);
|
|
132
|
+
const groupNames = groupFacts.map((f, g) => groupSubName[g] ?? nameFor(f));
|
|
100
133
|
// Schema producers, for schema-level change dependencies.
|
|
101
134
|
const schemaGroup = new Map();
|
|
102
135
|
facts.forEach((f, i) => {
|
|
@@ -142,6 +175,38 @@ function restructureChanges(changes, options) {
|
|
|
142
175
|
groupDeps[from].add(to);
|
|
143
176
|
}
|
|
144
177
|
});
|
|
178
|
+
// Alteration groups depend on their table's own change, and constraint
|
|
179
|
+
// groups additionally on the column changes they key on — the statement
|
|
180
|
+
// graph keys everything by the table, so these finer edges are added here.
|
|
181
|
+
if (alteration) {
|
|
182
|
+
const columnGroup = new Map();
|
|
183
|
+
groupKeys.forEach((key, g) => {
|
|
184
|
+
const parts = key.split('\u0000');
|
|
185
|
+
if (parts[0] === 'sub' && parts[1] === 'column') {
|
|
186
|
+
columnGroup.set(`${parts[2]}.${parts[3]}.${parts[4]}`, g);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
groupKeys.forEach((key, g) => {
|
|
190
|
+
const owner = groupOwnerKey[g];
|
|
191
|
+
if (owner === null)
|
|
192
|
+
return;
|
|
193
|
+
const tableGroup = groupKeyToIndex.get(owner);
|
|
194
|
+
if (tableGroup !== undefined && tableGroup !== g)
|
|
195
|
+
groupDeps[g].add(tableGroup);
|
|
196
|
+
const parts = key.split('\u0000');
|
|
197
|
+
if (parts[1] !== 'constraint')
|
|
198
|
+
return;
|
|
199
|
+
const constraint = groupFacts[g].stmt?.AlterTableStmt?.cmds?.[0]?.AlterTableCmd?.def?.Constraint;
|
|
200
|
+
const referenced = [...(constraint?.keys ?? []), ...(constraint?.fk_attrs ?? [])]
|
|
201
|
+
.map(k => k.String?.sval)
|
|
202
|
+
.filter((s) => typeof s === 'string');
|
|
203
|
+
for (const col of referenced) {
|
|
204
|
+
const to = columnGroup.get(`${owner}.${col}`);
|
|
205
|
+
if (to !== undefined && to !== g)
|
|
206
|
+
groupDeps[g].add(to);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
145
210
|
// Emit groups in first-statement order (already topological).
|
|
146
211
|
const order = [...groupNames.keys()].sort((a, b) => {
|
|
147
212
|
const firstA = groupOf.indexOf(a);
|
package/index.d.ts
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
|
-
export * from '@pgsql/transform';
|
|
2
|
-
export { loadModule } from 'plpgsql-parser';
|
|
3
1
|
export type { BundleScriptContext, ExtensionRoutingInput, NamespaceValidatorOptions, SchemaObjectRoute, SchemaTranspiler, SchemaTranspilerOptions, } from './bundle-driver';
|
|
4
2
|
export { buildExtensionRouter, buildSchemaRouter, makeNamespaceValidator, makeSchemaTranspiler, } from './bundle-driver';
|
|
5
|
-
export type {
|
|
6
|
-
export {
|
|
7
|
-
export type { DiffInputChange, ObjectDelta, SemanticDeltaChange, SemanticDiffOptions, SemanticDiffResult, SemanticObjectDiff, } from './semantic-diff-driver';
|
|
8
|
-
export { diffChangeSets, diffSchemas, } from './semantic-diff-driver';
|
|
9
|
-
export type { PartitionConfig, PartitionedChange, PartitionedPackage, PartitionInputChange, PartitionRule, PartitionUnit, PartitionUnitsResult, UnitSelector, } from './partition-driver';
|
|
10
|
-
export { PartitionCycleError, partitionUnits, RESIDUAL_UNIT_PATH, } from './partition-driver';
|
|
3
|
+
export type { CatalogQueryable, CatalogSnapshot } from './catalog-check';
|
|
4
|
+
export { diffCatalogSnapshots, snapshotCatalog, withoutColumnOrder } from './catalog-check';
|
|
11
5
|
export type { CategoryProfile, ChangeCategory, } from './categorize';
|
|
12
6
|
export { buildCategoryOf, categorizeChange, TIER_PROFILE, } from './categorize';
|
|
7
|
+
export type { ConstraintNode } from './constraint-names';
|
|
8
|
+
export { defaultConstraintName, firstColumnRef } from './constraint-names';
|
|
13
9
|
export type { ClosureChange, ClosureInputChange, ClosureReason, FixtureClosure, ResolveFixtureClosureOptions, } from './fixture-closure';
|
|
14
10
|
export { resolveFixtureClosure } from './fixture-closure';
|
|
15
|
-
export type {
|
|
16
|
-
export {
|
|
11
|
+
export type { ChangeGranularity, GranularityChange, RestructuredChange, RestructureModuleOptions, RestructureModuleResult, } from './granularity-driver';
|
|
12
|
+
export { CHANGE_GRANULARITIES, defaultChangeName, isChangeGranularity, restructureChanges, } from './granularity-driver';
|
|
13
|
+
export type { AppendModuleResult, PgpmModuleModel } from './module-emit';
|
|
14
|
+
export { appendModule, checkOverwrite, writeControlFile, writeModule } from './module-emit';
|
|
15
|
+
export type { ModuleSource, ModuleSourceChange } from './module-source';
|
|
16
|
+
export { loadModuleSource, stripTransactionWrapper } from './module-source';
|
|
17
|
+
export type { PartitionedPackageRows, PartitionExportRowsResult } from './partition';
|
|
18
|
+
export { parsePartitionConfig, partitionExportRows } from './partition';
|
|
19
|
+
export type { PartitionConfig, PartitionedChange, PartitionedPackage, PartitionInputChange, PartitionRule, PartitionUnit, PartitionUnitsResult, UnitSelector, } from './partition-driver';
|
|
20
|
+
export { PartitionCycleError, partitionUnits, RESIDUAL_UNIT_PATH, } from './partition-driver';
|
|
17
21
|
export type { SqlProgram, SqlStatementAst, SqlStatementSpan, } from './program';
|
|
18
22
|
export { emitSqlProgram, parseSqlProgram } from './program';
|
|
23
|
+
export type { GeneratedScript, RegeneratedScripts, } from './regen';
|
|
24
|
+
export { classifyScript, isStubScript, regenerateScripts, } from './regen';
|
|
25
|
+
export type { ExportGranularity, RestructureExportRowsOptions, RestructureExportRowsResult } from './restructure';
|
|
26
|
+
export { EXPORT_GRANULARITIES, isExportGranularity, restructureExportRows } from './restructure';
|
|
27
|
+
export type { DiffInputChange, ObjectDelta, SemanticDeltaChange, SemanticDiffOptions, SemanticDiffResult, SemanticObjectDiff, } from './semantic-diff-driver';
|
|
28
|
+
export { diffChangeSets, diffSchemas, } from './semantic-diff-driver';
|
|
29
|
+
export { nameUnnamedConstraints, subObjectIdentityOf } from './sub-object';
|
|
30
|
+
export * from '@pgsql/transform';
|
|
31
|
+
export { loadModule } from 'plpgsql-parser';
|
package/index.js
CHANGED
|
@@ -14,35 +14,62 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
18
|
-
__exportStar(require("@pgsql/transform"), exports);
|
|
19
|
-
var plpgsql_parser_1 = require("plpgsql-parser");
|
|
20
|
-
Object.defineProperty(exports, "loadModule", { enumerable: true, get: function () { return plpgsql_parser_1.loadModule; } });
|
|
17
|
+
exports.loadModule = exports.subObjectIdentityOf = exports.nameUnnamedConstraints = exports.diffSchemas = exports.diffChangeSets = exports.restructureExportRows = exports.isExportGranularity = exports.EXPORT_GRANULARITIES = exports.regenerateScripts = exports.isStubScript = exports.classifyScript = exports.parseSqlProgram = exports.emitSqlProgram = exports.RESIDUAL_UNIT_PATH = exports.partitionUnits = exports.PartitionCycleError = exports.partitionExportRows = exports.parsePartitionConfig = exports.stripTransactionWrapper = exports.loadModuleSource = exports.writeModule = exports.writeControlFile = exports.checkOverwrite = exports.appendModule = exports.restructureChanges = exports.isChangeGranularity = exports.defaultChangeName = exports.CHANGE_GRANULARITIES = exports.resolveFixtureClosure = exports.firstColumnRef = exports.defaultConstraintName = exports.TIER_PROFILE = exports.categorizeChange = exports.buildCategoryOf = exports.withoutColumnOrder = exports.snapshotCatalog = exports.diffCatalogSnapshots = exports.makeSchemaTranspiler = exports.makeNamespaceValidator = exports.buildSchemaRouter = exports.buildExtensionRouter = void 0;
|
|
21
18
|
var bundle_driver_1 = require("./bundle-driver");
|
|
22
19
|
Object.defineProperty(exports, "buildExtensionRouter", { enumerable: true, get: function () { return bundle_driver_1.buildExtensionRouter; } });
|
|
23
20
|
Object.defineProperty(exports, "buildSchemaRouter", { enumerable: true, get: function () { return bundle_driver_1.buildSchemaRouter; } });
|
|
24
21
|
Object.defineProperty(exports, "makeNamespaceValidator", { enumerable: true, get: function () { return bundle_driver_1.makeNamespaceValidator; } });
|
|
25
22
|
Object.defineProperty(exports, "makeSchemaTranspiler", { enumerable: true, get: function () { return bundle_driver_1.makeSchemaTranspiler; } });
|
|
23
|
+
var catalog_check_1 = require("./catalog-check");
|
|
24
|
+
Object.defineProperty(exports, "diffCatalogSnapshots", { enumerable: true, get: function () { return catalog_check_1.diffCatalogSnapshots; } });
|
|
25
|
+
Object.defineProperty(exports, "snapshotCatalog", { enumerable: true, get: function () { return catalog_check_1.snapshotCatalog; } });
|
|
26
|
+
Object.defineProperty(exports, "withoutColumnOrder", { enumerable: true, get: function () { return catalog_check_1.withoutColumnOrder; } });
|
|
27
|
+
var categorize_1 = require("./categorize");
|
|
28
|
+
Object.defineProperty(exports, "buildCategoryOf", { enumerable: true, get: function () { return categorize_1.buildCategoryOf; } });
|
|
29
|
+
Object.defineProperty(exports, "categorizeChange", { enumerable: true, get: function () { return categorize_1.categorizeChange; } });
|
|
30
|
+
Object.defineProperty(exports, "TIER_PROFILE", { enumerable: true, get: function () { return categorize_1.TIER_PROFILE; } });
|
|
31
|
+
var constraint_names_1 = require("./constraint-names");
|
|
32
|
+
Object.defineProperty(exports, "defaultConstraintName", { enumerable: true, get: function () { return constraint_names_1.defaultConstraintName; } });
|
|
33
|
+
Object.defineProperty(exports, "firstColumnRef", { enumerable: true, get: function () { return constraint_names_1.firstColumnRef; } });
|
|
34
|
+
var fixture_closure_1 = require("./fixture-closure");
|
|
35
|
+
Object.defineProperty(exports, "resolveFixtureClosure", { enumerable: true, get: function () { return fixture_closure_1.resolveFixtureClosure; } });
|
|
26
36
|
var granularity_driver_1 = require("./granularity-driver");
|
|
37
|
+
Object.defineProperty(exports, "CHANGE_GRANULARITIES", { enumerable: true, get: function () { return granularity_driver_1.CHANGE_GRANULARITIES; } });
|
|
27
38
|
Object.defineProperty(exports, "defaultChangeName", { enumerable: true, get: function () { return granularity_driver_1.defaultChangeName; } });
|
|
39
|
+
Object.defineProperty(exports, "isChangeGranularity", { enumerable: true, get: function () { return granularity_driver_1.isChangeGranularity; } });
|
|
28
40
|
Object.defineProperty(exports, "restructureChanges", { enumerable: true, get: function () { return granularity_driver_1.restructureChanges; } });
|
|
29
|
-
var
|
|
30
|
-
Object.defineProperty(exports, "
|
|
31
|
-
Object.defineProperty(exports, "
|
|
41
|
+
var module_emit_1 = require("./module-emit");
|
|
42
|
+
Object.defineProperty(exports, "appendModule", { enumerable: true, get: function () { return module_emit_1.appendModule; } });
|
|
43
|
+
Object.defineProperty(exports, "checkOverwrite", { enumerable: true, get: function () { return module_emit_1.checkOverwrite; } });
|
|
44
|
+
Object.defineProperty(exports, "writeControlFile", { enumerable: true, get: function () { return module_emit_1.writeControlFile; } });
|
|
45
|
+
Object.defineProperty(exports, "writeModule", { enumerable: true, get: function () { return module_emit_1.writeModule; } });
|
|
46
|
+
var module_source_1 = require("./module-source");
|
|
47
|
+
Object.defineProperty(exports, "loadModuleSource", { enumerable: true, get: function () { return module_source_1.loadModuleSource; } });
|
|
48
|
+
Object.defineProperty(exports, "stripTransactionWrapper", { enumerable: true, get: function () { return module_source_1.stripTransactionWrapper; } });
|
|
49
|
+
var partition_1 = require("./partition");
|
|
50
|
+
Object.defineProperty(exports, "parsePartitionConfig", { enumerable: true, get: function () { return partition_1.parsePartitionConfig; } });
|
|
51
|
+
Object.defineProperty(exports, "partitionExportRows", { enumerable: true, get: function () { return partition_1.partitionExportRows; } });
|
|
32
52
|
var partition_driver_1 = require("./partition-driver");
|
|
33
53
|
Object.defineProperty(exports, "PartitionCycleError", { enumerable: true, get: function () { return partition_driver_1.PartitionCycleError; } });
|
|
34
54
|
Object.defineProperty(exports, "partitionUnits", { enumerable: true, get: function () { return partition_driver_1.partitionUnits; } });
|
|
35
55
|
Object.defineProperty(exports, "RESIDUAL_UNIT_PATH", { enumerable: true, get: function () { return partition_driver_1.RESIDUAL_UNIT_PATH; } });
|
|
36
|
-
var
|
|
37
|
-
Object.defineProperty(exports, "
|
|
38
|
-
Object.defineProperty(exports, "
|
|
39
|
-
Object.defineProperty(exports, "TIER_PROFILE", { enumerable: true, get: function () { return categorize_1.TIER_PROFILE; } });
|
|
40
|
-
var fixture_closure_1 = require("./fixture-closure");
|
|
41
|
-
Object.defineProperty(exports, "resolveFixtureClosure", { enumerable: true, get: function () { return fixture_closure_1.resolveFixtureClosure; } });
|
|
56
|
+
var program_1 = require("./program");
|
|
57
|
+
Object.defineProperty(exports, "emitSqlProgram", { enumerable: true, get: function () { return program_1.emitSqlProgram; } });
|
|
58
|
+
Object.defineProperty(exports, "parseSqlProgram", { enumerable: true, get: function () { return program_1.parseSqlProgram; } });
|
|
42
59
|
var regen_1 = require("./regen");
|
|
43
60
|
Object.defineProperty(exports, "classifyScript", { enumerable: true, get: function () { return regen_1.classifyScript; } });
|
|
44
61
|
Object.defineProperty(exports, "isStubScript", { enumerable: true, get: function () { return regen_1.isStubScript; } });
|
|
45
62
|
Object.defineProperty(exports, "regenerateScripts", { enumerable: true, get: function () { return regen_1.regenerateScripts; } });
|
|
46
|
-
var
|
|
47
|
-
Object.defineProperty(exports, "
|
|
48
|
-
Object.defineProperty(exports, "
|
|
63
|
+
var restructure_1 = require("./restructure");
|
|
64
|
+
Object.defineProperty(exports, "EXPORT_GRANULARITIES", { enumerable: true, get: function () { return restructure_1.EXPORT_GRANULARITIES; } });
|
|
65
|
+
Object.defineProperty(exports, "isExportGranularity", { enumerable: true, get: function () { return restructure_1.isExportGranularity; } });
|
|
66
|
+
Object.defineProperty(exports, "restructureExportRows", { enumerable: true, get: function () { return restructure_1.restructureExportRows; } });
|
|
67
|
+
var semantic_diff_driver_1 = require("./semantic-diff-driver");
|
|
68
|
+
Object.defineProperty(exports, "diffChangeSets", { enumerable: true, get: function () { return semantic_diff_driver_1.diffChangeSets; } });
|
|
69
|
+
Object.defineProperty(exports, "diffSchemas", { enumerable: true, get: function () { return semantic_diff_driver_1.diffSchemas; } });
|
|
70
|
+
var sub_object_1 = require("./sub-object");
|
|
71
|
+
Object.defineProperty(exports, "nameUnnamedConstraints", { enumerable: true, get: function () { return sub_object_1.nameUnnamedConstraints; } });
|
|
72
|
+
Object.defineProperty(exports, "subObjectIdentityOf", { enumerable: true, get: function () { return sub_object_1.subObjectIdentityOf; } });
|
|
73
|
+
__exportStar(require("@pgsql/transform"), exports);
|
|
74
|
+
var plpgsql_parser_1 = require("plpgsql-parser");
|
|
75
|
+
Object.defineProperty(exports, "loadModule", { enumerable: true, get: function () { return plpgsql_parser_1.loadModule; } });
|
package/module-emit.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module emission — the single seam every dials command renders through.
|
|
3
|
+
*
|
|
4
|
+
* The engine (semantic diff, granularity, partition) produces plan-ordered
|
|
5
|
+
* {@link PgpmRow}s; a {@link PgpmModuleModel} is just those rows plus the
|
|
6
|
+
* module's name and control-file requires. `writeModule` is the one writer
|
|
7
|
+
* that turns a model into an on-disk pgpm module (`.control` + `pgpm.plan` +
|
|
8
|
+
* deploy/revert/verify trees) — so `pgpm diff`, `pgpm transform`, and
|
|
9
|
+
* `pgpm import` all emit byte-identical module layouts instead of each
|
|
10
|
+
* carrying its own copy of the writer.
|
|
11
|
+
*/
|
|
12
|
+
import { PgpmRow } from '@pgpmjs/ast';
|
|
13
|
+
/**
|
|
14
|
+
* A pgpm module as an in-memory value: a name, the extensions it requires
|
|
15
|
+
* (control-file names), and its changes in plan order. `writeModule` and the
|
|
16
|
+
* other projections (linear SQL, bundle) are pure functions of this model.
|
|
17
|
+
*/
|
|
18
|
+
export interface PgpmModuleModel {
|
|
19
|
+
/** Package (module) name. */
|
|
20
|
+
name: string;
|
|
21
|
+
/** Module names this package requires (control-file names). */
|
|
22
|
+
requires: string[];
|
|
23
|
+
/** Changes in plan order. */
|
|
24
|
+
rows: PgpmRow[];
|
|
25
|
+
}
|
|
26
|
+
/** Write a module's Postgres extension `.control` file. */
|
|
27
|
+
export declare const writeControlFile: (dir: string, name: string, requires: string[]) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Write a {@link PgpmModuleModel} to `<outBase>/<name>` as a full pgpm module:
|
|
30
|
+
* a `.control` file, `pgpm.plan`, and deploy/revert/verify script trees.
|
|
31
|
+
* The deploy/revert/verify directories are cleared first so a re-emit never
|
|
32
|
+
* leaves stale scripts behind. Returns the module directory.
|
|
33
|
+
*/
|
|
34
|
+
export declare const writeModule: (outBase: string, model: PgpmModuleModel, extraRequires?: string[]) => string;
|
|
35
|
+
/** Result of appending changes into an existing module (see {@link appendModule}). */
|
|
36
|
+
export interface AppendModuleResult {
|
|
37
|
+
/** The module directory that was appended to. */
|
|
38
|
+
dir: string;
|
|
39
|
+
/** Change names that were added to the plan. */
|
|
40
|
+
added: string[];
|
|
41
|
+
/** Change names skipped because they already exist in the plan. */
|
|
42
|
+
skipped: string[];
|
|
43
|
+
/** Non-fatal notices (skips, dropped dangling dependencies). */
|
|
44
|
+
warnings: string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Append plan-ordered {@link PgpmRow}s into an *existing* module rather than
|
|
48
|
+
* writing a fresh package. Existing changes, their scripts, and the `.control`
|
|
49
|
+
* file are left untouched; only the new changes are written (deploy/revert/
|
|
50
|
+
* verify) and appended to `pgpm.plan` after the current changes.
|
|
51
|
+
*
|
|
52
|
+
* Rows whose change name already exists are skipped (never overwritten).
|
|
53
|
+
* A new change's dependency bracket is filtered to names that resolve within
|
|
54
|
+
* the plan (existing changes, other appended changes, or `pkg:`-external
|
|
55
|
+
* references); a dangling internal dependency is dropped with a warning
|
|
56
|
+
* (plan order still sequences it after the current changes).
|
|
57
|
+
*/
|
|
58
|
+
export declare const appendModule: (moduleDir: string, rows: PgpmRow[], options?: {
|
|
59
|
+
author?: string;
|
|
60
|
+
}) => AppendModuleResult;
|
|
61
|
+
/**
|
|
62
|
+
* Guard against clobbering: writing into the source directory requires
|
|
63
|
+
* `--write`, as does overwriting any existing package directory.
|
|
64
|
+
*/
|
|
65
|
+
export declare const checkOverwrite: (targetDir: string, sourcePath: string, write: boolean) => string | null;
|
package/module-emit.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.checkOverwrite = exports.appendModule = exports.writeModule = exports.writeControlFile = void 0;
|
|
37
|
+
/**
|
|
38
|
+
* Module emission — the single seam every dials command renders through.
|
|
39
|
+
*
|
|
40
|
+
* The engine (semantic diff, granularity, partition) produces plan-ordered
|
|
41
|
+
* {@link PgpmRow}s; a {@link PgpmModuleModel} is just those rows plus the
|
|
42
|
+
* module's name and control-file requires. `writeModule` is the one writer
|
|
43
|
+
* that turns a model into an on-disk pgpm module (`.control` + `pgpm.plan` +
|
|
44
|
+
* deploy/revert/verify trees) — so `pgpm diff`, `pgpm transform`, and
|
|
45
|
+
* `pgpm import` all emit byte-identical module layouts instead of each
|
|
46
|
+
* carrying its own copy of the writer.
|
|
47
|
+
*/
|
|
48
|
+
const ast_1 = require("@pgpmjs/ast");
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
/** Write a module's Postgres extension `.control` file. */
|
|
52
|
+
const writeControlFile = (dir, name, requires) => {
|
|
53
|
+
const lines = [
|
|
54
|
+
`# ${name} extension`,
|
|
55
|
+
`comment = '${name} extension'`,
|
|
56
|
+
`default_version = '0.0.1'`,
|
|
57
|
+
`relocatable = false`,
|
|
58
|
+
`superuser = false`
|
|
59
|
+
];
|
|
60
|
+
if (requires.length) {
|
|
61
|
+
lines.push(`requires = '${requires.join(',')}'`);
|
|
62
|
+
}
|
|
63
|
+
fs.writeFileSync(path.join(dir, `${name}.control`), lines.join('\n') + '\n');
|
|
64
|
+
};
|
|
65
|
+
exports.writeControlFile = writeControlFile;
|
|
66
|
+
/**
|
|
67
|
+
* Write a {@link PgpmModuleModel} to `<outBase>/<name>` as a full pgpm module:
|
|
68
|
+
* a `.control` file, `pgpm.plan`, and deploy/revert/verify script trees.
|
|
69
|
+
* The deploy/revert/verify directories are cleared first so a re-emit never
|
|
70
|
+
* leaves stale scripts behind. Returns the module directory.
|
|
71
|
+
*/
|
|
72
|
+
const writeModule = (outBase, model, extraRequires = []) => {
|
|
73
|
+
const dir = path.join(outBase, model.name);
|
|
74
|
+
fs.rmSync(path.join(dir, 'deploy'), { recursive: true, force: true });
|
|
75
|
+
fs.rmSync(path.join(dir, 'revert'), { recursive: true, force: true });
|
|
76
|
+
fs.rmSync(path.join(dir, 'verify'), { recursive: true, force: true });
|
|
77
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
78
|
+
(0, exports.writeControlFile)(dir, model.name, [...extraRequires, ...model.requires]);
|
|
79
|
+
const opts = {
|
|
80
|
+
outdir: outBase,
|
|
81
|
+
name: model.name,
|
|
82
|
+
replacer: (str) => str.split('constructive-extension-name').join(model.name)
|
|
83
|
+
};
|
|
84
|
+
(0, ast_1.writePgpmPlan)(model.rows, opts);
|
|
85
|
+
(0, ast_1.writePgpmFiles)(model.rows, opts);
|
|
86
|
+
return dir;
|
|
87
|
+
};
|
|
88
|
+
exports.writeModule = writeModule;
|
|
89
|
+
/**
|
|
90
|
+
* Append plan-ordered {@link PgpmRow}s into an *existing* module rather than
|
|
91
|
+
* writing a fresh package. Existing changes, their scripts, and the `.control`
|
|
92
|
+
* file are left untouched; only the new changes are written (deploy/revert/
|
|
93
|
+
* verify) and appended to `pgpm.plan` after the current changes.
|
|
94
|
+
*
|
|
95
|
+
* Rows whose change name already exists are skipped (never overwritten).
|
|
96
|
+
* A new change's dependency bracket is filtered to names that resolve within
|
|
97
|
+
* the plan (existing changes, other appended changes, or `pkg:`-external
|
|
98
|
+
* references); a dangling internal dependency is dropped with a warning
|
|
99
|
+
* (plan order still sequences it after the current changes).
|
|
100
|
+
*/
|
|
101
|
+
const appendModule = (moduleDir, rows, options = {}) => {
|
|
102
|
+
const planPath = path.join(moduleDir, 'pgpm.plan');
|
|
103
|
+
if (!fs.existsSync(planPath)) {
|
|
104
|
+
throw new Error(`No pgpm.plan found at ${planPath}; append mode expects an existing pgpm module directory.`);
|
|
105
|
+
}
|
|
106
|
+
const parsed = (0, ast_1.parsePlanFile)(planPath);
|
|
107
|
+
if (!parsed.data) {
|
|
108
|
+
throw new Error(`Failed to parse ${planPath}: ${parsed.errors
|
|
109
|
+
.map(e => `line ${e.line}: ${e.message}`)
|
|
110
|
+
.join('; ')}`);
|
|
111
|
+
}
|
|
112
|
+
const plan = parsed.data;
|
|
113
|
+
const existingNames = new Set(plan.changes.map(c => c.name));
|
|
114
|
+
const incomingNames = new Set(rows.map(r => r.deploy));
|
|
115
|
+
const appended = new Set();
|
|
116
|
+
const added = [];
|
|
117
|
+
const skipped = [];
|
|
118
|
+
const warnings = [];
|
|
119
|
+
const newRows = [];
|
|
120
|
+
const { fullName, email } = (0, ast_1.parseAuthor)(options.author || 'constructive');
|
|
121
|
+
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
122
|
+
for (const row of rows) {
|
|
123
|
+
if (existingNames.has(row.deploy) || appended.has(row.deploy)) {
|
|
124
|
+
skipped.push(row.deploy);
|
|
125
|
+
warnings.push(`change ${row.deploy} already exists in ${plan.package}; left untouched`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const deps = (row.deps ?? []).filter(dep => {
|
|
129
|
+
if (dep.includes(':'))
|
|
130
|
+
return true; // cross-package (pkg:change) external
|
|
131
|
+
if (existingNames.has(dep) || incomingNames.has(dep))
|
|
132
|
+
return true;
|
|
133
|
+
warnings.push(`change ${row.deploy}: dropped dependency ${dep} (not present in ${plan.package})`);
|
|
134
|
+
return false;
|
|
135
|
+
});
|
|
136
|
+
plan.changes.push({
|
|
137
|
+
name: row.deploy,
|
|
138
|
+
dependencies: deps,
|
|
139
|
+
timestamp,
|
|
140
|
+
planner: fullName,
|
|
141
|
+
email: email || `${fullName}@constructive.io`,
|
|
142
|
+
comment: `add ${row.name ?? row.deploy}`
|
|
143
|
+
});
|
|
144
|
+
newRows.push({ ...row, deps });
|
|
145
|
+
appended.add(row.deploy);
|
|
146
|
+
added.push(row.deploy);
|
|
147
|
+
}
|
|
148
|
+
if (newRows.length) {
|
|
149
|
+
const opts = {
|
|
150
|
+
outdir: path.dirname(moduleDir),
|
|
151
|
+
name: path.basename(moduleDir),
|
|
152
|
+
replacer: (str) => str
|
|
153
|
+
};
|
|
154
|
+
(0, ast_1.writePgpmFiles)(newRows, opts);
|
|
155
|
+
(0, ast_1.writePlanFile)(planPath, plan);
|
|
156
|
+
}
|
|
157
|
+
return { dir: moduleDir, added, skipped, warnings };
|
|
158
|
+
};
|
|
159
|
+
exports.appendModule = appendModule;
|
|
160
|
+
/**
|
|
161
|
+
* Guard against clobbering: writing into the source directory requires
|
|
162
|
+
* `--write`, as does overwriting any existing package directory.
|
|
163
|
+
*/
|
|
164
|
+
const checkOverwrite = (targetDir, sourcePath, write) => {
|
|
165
|
+
const resolvedTarget = path.resolve(targetDir);
|
|
166
|
+
const resolvedSource = path.resolve(sourcePath);
|
|
167
|
+
if (resolvedTarget === resolvedSource && !write) {
|
|
168
|
+
return `Refusing to overwrite the source module at ${resolvedSource} (pass --write to allow).`;
|
|
169
|
+
}
|
|
170
|
+
if (resolvedTarget !== resolvedSource && fs.existsSync(resolvedTarget) && !write) {
|
|
171
|
+
return `Output directory ${resolvedTarget} already exists (pass --write to overwrite).`;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
};
|
|
175
|
+
exports.checkOverwrite = checkOverwrite;
|