pgpm 3.1.0 → 3.2.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/README.md CHANGED
@@ -70,6 +70,8 @@ Here are some useful commands for reference:
70
70
 
71
71
  - `pgpm init` - Initialize a new module
72
72
  - `pgpm init workspace` - Initialize a new workspace
73
+ - `pgpm init --template <path>` - Initialize using a full template path (e.g., `pnpm/module`)
74
+ - `pgpm init -w` - Create a workspace first, then create the module inside it
73
75
 
74
76
  ### Development Setup
75
77
 
@@ -142,7 +144,7 @@ pgpm deploy --createdb
142
144
 
143
145
  ## 🧰 Templates, Caching, and Updates
144
146
 
145
- - `pgpm init` now scaffolds workspaces/modules from `https://github.com/constructive-io/pgpm-boilerplates.git` using `create-gen-app` with a one-week cache (stored under `~/.pgpm/cache/repos`). Override with `--repo`, `--from-branch`, and `--template-path`, or use a local template path.
147
+ - `pgpm init` now scaffolds workspaces/modules from `https://github.com/constructive-io/pgpm-boilerplates.git` using `create-gen-app` with a one-week cache (stored under `~/.pgpm/cache/repos`). Override with `--repo`, `--from-branch`, and `--template`, or use a local template path.
146
148
  - Run `pgpm cache clean` to wipe the cached boilerplates if you need a fresh pull.
147
149
  - The CLI performs a lightweight npm version check at most once per week (skipped in CI or when `PGPM_SKIP_UPDATE_CHECK` is set). Use `pgpm update` to upgrade to the latest release.
148
150
 
