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.
@@ -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
- workspacePath = project.workspacePath;
250
+ resolvedWorkspacePath = project.workspacePath;
250
251
  workspaceTypeName = 'PGPM';
251
252
  }
252
253
  else {
253
- workspacePath = (0, env_1.resolveWorkspaceByType)(ctx.cwd, workspaceType);
254
+ resolvedWorkspacePath = (0, env_1.resolveWorkspaceByType)(ctx.cwd, workspaceType);
254
255
  workspaceTypeName = workspaceType.toUpperCase();
255
256
  }
256
- if (!workspacePath) {
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
- // Only offer to create a workspace for pgpm templates
265
- if (workspaceType === 'pgpm') {
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
- // Use workspace-aware initModule
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 a workspace - scaffold directly to current directory
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,3 @@
1
+ import { CLIOptions, Inquirerer } from 'inquirerer';
2
+ declare const _default: (argv: Partial<Record<string, any>>, prompter: Inquirerer, _options: CLIOptions) => Promise<Partial<Record<string, any>>>;
3
+ export default _default;
@@ -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
- workspacePath = project.workspacePath;
243
+ resolvedWorkspacePath = project.workspacePath;
243
244
  workspaceTypeName = 'PGPM';
244
245
  }
245
246
  else {
246
- workspacePath = resolveWorkspaceByType(ctx.cwd, workspaceType);
247
+ resolvedWorkspacePath = resolveWorkspaceByType(ctx.cwd, workspaceType);
247
248
  workspaceTypeName = workspaceType.toUpperCase();
248
249
  }
249
- if (!workspacePath) {
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
- // Only offer to create a workspace for pgpm templates
258
- if (workspaceType === 'pgpm') {
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
- // Use workspace-aware initModule
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 a workspace - scaffold directly to current directory
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.0.1",
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.1",
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": "b7b9bf9712a5d48619d6b19be6361251468e1bda"
77
+ "gitHead": "cb4843d3d80592f369e7f1a67b8a9223e6bfe48c"
78
78
  }