pgpm 3.0.1 → 3.1.1
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/commands/init/index.js +32 -10
- package/commands/slice.d.ts +3 -0
- package/commands/slice.js +255 -0
- package/commands.js +2 -0
- package/esm/commands/init/index.js +32 -10
- package/esm/commands/slice.js +253 -0
- package/esm/commands.js +2 -0
- package/esm/index.js +1 -0
- package/index.d.ts +1 -0
- package/index.js +3 -1
- package/package.json +3 -3
package/commands/init/index.js
CHANGED
|
@@ -241,19 +241,20 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
|
|
|
241
241
|
// Whether this is a pgpm-managed template (creates pgpm.plan, .control files)
|
|
242
242
|
const isPgpmTemplate = workspaceType === 'pgpm';
|
|
243
243
|
const project = new core_1.PgpmPackage(ctx.cwd);
|
|
244
|
+
// Track resolved workspace path for both pgpm and non-pgpm workspace types
|
|
245
|
+
let resolvedWorkspacePath;
|
|
246
|
+
let workspaceTypeName = '';
|
|
244
247
|
// Check workspace requirement based on type (skip if workspaceType is false)
|
|
245
248
|
if (workspaceType !== false) {
|
|
246
|
-
let workspacePath;
|
|
247
|
-
let workspaceTypeName = '';
|
|
248
249
|
if (workspaceType === 'pgpm') {
|
|
249
|
-
|
|
250
|
+
resolvedWorkspacePath = project.workspacePath;
|
|
250
251
|
workspaceTypeName = 'PGPM';
|
|
251
252
|
}
|
|
252
253
|
else {
|
|
253
|
-
|
|
254
|
+
resolvedWorkspacePath = (0, env_1.resolveWorkspaceByType)(ctx.cwd, workspaceType);
|
|
254
255
|
workspaceTypeName = workspaceType.toUpperCase();
|
|
255
256
|
}
|
|
256
|
-
if (!
|
|
257
|
+
if (!resolvedWorkspacePath) {
|
|
257
258
|
const noTty = Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true');
|
|
258
259
|
// If user explicitly requested module init or we're in non-interactive mode,
|
|
259
260
|
// just show the error with helpful guidance
|
|
@@ -261,8 +262,9 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
|
|
|
261
262
|
process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
|
|
262
263
|
throw types_1.errors.NOT_IN_WORKSPACE({});
|
|
263
264
|
}
|
|
264
|
-
//
|
|
265
|
-
|
|
265
|
+
// Offer to create a workspace for pgpm templates or when --dir is specified
|
|
266
|
+
// (when --dir is specified, we know which workspace variant to use)
|
|
267
|
+
if (workspaceType === 'pgpm' || ctx.dir) {
|
|
266
268
|
const recoveryQuestion = [
|
|
267
269
|
{
|
|
268
270
|
name: 'workspace',
|
|
@@ -284,7 +286,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
|
|
|
284
286
|
});
|
|
285
287
|
}
|
|
286
288
|
}
|
|
287
|
-
// User declined or non-pgpm workspace type, show the error
|
|
289
|
+
// User declined or non-pgpm workspace type without --dir, show the error
|
|
288
290
|
process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
|
|
289
291
|
throw types_1.errors.NOT_IN_WORKSPACE({});
|
|
290
292
|
}
|
|
@@ -334,7 +336,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
|
|
|
334
336
|
// Determine output path based on whether we're in a workspace
|
|
335
337
|
let modulePath;
|
|
336
338
|
if (project.workspacePath) {
|
|
337
|
-
//
|
|
339
|
+
// PGPM workspace - use workspace-aware initModule
|
|
338
340
|
await project.initModule({
|
|
339
341
|
name: modName,
|
|
340
342
|
description: answers.description || modName,
|
|
@@ -354,8 +356,28 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
|
|
|
354
356
|
? path_1.default.join(ctx.cwd, 'packages', modName)
|
|
355
357
|
: path_1.default.join(ctx.cwd, modName);
|
|
356
358
|
}
|
|
359
|
+
else if (resolvedWorkspacePath && workspaceType !== false) {
|
|
360
|
+
// Non-pgpm workspace (pnpm, lerna, npm) - scaffold to packages/ directory
|
|
361
|
+
const isRoot = path_1.default.resolve(resolvedWorkspacePath) === path_1.default.resolve(ctx.cwd);
|
|
362
|
+
modulePath = isRoot
|
|
363
|
+
? path_1.default.join(ctx.cwd, 'packages', modName)
|
|
364
|
+
: path_1.default.join(ctx.cwd, modName);
|
|
365
|
+
fs_1.default.mkdirSync(modulePath, { recursive: true });
|
|
366
|
+
await (0, core_1.scaffoldTemplate)({
|
|
367
|
+
fromPath: ctx.fromPath,
|
|
368
|
+
outputDir: modulePath,
|
|
369
|
+
templateRepo: ctx.templateRepo,
|
|
370
|
+
branch: ctx.branch,
|
|
371
|
+
dir: ctx.dir,
|
|
372
|
+
answers: templateAnswers,
|
|
373
|
+
noTty: ctx.noTty,
|
|
374
|
+
toolName: core_1.DEFAULT_TEMPLATE_TOOL_NAME,
|
|
375
|
+
cwd: ctx.cwd,
|
|
376
|
+
prompter
|
|
377
|
+
});
|
|
378
|
+
}
|
|
357
379
|
else {
|
|
358
|
-
// Not in
|
|
380
|
+
// Not in any workspace (requiresWorkspace: false) - scaffold to current directory
|
|
359
381
|
modulePath = path_1.default.join(ctx.cwd, modName);
|
|
360
382
|
fs_1.default.mkdirSync(modulePath, { recursive: true });
|
|
361
383
|
await (0, core_1.scaffoldTemplate)({
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@pgpmjs/core");
|
|
4
|
+
const types_1 = require("@pgpmjs/types");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const sliceUsageText = `
|
|
8
|
+
Slice Command:
|
|
9
|
+
|
|
10
|
+
pgpm slice [OPTIONS]
|
|
11
|
+
|
|
12
|
+
Slice a large plan file into multiple modular packages based on folder structure
|
|
13
|
+
or glob patterns.
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--help, -h Show this help message
|
|
17
|
+
--plan <path> Path to source plan file (default: pgpm.plan in current module)
|
|
18
|
+
--output <directory> Output directory for sliced packages (default: ./sliced)
|
|
19
|
+
--strategy <type> Grouping strategy: 'folder' or 'pattern' (default: folder)
|
|
20
|
+
--depth <number> Folder depth for package extraction (default: 1, folder strategy only)
|
|
21
|
+
--prefix <string> Prefix to strip from paths (default: schemas, folder strategy only)
|
|
22
|
+
--patterns <file> JSON file with pattern definitions (pattern strategy only)
|
|
23
|
+
--default <name> Default package name for unmatched changes (default: core)
|
|
24
|
+
--min-changes <number> Minimum changes per package (smaller packages are merged)
|
|
25
|
+
--use-tags Use tags for cross-package dependencies
|
|
26
|
+
--dry-run Show what would be created without writing files
|
|
27
|
+
--overwrite Overwrite existing package directories
|
|
28
|
+
--copy-files Copy SQL files from source to output packages
|
|
29
|
+
--cwd <directory> Working directory (default: current directory)
|
|
30
|
+
|
|
31
|
+
Strategies:
|
|
32
|
+
folder - Groups changes by folder depth (e.g., schemas/auth_public/* -> auth_public)
|
|
33
|
+
pattern - Groups changes by glob patterns defined in a JSON file
|
|
34
|
+
|
|
35
|
+
Pattern File Format (for --patterns):
|
|
36
|
+
{
|
|
37
|
+
"slices": [
|
|
38
|
+
{ "packageName": "auth", "patterns": ["schemas/*_auth_*/**", "schemas/*_tokens_*/**"] },
|
|
39
|
+
{ "packageName": "users", "patterns": ["schemas/*_users_*/**", "schemas/*_emails_*/**"] }
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
pgpm slice Slice using folder strategy (default)
|
|
45
|
+
pgpm slice --dry-run Preview slicing without writing files
|
|
46
|
+
pgpm slice --depth 2 Use 2-level folder grouping
|
|
47
|
+
pgpm slice --strategy pattern --patterns ./slices.json Use pattern-based slicing
|
|
48
|
+
pgpm slice --output ./packages Output to specific directory
|
|
49
|
+
pgpm slice --min-changes 10 Merge packages with fewer than 10 changes
|
|
50
|
+
`;
|
|
51
|
+
exports.default = async (argv, prompter, _options) => {
|
|
52
|
+
// Show usage if explicitly requested
|
|
53
|
+
if (argv.help || argv.h) {
|
|
54
|
+
console.log(sliceUsageText);
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
const { username, email } = (0, types_1.getGitConfigInfo)();
|
|
58
|
+
const cwd = argv.cwd ?? process.cwd();
|
|
59
|
+
const project = new core_1.PgpmPackage(cwd);
|
|
60
|
+
// Determine source plan file
|
|
61
|
+
let sourcePlan;
|
|
62
|
+
if (argv.plan) {
|
|
63
|
+
sourcePlan = (0, path_1.resolve)(cwd, argv.plan);
|
|
64
|
+
}
|
|
65
|
+
else if (project.isInModule()) {
|
|
66
|
+
sourcePlan = (0, path_1.resolve)(project.getModulePath(), 'pgpm.plan');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Prompt for plan file
|
|
70
|
+
const { planPath } = await prompter.prompt(argv, [
|
|
71
|
+
{
|
|
72
|
+
type: 'text',
|
|
73
|
+
name: 'planPath',
|
|
74
|
+
message: 'Path to source plan file',
|
|
75
|
+
default: 'pgpm.plan',
|
|
76
|
+
required: true
|
|
77
|
+
}
|
|
78
|
+
]);
|
|
79
|
+
sourcePlan = (0, path_1.resolve)(cwd, planPath);
|
|
80
|
+
}
|
|
81
|
+
// Determine output directory
|
|
82
|
+
const outputDir = argv.output
|
|
83
|
+
? (0, path_1.resolve)(cwd, argv.output)
|
|
84
|
+
: (0, path_1.resolve)(cwd, 'sliced');
|
|
85
|
+
// Determine strategy type
|
|
86
|
+
const strategyType = argv.strategy ?? 'folder';
|
|
87
|
+
// Load pattern file if using pattern strategy
|
|
88
|
+
let patternSlices = [];
|
|
89
|
+
if (strategyType === 'pattern') {
|
|
90
|
+
if (!argv.patterns) {
|
|
91
|
+
console.error('Error: --patterns <file> is required when using pattern strategy');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
const patternsPath = (0, path_1.resolve)(cwd, argv.patterns);
|
|
95
|
+
if (!(0, fs_1.existsSync)(patternsPath)) {
|
|
96
|
+
console.error(`Error: Pattern file not found: ${patternsPath}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const patternsContent = (0, fs_1.readFileSync)(patternsPath, 'utf-8');
|
|
101
|
+
const patternsData = JSON.parse(patternsContent);
|
|
102
|
+
if (!patternsData.slices || !Array.isArray(patternsData.slices)) {
|
|
103
|
+
console.error('Error: Pattern file must contain a "slices" array');
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
patternSlices = patternsData.slices;
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.error(`Error parsing pattern file: ${err.message}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Get configuration options (only prompt for folder-specific options if using folder strategy)
|
|
114
|
+
const folderQuestions = strategyType === 'folder' ? [
|
|
115
|
+
{
|
|
116
|
+
type: 'number',
|
|
117
|
+
name: 'depth',
|
|
118
|
+
message: 'Folder depth for package extraction',
|
|
119
|
+
default: 1,
|
|
120
|
+
useDefault: true
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: 'text',
|
|
124
|
+
name: 'prefix',
|
|
125
|
+
message: 'Prefix to strip from paths',
|
|
126
|
+
default: 'schemas',
|
|
127
|
+
useDefault: true
|
|
128
|
+
}
|
|
129
|
+
] : [];
|
|
130
|
+
const { depth, prefix, defaultPackage, minChanges, useTags } = await prompter.prompt(argv, [
|
|
131
|
+
...folderQuestions,
|
|
132
|
+
{
|
|
133
|
+
type: 'text',
|
|
134
|
+
name: 'defaultPackage',
|
|
135
|
+
message: 'Default package name for unmatched changes',
|
|
136
|
+
default: 'core',
|
|
137
|
+
useDefault: true
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
type: 'number',
|
|
141
|
+
name: 'minChanges',
|
|
142
|
+
message: 'Minimum changes per package (0 to disable merging)',
|
|
143
|
+
default: 0,
|
|
144
|
+
useDefault: true
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: 'confirm',
|
|
148
|
+
name: 'useTags',
|
|
149
|
+
message: 'Use tags for cross-package dependencies?',
|
|
150
|
+
default: false,
|
|
151
|
+
useDefault: true
|
|
152
|
+
}
|
|
153
|
+
]);
|
|
154
|
+
// Build slice configuration based on strategy
|
|
155
|
+
const config = {
|
|
156
|
+
sourcePlan,
|
|
157
|
+
outputDir,
|
|
158
|
+
strategy: strategyType === 'pattern'
|
|
159
|
+
? { type: 'pattern', slices: patternSlices }
|
|
160
|
+
: {
|
|
161
|
+
type: 'folder',
|
|
162
|
+
depth: argv.depth ?? depth ?? 1,
|
|
163
|
+
prefixToStrip: argv.prefix ?? prefix ?? 'schemas'
|
|
164
|
+
},
|
|
165
|
+
defaultPackage: argv.default ?? defaultPackage ?? 'core',
|
|
166
|
+
minChangesPerPackage: argv['min-changes'] ?? minChanges ?? 0,
|
|
167
|
+
useTagsForCrossPackageDeps: argv['use-tags'] ?? useTags ?? false,
|
|
168
|
+
author: `${username} <${email}>`
|
|
169
|
+
};
|
|
170
|
+
console.log(`\nSlicing plan: ${sourcePlan}`);
|
|
171
|
+
console.log(`Output directory: ${outputDir}`);
|
|
172
|
+
if (config.strategy.type === 'folder') {
|
|
173
|
+
console.log(`Strategy: folder-based (depth=${config.strategy.depth})`);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
console.log(`Strategy: pattern-based (${patternSlices.length} slice definitions)`);
|
|
177
|
+
}
|
|
178
|
+
console.log('');
|
|
179
|
+
// Perform slicing
|
|
180
|
+
const result = (0, core_1.slicePlan)(config);
|
|
181
|
+
// Handle dry run
|
|
182
|
+
if (argv['dry-run'] || argv.dryRun) {
|
|
183
|
+
const report = (0, core_1.generateDryRunReport)(result);
|
|
184
|
+
console.log(report);
|
|
185
|
+
prompter.close();
|
|
186
|
+
return argv;
|
|
187
|
+
}
|
|
188
|
+
// Show summary before writing
|
|
189
|
+
console.log(`Found ${result.stats.totalChanges} changes`);
|
|
190
|
+
console.log(`Creating ${result.stats.packagesCreated} packages`);
|
|
191
|
+
console.log(`Cross-package dependency ratio: ${(result.stats.crossPackageRatio * 100).toFixed(1)}%`);
|
|
192
|
+
console.log('');
|
|
193
|
+
// Show warnings
|
|
194
|
+
if (result.warnings.length > 0) {
|
|
195
|
+
console.log('Warnings:');
|
|
196
|
+
for (const warning of result.warnings) {
|
|
197
|
+
console.log(` [${warning.type}] ${warning.message}`);
|
|
198
|
+
}
|
|
199
|
+
console.log('');
|
|
200
|
+
}
|
|
201
|
+
// Show deploy order
|
|
202
|
+
console.log('Deploy order:');
|
|
203
|
+
for (let i = 0; i < result.workspace.deployOrder.length; i++) {
|
|
204
|
+
const pkg = result.workspace.deployOrder[i];
|
|
205
|
+
const deps = result.workspace.dependencies[pkg] || [];
|
|
206
|
+
const depStr = deps.length > 0 ? ` -> ${deps.join(', ')}` : '';
|
|
207
|
+
console.log(` ${i + 1}. ${pkg}${depStr}`);
|
|
208
|
+
}
|
|
209
|
+
console.log('');
|
|
210
|
+
// Confirm before writing (unless --overwrite is specified)
|
|
211
|
+
if (!argv.overwrite) {
|
|
212
|
+
const confirmResult = await prompter.prompt({}, [
|
|
213
|
+
{
|
|
214
|
+
type: 'confirm',
|
|
215
|
+
name: 'confirm',
|
|
216
|
+
message: 'Proceed with writing packages?',
|
|
217
|
+
default: true
|
|
218
|
+
}
|
|
219
|
+
]);
|
|
220
|
+
if (!confirmResult.confirm) {
|
|
221
|
+
console.log('Aborted.');
|
|
222
|
+
prompter.close();
|
|
223
|
+
return argv;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Determine source directory for copying files
|
|
227
|
+
let sourceDir;
|
|
228
|
+
if (argv['copy-files'] || argv.copyFiles) {
|
|
229
|
+
if (project.isInModule()) {
|
|
230
|
+
sourceDir = project.getModulePath();
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// Use directory containing the plan file
|
|
234
|
+
sourceDir = (0, path_1.resolve)(sourcePlan, '..');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Write packages to disk
|
|
238
|
+
(0, core_1.writeSliceResult)(result, {
|
|
239
|
+
outputDir,
|
|
240
|
+
overwrite: argv.overwrite ?? false,
|
|
241
|
+
copySourceFiles: argv['copy-files'] ?? argv.copyFiles ?? false,
|
|
242
|
+
sourceDir
|
|
243
|
+
});
|
|
244
|
+
prompter.close();
|
|
245
|
+
console.log(`
|
|
246
|
+
|||
|
|
247
|
+
(o o)
|
|
248
|
+
ooO--(_)--Ooo-
|
|
249
|
+
|
|
250
|
+
Sliced into ${result.stats.packagesCreated} packages!
|
|
251
|
+
|
|
252
|
+
Output: ${outputDir}
|
|
253
|
+
`);
|
|
254
|
+
return argv;
|
|
255
|
+
};
|
package/commands.js
CHANGED
|
@@ -29,6 +29,7 @@ const upgrade_1 = __importDefault(require("./commands/upgrade"));
|
|
|
29
29
|
const remove_1 = __importDefault(require("./commands/remove"));
|
|
30
30
|
const rename_1 = __importDefault(require("./commands/rename"));
|
|
31
31
|
const revert_1 = __importDefault(require("./commands/revert"));
|
|
32
|
+
const slice_1 = __importDefault(require("./commands/slice"));
|
|
32
33
|
const tag_1 = __importDefault(require("./commands/tag"));
|
|
33
34
|
const test_packages_1 = __importDefault(require("./commands/test-packages"));
|
|
34
35
|
const verify_1 = __importDefault(require("./commands/verify"));
|
|
@@ -67,6 +68,7 @@ const createPgpmCommandMap = (skipPgTeardown = false) => {
|
|
|
67
68
|
migrate: pgt(migrate_1.default),
|
|
68
69
|
analyze: pgt(analyze_1.default),
|
|
69
70
|
rename: pgt(rename_1.default),
|
|
71
|
+
slice: slice_1.default,
|
|
70
72
|
'test-packages': pgt(test_packages_1.default),
|
|
71
73
|
upgrade: pgt(upgrade_1.default),
|
|
72
74
|
up: pgt(upgrade_1.default),
|
|
@@ -234,19 +234,20 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
|
|
|
234
234
|
// Whether this is a pgpm-managed template (creates pgpm.plan, .control files)
|
|
235
235
|
const isPgpmTemplate = workspaceType === 'pgpm';
|
|
236
236
|
const project = new PgpmPackage(ctx.cwd);
|
|
237
|
+
// Track resolved workspace path for both pgpm and non-pgpm workspace types
|
|
238
|
+
let resolvedWorkspacePath;
|
|
239
|
+
let workspaceTypeName = '';
|
|
237
240
|
// Check workspace requirement based on type (skip if workspaceType is false)
|
|
238
241
|
if (workspaceType !== false) {
|
|
239
|
-
let workspacePath;
|
|
240
|
-
let workspaceTypeName = '';
|
|
241
242
|
if (workspaceType === 'pgpm') {
|
|
242
|
-
|
|
243
|
+
resolvedWorkspacePath = project.workspacePath;
|
|
243
244
|
workspaceTypeName = 'PGPM';
|
|
244
245
|
}
|
|
245
246
|
else {
|
|
246
|
-
|
|
247
|
+
resolvedWorkspacePath = resolveWorkspaceByType(ctx.cwd, workspaceType);
|
|
247
248
|
workspaceTypeName = workspaceType.toUpperCase();
|
|
248
249
|
}
|
|
249
|
-
if (!
|
|
250
|
+
if (!resolvedWorkspacePath) {
|
|
250
251
|
const noTty = Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true');
|
|
251
252
|
// If user explicitly requested module init or we're in non-interactive mode,
|
|
252
253
|
// just show the error with helpful guidance
|
|
@@ -254,8 +255,9 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
|
|
|
254
255
|
process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
|
|
255
256
|
throw errors.NOT_IN_WORKSPACE({});
|
|
256
257
|
}
|
|
257
|
-
//
|
|
258
|
-
|
|
258
|
+
// Offer to create a workspace for pgpm templates or when --dir is specified
|
|
259
|
+
// (when --dir is specified, we know which workspace variant to use)
|
|
260
|
+
if (workspaceType === 'pgpm' || ctx.dir) {
|
|
259
261
|
const recoveryQuestion = [
|
|
260
262
|
{
|
|
261
263
|
name: 'workspace',
|
|
@@ -277,7 +279,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
|
|
|
277
279
|
});
|
|
278
280
|
}
|
|
279
281
|
}
|
|
280
|
-
// User declined or non-pgpm workspace type, show the error
|
|
282
|
+
// User declined or non-pgpm workspace type without --dir, show the error
|
|
281
283
|
process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
|
|
282
284
|
throw errors.NOT_IN_WORKSPACE({});
|
|
283
285
|
}
|
|
@@ -327,7 +329,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
|
|
|
327
329
|
// Determine output path based on whether we're in a workspace
|
|
328
330
|
let modulePath;
|
|
329
331
|
if (project.workspacePath) {
|
|
330
|
-
//
|
|
332
|
+
// PGPM workspace - use workspace-aware initModule
|
|
331
333
|
await project.initModule({
|
|
332
334
|
name: modName,
|
|
333
335
|
description: answers.description || modName,
|
|
@@ -347,8 +349,28 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
|
|
|
347
349
|
? path.join(ctx.cwd, 'packages', modName)
|
|
348
350
|
: path.join(ctx.cwd, modName);
|
|
349
351
|
}
|
|
352
|
+
else if (resolvedWorkspacePath && workspaceType !== false) {
|
|
353
|
+
// Non-pgpm workspace (pnpm, lerna, npm) - scaffold to packages/ directory
|
|
354
|
+
const isRoot = path.resolve(resolvedWorkspacePath) === path.resolve(ctx.cwd);
|
|
355
|
+
modulePath = isRoot
|
|
356
|
+
? path.join(ctx.cwd, 'packages', modName)
|
|
357
|
+
: path.join(ctx.cwd, modName);
|
|
358
|
+
fs.mkdirSync(modulePath, { recursive: true });
|
|
359
|
+
await scaffoldTemplate({
|
|
360
|
+
fromPath: ctx.fromPath,
|
|
361
|
+
outputDir: modulePath,
|
|
362
|
+
templateRepo: ctx.templateRepo,
|
|
363
|
+
branch: ctx.branch,
|
|
364
|
+
dir: ctx.dir,
|
|
365
|
+
answers: templateAnswers,
|
|
366
|
+
noTty: ctx.noTty,
|
|
367
|
+
toolName: DEFAULT_TEMPLATE_TOOL_NAME,
|
|
368
|
+
cwd: ctx.cwd,
|
|
369
|
+
prompter
|
|
370
|
+
});
|
|
371
|
+
}
|
|
350
372
|
else {
|
|
351
|
-
// Not in
|
|
373
|
+
// Not in any workspace (requiresWorkspace: false) - scaffold to current directory
|
|
352
374
|
modulePath = path.join(ctx.cwd, modName);
|
|
353
375
|
fs.mkdirSync(modulePath, { recursive: true });
|
|
354
376
|
await scaffoldTemplate({
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { PgpmPackage, slicePlan, writeSliceResult, generateDryRunReport } from '@pgpmjs/core';
|
|
2
|
+
import { getGitConfigInfo } from '@pgpmjs/types';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { readFileSync, existsSync } from 'fs';
|
|
5
|
+
const sliceUsageText = `
|
|
6
|
+
Slice Command:
|
|
7
|
+
|
|
8
|
+
pgpm slice [OPTIONS]
|
|
9
|
+
|
|
10
|
+
Slice a large plan file into multiple modular packages based on folder structure
|
|
11
|
+
or glob patterns.
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
--help, -h Show this help message
|
|
15
|
+
--plan <path> Path to source plan file (default: pgpm.plan in current module)
|
|
16
|
+
--output <directory> Output directory for sliced packages (default: ./sliced)
|
|
17
|
+
--strategy <type> Grouping strategy: 'folder' or 'pattern' (default: folder)
|
|
18
|
+
--depth <number> Folder depth for package extraction (default: 1, folder strategy only)
|
|
19
|
+
--prefix <string> Prefix to strip from paths (default: schemas, folder strategy only)
|
|
20
|
+
--patterns <file> JSON file with pattern definitions (pattern strategy only)
|
|
21
|
+
--default <name> Default package name for unmatched changes (default: core)
|
|
22
|
+
--min-changes <number> Minimum changes per package (smaller packages are merged)
|
|
23
|
+
--use-tags Use tags for cross-package dependencies
|
|
24
|
+
--dry-run Show what would be created without writing files
|
|
25
|
+
--overwrite Overwrite existing package directories
|
|
26
|
+
--copy-files Copy SQL files from source to output packages
|
|
27
|
+
--cwd <directory> Working directory (default: current directory)
|
|
28
|
+
|
|
29
|
+
Strategies:
|
|
30
|
+
folder - Groups changes by folder depth (e.g., schemas/auth_public/* -> auth_public)
|
|
31
|
+
pattern - Groups changes by glob patterns defined in a JSON file
|
|
32
|
+
|
|
33
|
+
Pattern File Format (for --patterns):
|
|
34
|
+
{
|
|
35
|
+
"slices": [
|
|
36
|
+
{ "packageName": "auth", "patterns": ["schemas/*_auth_*/**", "schemas/*_tokens_*/**"] },
|
|
37
|
+
{ "packageName": "users", "patterns": ["schemas/*_users_*/**", "schemas/*_emails_*/**"] }
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
pgpm slice Slice using folder strategy (default)
|
|
43
|
+
pgpm slice --dry-run Preview slicing without writing files
|
|
44
|
+
pgpm slice --depth 2 Use 2-level folder grouping
|
|
45
|
+
pgpm slice --strategy pattern --patterns ./slices.json Use pattern-based slicing
|
|
46
|
+
pgpm slice --output ./packages Output to specific directory
|
|
47
|
+
pgpm slice --min-changes 10 Merge packages with fewer than 10 changes
|
|
48
|
+
`;
|
|
49
|
+
export default async (argv, prompter, _options) => {
|
|
50
|
+
// Show usage if explicitly requested
|
|
51
|
+
if (argv.help || argv.h) {
|
|
52
|
+
console.log(sliceUsageText);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
const { username, email } = getGitConfigInfo();
|
|
56
|
+
const cwd = argv.cwd ?? process.cwd();
|
|
57
|
+
const project = new PgpmPackage(cwd);
|
|
58
|
+
// Determine source plan file
|
|
59
|
+
let sourcePlan;
|
|
60
|
+
if (argv.plan) {
|
|
61
|
+
sourcePlan = resolve(cwd, argv.plan);
|
|
62
|
+
}
|
|
63
|
+
else if (project.isInModule()) {
|
|
64
|
+
sourcePlan = resolve(project.getModulePath(), 'pgpm.plan');
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Prompt for plan file
|
|
68
|
+
const { planPath } = await prompter.prompt(argv, [
|
|
69
|
+
{
|
|
70
|
+
type: 'text',
|
|
71
|
+
name: 'planPath',
|
|
72
|
+
message: 'Path to source plan file',
|
|
73
|
+
default: 'pgpm.plan',
|
|
74
|
+
required: true
|
|
75
|
+
}
|
|
76
|
+
]);
|
|
77
|
+
sourcePlan = resolve(cwd, planPath);
|
|
78
|
+
}
|
|
79
|
+
// Determine output directory
|
|
80
|
+
const outputDir = argv.output
|
|
81
|
+
? resolve(cwd, argv.output)
|
|
82
|
+
: resolve(cwd, 'sliced');
|
|
83
|
+
// Determine strategy type
|
|
84
|
+
const strategyType = argv.strategy ?? 'folder';
|
|
85
|
+
// Load pattern file if using pattern strategy
|
|
86
|
+
let patternSlices = [];
|
|
87
|
+
if (strategyType === 'pattern') {
|
|
88
|
+
if (!argv.patterns) {
|
|
89
|
+
console.error('Error: --patterns <file> is required when using pattern strategy');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
const patternsPath = resolve(cwd, argv.patterns);
|
|
93
|
+
if (!existsSync(patternsPath)) {
|
|
94
|
+
console.error(`Error: Pattern file not found: ${patternsPath}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const patternsContent = readFileSync(patternsPath, 'utf-8');
|
|
99
|
+
const patternsData = JSON.parse(patternsContent);
|
|
100
|
+
if (!patternsData.slices || !Array.isArray(patternsData.slices)) {
|
|
101
|
+
console.error('Error: Pattern file must contain a "slices" array');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
patternSlices = patternsData.slices;
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error(`Error parsing pattern file: ${err.message}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Get configuration options (only prompt for folder-specific options if using folder strategy)
|
|
112
|
+
const folderQuestions = strategyType === 'folder' ? [
|
|
113
|
+
{
|
|
114
|
+
type: 'number',
|
|
115
|
+
name: 'depth',
|
|
116
|
+
message: 'Folder depth for package extraction',
|
|
117
|
+
default: 1,
|
|
118
|
+
useDefault: true
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: 'text',
|
|
122
|
+
name: 'prefix',
|
|
123
|
+
message: 'Prefix to strip from paths',
|
|
124
|
+
default: 'schemas',
|
|
125
|
+
useDefault: true
|
|
126
|
+
}
|
|
127
|
+
] : [];
|
|
128
|
+
const { depth, prefix, defaultPackage, minChanges, useTags } = await prompter.prompt(argv, [
|
|
129
|
+
...folderQuestions,
|
|
130
|
+
{
|
|
131
|
+
type: 'text',
|
|
132
|
+
name: 'defaultPackage',
|
|
133
|
+
message: 'Default package name for unmatched changes',
|
|
134
|
+
default: 'core',
|
|
135
|
+
useDefault: true
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'number',
|
|
139
|
+
name: 'minChanges',
|
|
140
|
+
message: 'Minimum changes per package (0 to disable merging)',
|
|
141
|
+
default: 0,
|
|
142
|
+
useDefault: true
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'confirm',
|
|
146
|
+
name: 'useTags',
|
|
147
|
+
message: 'Use tags for cross-package dependencies?',
|
|
148
|
+
default: false,
|
|
149
|
+
useDefault: true
|
|
150
|
+
}
|
|
151
|
+
]);
|
|
152
|
+
// Build slice configuration based on strategy
|
|
153
|
+
const config = {
|
|
154
|
+
sourcePlan,
|
|
155
|
+
outputDir,
|
|
156
|
+
strategy: strategyType === 'pattern'
|
|
157
|
+
? { type: 'pattern', slices: patternSlices }
|
|
158
|
+
: {
|
|
159
|
+
type: 'folder',
|
|
160
|
+
depth: argv.depth ?? depth ?? 1,
|
|
161
|
+
prefixToStrip: argv.prefix ?? prefix ?? 'schemas'
|
|
162
|
+
},
|
|
163
|
+
defaultPackage: argv.default ?? defaultPackage ?? 'core',
|
|
164
|
+
minChangesPerPackage: argv['min-changes'] ?? minChanges ?? 0,
|
|
165
|
+
useTagsForCrossPackageDeps: argv['use-tags'] ?? useTags ?? false,
|
|
166
|
+
author: `${username} <${email}>`
|
|
167
|
+
};
|
|
168
|
+
console.log(`\nSlicing plan: ${sourcePlan}`);
|
|
169
|
+
console.log(`Output directory: ${outputDir}`);
|
|
170
|
+
if (config.strategy.type === 'folder') {
|
|
171
|
+
console.log(`Strategy: folder-based (depth=${config.strategy.depth})`);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
console.log(`Strategy: pattern-based (${patternSlices.length} slice definitions)`);
|
|
175
|
+
}
|
|
176
|
+
console.log('');
|
|
177
|
+
// Perform slicing
|
|
178
|
+
const result = slicePlan(config);
|
|
179
|
+
// Handle dry run
|
|
180
|
+
if (argv['dry-run'] || argv.dryRun) {
|
|
181
|
+
const report = generateDryRunReport(result);
|
|
182
|
+
console.log(report);
|
|
183
|
+
prompter.close();
|
|
184
|
+
return argv;
|
|
185
|
+
}
|
|
186
|
+
// Show summary before writing
|
|
187
|
+
console.log(`Found ${result.stats.totalChanges} changes`);
|
|
188
|
+
console.log(`Creating ${result.stats.packagesCreated} packages`);
|
|
189
|
+
console.log(`Cross-package dependency ratio: ${(result.stats.crossPackageRatio * 100).toFixed(1)}%`);
|
|
190
|
+
console.log('');
|
|
191
|
+
// Show warnings
|
|
192
|
+
if (result.warnings.length > 0) {
|
|
193
|
+
console.log('Warnings:');
|
|
194
|
+
for (const warning of result.warnings) {
|
|
195
|
+
console.log(` [${warning.type}] ${warning.message}`);
|
|
196
|
+
}
|
|
197
|
+
console.log('');
|
|
198
|
+
}
|
|
199
|
+
// Show deploy order
|
|
200
|
+
console.log('Deploy order:');
|
|
201
|
+
for (let i = 0; i < result.workspace.deployOrder.length; i++) {
|
|
202
|
+
const pkg = result.workspace.deployOrder[i];
|
|
203
|
+
const deps = result.workspace.dependencies[pkg] || [];
|
|
204
|
+
const depStr = deps.length > 0 ? ` -> ${deps.join(', ')}` : '';
|
|
205
|
+
console.log(` ${i + 1}. ${pkg}${depStr}`);
|
|
206
|
+
}
|
|
207
|
+
console.log('');
|
|
208
|
+
// Confirm before writing (unless --overwrite is specified)
|
|
209
|
+
if (!argv.overwrite) {
|
|
210
|
+
const confirmResult = await prompter.prompt({}, [
|
|
211
|
+
{
|
|
212
|
+
type: 'confirm',
|
|
213
|
+
name: 'confirm',
|
|
214
|
+
message: 'Proceed with writing packages?',
|
|
215
|
+
default: true
|
|
216
|
+
}
|
|
217
|
+
]);
|
|
218
|
+
if (!confirmResult.confirm) {
|
|
219
|
+
console.log('Aborted.');
|
|
220
|
+
prompter.close();
|
|
221
|
+
return argv;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Determine source directory for copying files
|
|
225
|
+
let sourceDir;
|
|
226
|
+
if (argv['copy-files'] || argv.copyFiles) {
|
|
227
|
+
if (project.isInModule()) {
|
|
228
|
+
sourceDir = project.getModulePath();
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
// Use directory containing the plan file
|
|
232
|
+
sourceDir = resolve(sourcePlan, '..');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Write packages to disk
|
|
236
|
+
writeSliceResult(result, {
|
|
237
|
+
outputDir,
|
|
238
|
+
overwrite: argv.overwrite ?? false,
|
|
239
|
+
copySourceFiles: argv['copy-files'] ?? argv.copyFiles ?? false,
|
|
240
|
+
sourceDir
|
|
241
|
+
});
|
|
242
|
+
prompter.close();
|
|
243
|
+
console.log(`
|
|
244
|
+
|||
|
|
245
|
+
(o o)
|
|
246
|
+
ooO--(_)--Ooo-
|
|
247
|
+
|
|
248
|
+
Sliced into ${result.stats.packagesCreated} packages!
|
|
249
|
+
|
|
250
|
+
Output: ${outputDir}
|
|
251
|
+
`);
|
|
252
|
+
return argv;
|
|
253
|
+
};
|
package/esm/commands.js
CHANGED
|
@@ -23,6 +23,7 @@ import upgrade from './commands/upgrade';
|
|
|
23
23
|
import remove from './commands/remove';
|
|
24
24
|
import renameCmd from './commands/rename';
|
|
25
25
|
import revert from './commands/revert';
|
|
26
|
+
import slice from './commands/slice';
|
|
26
27
|
import tag from './commands/tag';
|
|
27
28
|
import testPackages from './commands/test-packages';
|
|
28
29
|
import verify from './commands/verify';
|
|
@@ -61,6 +62,7 @@ export const createPgpmCommandMap = (skipPgTeardown = false) => {
|
|
|
61
62
|
migrate: pgt(migrate),
|
|
62
63
|
analyze: pgt(analyze),
|
|
63
64
|
rename: pgt(renameCmd),
|
|
65
|
+
slice,
|
|
64
66
|
'test-packages': pgt(testPackages),
|
|
65
67
|
upgrade: pgt(upgrade),
|
|
66
68
|
up: pgt(upgrade),
|
package/esm/index.js
CHANGED
|
@@ -21,6 +21,7 @@ export { default as plan } from './commands/plan';
|
|
|
21
21
|
export { default as remove } from './commands/remove';
|
|
22
22
|
export { default as renameCmd } from './commands/rename';
|
|
23
23
|
export { default as revert } from './commands/revert';
|
|
24
|
+
export { default as slice } from './commands/slice';
|
|
24
25
|
export { default as tag } from './commands/tag';
|
|
25
26
|
export { default as testPackages } from './commands/test-packages';
|
|
26
27
|
export { default as verify } from './commands/verify';
|
package/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export { default as plan } from './commands/plan';
|
|
|
21
21
|
export { default as remove } from './commands/remove';
|
|
22
22
|
export { default as renameCmd } from './commands/rename';
|
|
23
23
|
export { default as revert } from './commands/revert';
|
|
24
|
+
export { default as slice } from './commands/slice';
|
|
24
25
|
export { default as tag } from './commands/tag';
|
|
25
26
|
export { default as testPackages } from './commands/test-packages';
|
|
26
27
|
export { default as verify } from './commands/verify';
|
package/index.js
CHANGED
|
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.options = exports.verify = exports.testPackages = exports.tag = exports.revert = exports.renameCmd = exports.remove = exports.plan = exports._package = exports.migrate = exports.kill = exports.install = exports.extension = exports._export = exports.env = exports.dump = exports.docker = exports.deploy = exports.clear = exports.analyze = exports.adminUsers = exports.add = exports.createPgpmCommandMap = exports.createInitUsageText = void 0;
|
|
21
|
+
exports.options = exports.verify = exports.testPackages = exports.tag = exports.slice = exports.revert = exports.renameCmd = exports.remove = exports.plan = exports._package = exports.migrate = exports.kill = exports.install = exports.extension = exports._export = exports.env = exports.dump = exports.docker = exports.deploy = exports.clear = exports.analyze = exports.adminUsers = exports.add = exports.createPgpmCommandMap = exports.createInitUsageText = void 0;
|
|
22
22
|
const inquirerer_1 = require("inquirerer");
|
|
23
23
|
const commands_1 = require("./commands");
|
|
24
24
|
Object.defineProperty(exports, "createPgpmCommandMap", { enumerable: true, get: function () { return commands_1.createPgpmCommandMap; } });
|
|
@@ -60,6 +60,8 @@ var rename_1 = require("./commands/rename");
|
|
|
60
60
|
Object.defineProperty(exports, "renameCmd", { enumerable: true, get: function () { return __importDefault(rename_1).default; } });
|
|
61
61
|
var revert_1 = require("./commands/revert");
|
|
62
62
|
Object.defineProperty(exports, "revert", { enumerable: true, get: function () { return __importDefault(revert_1).default; } });
|
|
63
|
+
var slice_1 = require("./commands/slice");
|
|
64
|
+
Object.defineProperty(exports, "slice", { enumerable: true, get: function () { return __importDefault(slice_1).default; } });
|
|
63
65
|
var tag_1 = require("./commands/tag");
|
|
64
66
|
Object.defineProperty(exports, "tag", { enumerable: true, get: function () { return __importDefault(tag_1).default; } });
|
|
65
67
|
var test_packages_1 = require("./commands/test-packages");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgpm",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "PostgreSQL Package Manager - Database migration and package management CLI",
|
|
6
6
|
"main": "index.js",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@inquirerer/utils": "^3.2.0",
|
|
49
|
-
"@pgpmjs/core": "^5.0
|
|
49
|
+
"@pgpmjs/core": "^5.1.0",
|
|
50
50
|
"@pgpmjs/env": "^2.10.1",
|
|
51
51
|
"@pgpmjs/logger": "^2.0.0",
|
|
52
52
|
"@pgpmjs/types": "^2.15.0",
|
|
@@ -74,5 +74,5 @@
|
|
|
74
74
|
"pg",
|
|
75
75
|
"pgsql"
|
|
76
76
|
],
|
|
77
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "cb4843d3d80592f369e7f1a67b8a9223e6bfe48c"
|
|
78
78
|
}
|