@@ -32,16 +32,20 @@ Options:
32
32
  --repo <repo> Template repo (default: https://github.com/constructive-io/pgpm-boilerplates.git)
33
33
  --from-branch <branch> Branch/tag to use when cloning repo
34
34
  --dir <variant> Template variant directory (e.g., supabase, drizzle)
35
+ --template, -t <path> Full template path (e.g., pnpm/module) - combines dir and fromPath
35
36
  --boilerplate Prompt to select from available boilerplates
37
+ --create-workspace, -w Create a workspace first, then create the module inside it
36
38
 
37
39
  Examples:
38
40
  ${binaryName} init Initialize new module (default)
39
41
  ${binaryName} init workspace Initialize new workspace
40
42
  ${binaryName} init module Initialize new module explicitly
41
43
  ${binaryName} init workspace --dir <variant> Use variant templates
44
+ ${binaryName} init --template pnpm/module Use full template path (dir + type)
42
45
  ${binaryName} init --boilerplate Select from available boilerplates
43
46
  ${binaryName} init --repo owner/repo Use templates from GitHub repository
44
47
  ${binaryName} init --repo owner/repo --from-branch develop Use specific branch
48
+ ${binaryName} init --dir pnpm -w Create pnpm workspace + module in one command
45
49
  `;
46
50
  };
47
51
  exports.createInitUsageText = createInitUsageText;
@@ -57,11 +61,28 @@ async function handleInit(argv, prompter) {
57
61
  const { cwd = process.cwd() } = argv;
58
62
  const templateRepo = argv.repo ?? core_1.DEFAULT_TEMPLATE_REPO;
59
63
  const branch = argv.fromBranch;
60
- const dir = argv.dir;
61
64
  const noTty = Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true');
62
65
  const useBoilerplatePrompt = Boolean(argv.boilerplate);
66
+ const createWorkspace = Boolean(argv.createWorkspace || argv['create-workspace'] || argv.w);
63
67
  // Get fromPath from first positional arg
64
68
  const positionalFromPath = argv._?.[0];
69
+ // Handle --template flag: parses "dir/fromPath" format and extracts both components
70
+ // When --template is provided, it takes precedence over --dir and positional fromPath
71
+ const templateArg = argv.template;
72
+ let dir = argv.dir;
73
+ let templateFromPath;
74
+ if (templateArg) {
75
+ // Parse template path like "pnpm/module" into dir="pnpm" and fromPath="module"
76
+ const slashIndex = templateArg.indexOf('/');
77
+ if (slashIndex > 0) {
78
+ dir = templateArg.substring(0, slashIndex);
79
+ templateFromPath = templateArg.substring(slashIndex + 1);
80
+ }
81
+ else {
82
+ // No slash - treat the whole thing as fromPath (e.g., --template workspace)
83
+ templateFromPath = templateArg;
84
+ }
85
+ }
65
86
  // Handle --boilerplate flag: separate path from regular init
66
87
  if (useBoilerplatePrompt) {
67
88
  return handleBoilerplateInit(argv, prompter, {
@@ -73,10 +94,10 @@ async function handleInit(argv, prompter) {
73
94
  cwd,
74
95
  });
75
96
  }
76
- // Regular init path: default to 'module' if no fromPath provided
77
- const fromPath = positionalFromPath || 'module';
78
- // Track if user explicitly requested module (e.g., `pgpm init module`)
79
- const wasExplicitModuleRequest = positionalFromPath === 'module';
97
+ // Regular init path: --template takes precedence, then positional arg, then default to 'module'
98
+ const fromPath = templateFromPath || positionalFromPath || 'module';
99
+ // Track if user explicitly requested module (e.g., `pgpm init module` or `--template pnpm/module`)
100
+ const wasExplicitModuleRequest = positionalFromPath === 'module' || templateFromPath === 'module';
80
101
  // Inspect the template to get its type
81
102
  const inspection = (0, core_1.inspectTemplate)({
82
103
  fromPath,
@@ -107,6 +128,7 @@ async function handleInit(argv, prompter) {
107
128
  noTty,
108
129
  cwd,
109
130
  requiresWorkspace: inspection.config?.requiresWorkspace,
131
+ createWorkspace,
110
132
  }, wasExplicitModuleRequest);
111
133
  }
112
134
  async function handleBoilerplateInit(argv, prompter, ctx) {
@@ -235,58 +257,130 @@ async function handleWorkspaceInit(argv, prompter, ctx) {
235
257
  process.stdout.write(`\n✨ Enjoy!\n\ncd ./${dirName}\n`);
236
258
  return { ...argv, ...answers, cwd: targetPath };
237
259
  }
260
+ function resolveWorkspaceTemplateRepo(options) {
261
+ const { templateRepo, branch, dir, workspaceType, cwd } = options;
262
+ // Determine the dir to use for workspace template
263
+ // If dir is specified, use it; otherwise use the workspaceType as the dir variant
264
+ const workspaceDir = dir || workspaceType;
265
+ // Try to find workspace template in the specified repo
266
+ try {
267
+ const inspection = (0, core_1.inspectTemplate)({
268
+ fromPath: 'workspace',
269
+ templateRepo,
270
+ branch,
271
+ dir: workspaceDir,
272
+ toolName: core_1.DEFAULT_TEMPLATE_TOOL_NAME,
273
+ cwd,
274
+ });
275
+ // If we found a valid workspace template, use the specified repo
276
+ if (inspection.config?.type === 'workspace') {
277
+ return {
278
+ repo: templateRepo,
279
+ branch,
280
+ dir: workspaceDir,
281
+ };
282
+ }
283
+ }
284
+ catch {
285
+ // Template not found in specified repo, fall through to default
286
+ }
287
+ // Fall back to default template repo
288
+ // Use the workspaceType as the dir variant (pgpm or pnpm)
289
+ return {
290
+ repo: core_1.DEFAULT_TEMPLATE_REPO,
291
+ branch: undefined, // Use default branch for default repo
292
+ dir: workspaceType,
293
+ };
294
+ }
238
295
  async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest = false) {
239
296
  // Determine workspace requirement (defaults to 'pgpm' for backward compatibility)
240
297
  const workspaceType = ctx.requiresWorkspace ?? 'pgpm';
241
298
  // Whether this is a pgpm-managed template (creates pgpm.plan, .control files)
242
299
  const isPgpmTemplate = workspaceType === 'pgpm';
243
- const project = new core_1.PgpmPackage(ctx.cwd);
300
+ let project = new core_1.PgpmPackage(ctx.cwd);
301
+ // Track resolved workspace path for both pgpm and non-pgpm workspace types
302
+ let resolvedWorkspacePath;
303
+ let workspaceTypeName = '';
244
304
  // Check workspace requirement based on type (skip if workspaceType is false)
245
305
  if (workspaceType !== false) {
246
- let workspacePath;
247
- let workspaceTypeName = '';
248
306
  if (workspaceType === 'pgpm') {
249
- workspacePath = project.workspacePath;
307
+ resolvedWorkspacePath = project.workspacePath;
250
308
  workspaceTypeName = 'PGPM';
251
309
  }
252
310
  else {
253
- workspacePath = (0, env_1.resolveWorkspaceByType)(ctx.cwd, workspaceType);
311
+ resolvedWorkspacePath = (0, env_1.resolveWorkspaceByType)(ctx.cwd, workspaceType);
254
312
  workspaceTypeName = workspaceType.toUpperCase();
255
313
  }
256
- if (!workspacePath) {
314
+ if (!resolvedWorkspacePath) {
257
315
  const noTty = Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true');
258
- // If user explicitly requested module init or we're in non-interactive mode,
259
- // just show the error with helpful guidance
260
- if (wasExplicitModuleRequest || noTty) {
261
- process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
262
- throw types_1.errors.NOT_IN_WORKSPACE({});
316
+ // Handle --create-workspace flag: create workspace first, then module
317
+ if (ctx.createWorkspace && (workspaceType === 'pgpm' || workspaceType === 'pnpm')) {
318
+ // Resolve workspace template repo with fallback to default
319
+ const workspaceTemplateConfig = resolveWorkspaceTemplateRepo({
320
+ templateRepo: ctx.templateRepo,
321
+ branch: ctx.branch,
322
+ dir: ctx.dir,
323
+ workspaceType,
324
+ cwd: ctx.cwd,
325
+ });
326
+ // Create workspace first
327
+ const workspaceResult = await handleWorkspaceInit(argv, prompter, {
328
+ fromPath: 'workspace',
329
+ templateRepo: workspaceTemplateConfig.repo,
330
+ branch: workspaceTemplateConfig.branch,
331
+ dir: workspaceTemplateConfig.dir,
332
+ noTty: ctx.noTty,
333
+ cwd: ctx.cwd,
334
+ });
335
+ // Update context to point to new workspace and continue with module creation
336
+ const newCwd = workspaceResult.cwd;
337
+ project = new core_1.PgpmPackage(newCwd);
338
+ // Re-resolve workspace path with new cwd
339
+ if (workspaceType === 'pgpm') {
340
+ resolvedWorkspacePath = project.workspacePath;
341
+ }
342
+ else {
343
+ resolvedWorkspacePath = (0, env_1.resolveWorkspaceByType)(newCwd, workspaceType);
344
+ }
345
+ // Update ctx for module creation
346
+ ctx.cwd = newCwd;
347
+ // Continue to module creation below (don't return here)
263
348
  }
264
- // Only offer to create a workspace for pgpm templates
265
- if (workspaceType === 'pgpm') {
266
- const recoveryQuestion = [
267
- {
268
- name: 'workspace',
269
- alias: 'w',
270
- message: `You are not inside a ${workspaceTypeName} workspace. Would you like to create a new workspace instead?`,
271
- type: 'confirm',
272
- required: true,
273
- },
274
- ];
275
- const { workspace } = await prompter.prompt(argv, recoveryQuestion);
276
- if (workspace) {
277
- return handleWorkspaceInit(argv, prompter, {
278
- fromPath: 'workspace',
279
- templateRepo: ctx.templateRepo,
280
- branch: ctx.branch,
281
- dir: ctx.dir,
282
- noTty: ctx.noTty,
283
- cwd: ctx.cwd,
284
- });
349
+ else {
350
+ // If user explicitly requested module init or we're in non-interactive mode,
351
+ // just show the error with helpful guidance
352
+ if (wasExplicitModuleRequest || noTty) {
353
+ process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
354
+ throw types_1.errors.NOT_IN_WORKSPACE({});
355
+ }
356
+ // Offer to create a workspace for pgpm templates or when --dir is specified
357
+ // (when --dir is specified, we know which workspace variant to use)
358
+ if (workspaceType === 'pgpm' || ctx.dir) {
359
+ const recoveryQuestion = [
360
+ {
361
+ name: 'workspace',
362
+ alias: 'W',
363
+ message: `You are not inside a ${workspaceTypeName} workspace. Would you like to create a new workspace instead?`,
364
+ type: 'confirm',
365
+ required: true,
366
+ },
367
+ ];
368
+ const { workspace } = await prompter.prompt(argv, recoveryQuestion);
369
+ if (workspace) {
370
+ return handleWorkspaceInit(argv, prompter, {
371
+ fromPath: 'workspace',
372
+ templateRepo: ctx.templateRepo,
373
+ branch: ctx.branch,
374
+ dir: ctx.dir,
375
+ noTty: ctx.noTty,
376
+ cwd: ctx.cwd,
377
+ });
378
+ }
285
379
  }
380
+ // User declined or non-pgpm workspace type without --dir, show the error
381
+ process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
382
+ throw types_1.errors.NOT_IN_WORKSPACE({});
286
383
  }
287
- // User declined or non-pgpm workspace type, show the error
288
- process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
289
- throw types_1.errors.NOT_IN_WORKSPACE({});
290
384
  }
291
385
  }
292
386
  // Only check workspace directory constraints if we're in a workspace
@@ -334,7 +428,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
334
428
  // Determine output path based on whether we're in a workspace
335
429
  let modulePath;
336
430
  if (project.workspacePath) {
337
- // Use workspace-aware initModule
431
+ // PGPM workspace - use workspace-aware initModule
338
432
  await project.initModule({
339
433
  name: modName,
340
434
  description: answers.description || modName,
@@ -354,8 +448,28 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
354
448
  ? path_1.default.join(ctx.cwd, 'packages', modName)
355
449
  : path_1.default.join(ctx.cwd, modName);
356
450
  }
451
+ else if (resolvedWorkspacePath && workspaceType !== false) {
452
+ // Non-pgpm workspace (pnpm, lerna, npm) - scaffold to packages/ directory
453
+ const isRoot = path_1.default.resolve(resolvedWorkspacePath) === path_1.default.resolve(ctx.cwd);
454
+ modulePath = isRoot
455
+ ? path_1.default.join(ctx.cwd, 'packages', modName)
456
+ : path_1.default.join(ctx.cwd, modName);
457
+ fs_1.default.mkdirSync(modulePath, { recursive: true });
458
+ await (0, core_1.scaffoldTemplate)({
459
+ fromPath: ctx.fromPath,
460
+ outputDir: modulePath,
461
+ templateRepo: ctx.templateRepo,
462
+ branch: ctx.branch,
463
+ dir: ctx.dir,
464
+ answers: templateAnswers,
465
+ noTty: ctx.noTty,
466
+ toolName: core_1.DEFAULT_TEMPLATE_TOOL_NAME,
467
+ cwd: ctx.cwd,
468
+ prompter
469
+ });
470
+ }
357
471
  else {
358
- // Not in a workspace - scaffold directly to current directory
472
+ // Not in any workspace (requiresWorkspace: false) - scaffold to current directory
359
473
  modulePath = path_1.default.join(ctx.cwd, modName);
360
474
  fs_1.default.mkdirSync(modulePath, { recursive: true });
361
475
  await (0, core_1.scaffoldTemplate)({
@@ -29,15 +29,16 @@ async function runWorkspaceSetup(argv, prompter) {
29
29
  // Prevent double-echoed keystrokes by closing our prompter before template prompts.
30
30
  prompter.close();
31
31
  const templateRepo = argv.repo ?? core_1.DEFAULT_TEMPLATE_REPO;
32
- // Don't set default templatePath - let scaffoldTemplate use metadata-driven resolution
33
- const templatePath = argv.templatePath;
32
+ // Don't set default template - let scaffoldTemplate use metadata-driven resolution
33
+ // Support both --template (new) and --template-path (deprecated) for backward compatibility
34
+ const template = (argv.template || argv.templatePath);
34
35
  // Register workspace.dirname resolver so boilerplate templates can use it via defaultFrom/setFrom
35
36
  // This provides the intended workspace directory name before the folder is created
36
37
  const dirName = path_1.default.basename(targetPath);
37
38
  (0, inquirerer_1.registerDefaultResolver)('workspace.dirname', () => dirName);
38
39
  const dir = argv.dir;
39
40
  await (0, core_1.scaffoldTemplate)({
40
- fromPath: templatePath ?? 'workspace',
41
+ fromPath: template ?? 'workspace',
41
42
  outputDir: targetPath,
42
43
  templateRepo,
43
44
  branch: argv.fromBranch,
@@ -26,16 +26,20 @@ Options:
26
26
  --repo <repo> Template repo (default: https://github.com/constructive-io/pgpm-boilerplates.git)
27
27
  --from-branch <branch> Branch/tag to use when cloning repo
28
28
  --dir <variant> Template variant directory (e.g., supabase, drizzle)
29
+ --template, -t <path> Full template path (e.g., pnpm/module) - combines dir and fromPath
29
30
  --boilerplate Prompt to select from available boilerplates
31
+ --create-workspace, -w Create a workspace first, then create the module inside it
30
32
 
31
33
  Examples:
32
34
  ${binaryName} init Initialize new module (default)
33
35
  ${binaryName} init workspace Initialize new workspace
34
36
  ${binaryName} init module Initialize new module explicitly
35
37
  ${binaryName} init workspace --dir <variant> Use variant templates
38
+ ${binaryName} init --template pnpm/module Use full template path (dir + type)
36
39
  ${binaryName} init --boilerplate Select from available boilerplates
37
40
  ${binaryName} init --repo owner/repo Use templates from GitHub repository
38
41
  ${binaryName} init --repo owner/repo --from-branch develop Use specific branch
42
+ ${binaryName} init --dir pnpm -w Create pnpm workspace + module in one command
39
43
  `;
40
44
  };
41
45
  export default async (argv, prompter, _options) => {
@@ -50,11 +54,28 @@ async function handleInit(argv, prompter) {
50
54
  const { cwd = process.cwd() } = argv;
51
55
  const templateRepo = argv.repo ?? DEFAULT_TEMPLATE_REPO;
52
56
  const branch = argv.fromBranch;
53
- const dir = argv.dir;
54
57
  const noTty = Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true');
55
58
  const useBoilerplatePrompt = Boolean(argv.boilerplate);
59
+ const createWorkspace = Boolean(argv.createWorkspace || argv['create-workspace'] || argv.w);
56
60
  // Get fromPath from first positional arg
57
61
  const positionalFromPath = argv._?.[0];
62
+ // Handle --template flag: parses "dir/fromPath" format and extracts both components
63
+ // When --template is provided, it takes precedence over --dir and positional fromPath
64
+ const templateArg = argv.template;
65
+ let dir = argv.dir;
66
+ let templateFromPath;
67
+ if (templateArg) {
68
+ // Parse template path like "pnpm/module" into dir="pnpm" and fromPath="module"
69
+ const slashIndex = templateArg.indexOf('/');
70
+ if (slashIndex > 0) {
71
+ dir = templateArg.substring(0, slashIndex);
72
+ templateFromPath = templateArg.substring(slashIndex + 1);
73
+ }
74
+ else {
75
+ // No slash - treat the whole thing as fromPath (e.g., --template workspace)
76
+ templateFromPath = templateArg;
77
+ }
78
+ }
58
79
  // Handle --boilerplate flag: separate path from regular init
59
80
  if (useBoilerplatePrompt) {
60
81
  return handleBoilerplateInit(argv, prompter, {
@@ -66,10 +87,10 @@ async function handleInit(argv, prompter) {
66
87
  cwd,
67
88
  });
68
89
  }
69
- // Regular init path: default to 'module' if no fromPath provided
70
- const fromPath = positionalFromPath || 'module';
71
- // Track if user explicitly requested module (e.g., `pgpm init module`)
72
- const wasExplicitModuleRequest = positionalFromPath === 'module';
90
+ // Regular init path: --template takes precedence, then positional arg, then default to 'module'
91
+ const fromPath = templateFromPath || positionalFromPath || 'module';
92
+ // Track if user explicitly requested module (e.g., `pgpm init module` or `--template pnpm/module`)
93
+ const wasExplicitModuleRequest = positionalFromPath === 'module' || templateFromPath === 'module';
73
94
  // Inspect the template to get its type
74
95
  const inspection = inspectTemplate({
75
96
  fromPath,
@@ -100,6 +121,7 @@ async function handleInit(argv, prompter) {
100
121
  noTty,
101
122
  cwd,
102
123
  requiresWorkspace: inspection.config?.requiresWorkspace,
124
+ createWorkspace,
103
125
  }, wasExplicitModuleRequest);
104
126
  }
105
127
  async function handleBoilerplateInit(argv, prompter, ctx) {
@@ -228,58 +250,130 @@ async function handleWorkspaceInit(argv, prompter, ctx) {
228
250
  process.stdout.write(`\n✨ Enjoy!\n\ncd ./${dirName}\n`);
229
251
  return { ...argv, ...answers, cwd: targetPath };
230
252
  }
253
+ function resolveWorkspaceTemplateRepo(options) {
254
+ const { templateRepo, branch, dir, workspaceType, cwd } = options;
255
+ // Determine the dir to use for workspace template
256
+ // If dir is specified, use it; otherwise use the workspaceType as the dir variant
257
+ const workspaceDir = dir || workspaceType;
258
+ // Try to find workspace template in the specified repo
259
+ try {
260
+ const inspection = inspectTemplate({
261
+ fromPath: 'workspace',
262
+ templateRepo,
263
+ branch,
264
+ dir: workspaceDir,
265
+ toolName: DEFAULT_TEMPLATE_TOOL_NAME,
266
+ cwd,
267
+ });
268
+ // If we found a valid workspace template, use the specified repo
269
+ if (inspection.config?.type === 'workspace') {
270
+ return {
271
+ repo: templateRepo,
272
+ branch,
273
+ dir: workspaceDir,
274
+ };
275
+ }
276
+ }
277
+ catch {
278
+ // Template not found in specified repo, fall through to default
279
+ }
280
+ // Fall back to default template repo
281
+ // Use the workspaceType as the dir variant (pgpm or pnpm)
282
+ return {
283
+ repo: DEFAULT_TEMPLATE_REPO,
284
+ branch: undefined, // Use default branch for default repo
285
+ dir: workspaceType,
286
+ };
287
+ }
231
288
  async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest = false) {
232
289
  // Determine workspace requirement (defaults to 'pgpm' for backward compatibility)
233
290
  const workspaceType = ctx.requiresWorkspace ?? 'pgpm';
234
291
  // Whether this is a pgpm-managed template (creates pgpm.plan, .control files)
235
292
  const isPgpmTemplate = workspaceType === 'pgpm';
236
- const project = new PgpmPackage(ctx.cwd);
293
+ let project = new PgpmPackage(ctx.cwd);
294
+ // Track resolved workspace path for both pgpm and non-pgpm workspace types
295
+ let resolvedWorkspacePath;
296
+ let workspaceTypeName = '';
237
297
  // Check workspace requirement based on type (skip if workspaceType is false)
238
298
  if (workspaceType !== false) {
239
- let workspacePath;
240
- let workspaceTypeName = '';
241
299
  if (workspaceType === 'pgpm') {
242
- workspacePath = project.workspacePath;
300
+ resolvedWorkspacePath = project.workspacePath;
243
301
  workspaceTypeName = 'PGPM';
244
302
  }
245
303
  else {
246
- workspacePath = resolveWorkspaceByType(ctx.cwd, workspaceType);
304
+ resolvedWorkspacePath = resolveWorkspaceByType(ctx.cwd, workspaceType);
247
305
  workspaceTypeName = workspaceType.toUpperCase();
248
306
  }
249
- if (!workspacePath) {
307
+ if (!resolvedWorkspacePath) {
250
308
  const noTty = Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true');
251
- // If user explicitly requested module init or we're in non-interactive mode,
252
- // just show the error with helpful guidance
253
- if (wasExplicitModuleRequest || noTty) {
254
- process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
255
- throw errors.NOT_IN_WORKSPACE({});
309
+ // Handle --create-workspace flag: create workspace first, then module
310
+ if (ctx.createWorkspace && (workspaceType === 'pgpm' || workspaceType === 'pnpm')) {
311
+ // Resolve workspace template repo with fallback to default
312
+ const workspaceTemplateConfig = resolveWorkspaceTemplateRepo({
313
+ templateRepo: ctx.templateRepo,
314
+ branch: ctx.branch,
315
+ dir: ctx.dir,
316
+ workspaceType,
317
+ cwd: ctx.cwd,
318
+ });
319
+ // Create workspace first
320
+ const workspaceResult = await handleWorkspaceInit(argv, prompter, {
321
+ fromPath: 'workspace',
322
+ templateRepo: workspaceTemplateConfig.repo,
323
+ branch: workspaceTemplateConfig.branch,
324
+ dir: workspaceTemplateConfig.dir,
325
+ noTty: ctx.noTty,
326
+ cwd: ctx.cwd,
327
+ });
328
+ // Update context to point to new workspace and continue with module creation
329
+ const newCwd = workspaceResult.cwd;
330
+ project = new PgpmPackage(newCwd);
331
+ // Re-resolve workspace path with new cwd
332
+ if (workspaceType === 'pgpm') {
333
+ resolvedWorkspacePath = project.workspacePath;
334
+ }
335
+ else {
336
+ resolvedWorkspacePath = resolveWorkspaceByType(newCwd, workspaceType);
337
+ }
338
+ // Update ctx for module creation
339
+ ctx.cwd = newCwd;
340
+ // Continue to module creation below (don't return here)
256
341
  }
257
- // Only offer to create a workspace for pgpm templates
258
- if (workspaceType === 'pgpm') {
259
- const recoveryQuestion = [
260
- {
261
- name: 'workspace',
262
- alias: 'w',
263
- message: `You are not inside a ${workspaceTypeName} workspace. Would you like to create a new workspace instead?`,
264
- type: 'confirm',
265
- required: true,
266
- },
267
- ];
268
- const { workspace } = await prompter.prompt(argv, recoveryQuestion);
269
- if (workspace) {
270
- return handleWorkspaceInit(argv, prompter, {
271
- fromPath: 'workspace',
272
- templateRepo: ctx.templateRepo,
273
- branch: ctx.branch,
274
- dir: ctx.dir,
275
- noTty: ctx.noTty,
276
- cwd: ctx.cwd,
277
- });
342
+ else {
343
+ // If user explicitly requested module init or we're in non-interactive mode,
344
+ // just show the error with helpful guidance
345
+ if (wasExplicitModuleRequest || noTty) {
346
+ process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
347
+ throw errors.NOT_IN_WORKSPACE({});
348
+ }
349
+ // Offer to create a workspace for pgpm templates or when --dir is specified
350
+ // (when --dir is specified, we know which workspace variant to use)
351
+ if (workspaceType === 'pgpm' || ctx.dir) {
352
+ const recoveryQuestion = [
353
+ {
354
+ name: 'workspace',
355
+ alias: 'W',
356
+ message: `You are not inside a ${workspaceTypeName} workspace. Would you like to create a new workspace instead?`,
357
+ type: 'confirm',
358
+ required: true,
359
+ },
360
+ ];
361
+ const { workspace } = await prompter.prompt(argv, recoveryQuestion);
362
+ if (workspace) {
363
+ return handleWorkspaceInit(argv, prompter, {
364
+ fromPath: 'workspace',
365
+ templateRepo: ctx.templateRepo,
366
+ branch: ctx.branch,
367
+ dir: ctx.dir,
368
+ noTty: ctx.noTty,
369
+ cwd: ctx.cwd,
370
+ });
371
+ }
278
372
  }
373
+ // User declined or non-pgpm workspace type without --dir, show the error
374
+ process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
375
+ throw errors.NOT_IN_WORKSPACE({});
279
376
  }
280
- // User declined or non-pgpm workspace type, show the error
281
- process.stderr.write(`Not inside a ${workspaceTypeName} workspace.\n`);
282
- throw errors.NOT_IN_WORKSPACE({});
283
377
  }
284
378
  }
285
379
  // Only check workspace directory constraints if we're in a workspace
@@ -327,7 +421,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
327
421
  // Determine output path based on whether we're in a workspace
328
422
  let modulePath;
329
423
  if (project.workspacePath) {
330
- // Use workspace-aware initModule
424
+ // PGPM workspace - use workspace-aware initModule
331
425
  await project.initModule({
332
426
  name: modName,
333
427
  description: answers.description || modName,
@@ -347,8 +441,28 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
347
441
  ? path.join(ctx.cwd, 'packages', modName)
348
442
  : path.join(ctx.cwd, modName);
349
443
  }
444
+ else if (resolvedWorkspacePath && workspaceType !== false) {
445
+ // Non-pgpm workspace (pnpm, lerna, npm) - scaffold to packages/ directory
446
+ const isRoot = path.resolve(resolvedWorkspacePath) === path.resolve(ctx.cwd);
447
+ modulePath = isRoot
448
+ ? path.join(ctx.cwd, 'packages', modName)
449
+ : path.join(ctx.cwd, modName);
450
+ fs.mkdirSync(modulePath, { recursive: true });
451
+ await scaffoldTemplate({
452
+ fromPath: ctx.fromPath,
453
+ outputDir: modulePath,
454
+ templateRepo: ctx.templateRepo,
455
+ branch: ctx.branch,
456
+ dir: ctx.dir,
457
+ answers: templateAnswers,
458
+ noTty: ctx.noTty,
459
+ toolName: DEFAULT_TEMPLATE_TOOL_NAME,
460
+ cwd: ctx.cwd,
461
+ prompter
462
+ });
463
+ }
350
464
  else {
351
- // Not in a workspace - scaffold directly to current directory
465
+ // Not in any workspace (requiresWorkspace: false) - scaffold to current directory
352
466
  modulePath = path.join(ctx.cwd, modName);
353
467
  fs.mkdirSync(modulePath, { recursive: true });
354
468
  await scaffoldTemplate({
@@ -23,15 +23,16 @@ export default async function runWorkspaceSetup(argv, prompter) {
23
23
  // Prevent double-echoed keystrokes by closing our prompter before template prompts.
24
24
  prompter.close();
25
25
  const templateRepo = argv.repo ?? DEFAULT_TEMPLATE_REPO;
26
- // Don't set default templatePath - let scaffoldTemplate use metadata-driven resolution
27
- const templatePath = argv.templatePath;
26
+ // Don't set default template - let scaffoldTemplate use metadata-driven resolution
27
+ // Support both --template (new) and --template-path (deprecated) for backward compatibility
28
+ const template = (argv.template || argv.templatePath);
28
29
  // Register workspace.dirname resolver so boilerplate templates can use it via defaultFrom/setFrom
29
30
  // This provides the intended workspace directory name before the folder is created
30
31
  const dirName = path.basename(targetPath);
31
32
  registerDefaultResolver('workspace.dirname', () => dirName);
32
33
  const dir = argv.dir;
33
34
  await scaffoldTemplate({
34
- fromPath: templatePath ?? 'workspace',
35
+ fromPath: template ?? 'workspace',
35
36
  outputDir: targetPath,
36
37
  templateRepo,
37
38
  branch: argv.fromBranch,
package/esm/index.js CHANGED
@@ -32,7 +32,12 @@ export const options = {
32
32
  v: 'version',
33
33
  h: 'help',
34
34
  'from-branch': 'fromBranch',
35
- 'template-path': 'templatePath'
35
+ // Support both --template and --template-path (deprecated) for backward compatibility
36
+ 'template-path': 'template',
37
+ t: 'template',
38
+ // -w for --create-workspace flag
39
+ w: 'createWorkspace',
40
+ 'create-workspace': 'createWorkspace'
36
41
  }
37
42
  }
38
43
  };
package/index.js CHANGED
@@ -75,7 +75,12 @@ exports.options = {
75
75
  v: 'version',
76
76
  h: 'help',
77
77
  'from-branch': 'fromBranch',
78
- 'template-path': 'templatePath'
78
+ // Support both --template and --template-path (deprecated) for backward compatibility
79
+ 'template-path': 'template',
80
+ t: 'template',
81
+ // -w for --create-workspace flag
82
+ w: 'createWorkspace',
83
+ 'create-workspace': 'createWorkspace'
79
84
  }
80
85
  }
81
86
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgpm",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PostgreSQL Package Manager - Database migration and package management CLI",
6
6
  "main": "index.js",
@@ -74,5 +74,5 @@
74
74
  "pg",
75
75
  "pgsql"
76
76
  ],
77
- "gitHead": "20d7e992348542531e73f20c0fd084decec86325"
77
+ "gitHead": "10d6dc9d8f3846c7347272874537702949422a64"
78
78
  }