pgpm 4.35.0 → 4.36.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.
@@ -6,15 +6,39 @@ Extension Command:
6
6
 
7
7
  pgpm extension [OPTIONS]
8
8
 
9
- Manage module dependencies.
9
+ Manage the extensions / module dependencies of the current module
10
+ (the \`requires\` line of its .control file).
10
11
 
11
12
  Options:
12
13
  --help, -h Show this help message
13
14
  --cwd <directory> Working directory (default: current directory)
15
+ --add <a,b> Add one or more dependencies (non-interactive)
16
+ --remove <a,b> Remove one or more dependencies (non-interactive)
17
+ --set <a,b> Replace all dependencies with this exact set (non-interactive)
14
18
 
15
19
  Examples:
16
- pgpm extension Manage dependencies for current module
20
+ pgpm extension Manage dependencies interactively
21
+ pgpm extension --add uuid-ossp Add a dependency
22
+ pgpm extension --add pgcrypto,citext Add several dependencies
23
+ pgpm extension --remove uuid-ossp Remove a dependency
24
+ pgpm extension --set plpgsql Replace the dependency set
17
25
  `;
26
+ /**
27
+ * Parse a flag that may be a comma-separated string, a single value, or an
28
+ * array (repeated flags) into a clean string[].
29
+ */
30
+ const parseList = (value) => {
31
+ if (Array.isArray(value)) {
32
+ return value.flatMap((v) => parseList(v));
33
+ }
34
+ if (typeof value === 'string') {
35
+ return value
36
+ .split(',')
37
+ .map((s) => s.trim())
38
+ .filter(Boolean);
39
+ }
40
+ return [];
41
+ };
18
42
  exports.default = async (argv, prompter, _options) => {
19
43
  // Show usage if explicitly requested
20
44
  if (argv.help || argv.h) {
@@ -26,14 +50,31 @@ exports.default = async (argv, prompter, _options) => {
26
50
  if (!project.isInModule()) {
27
51
  throw new Error('You must run this command inside a PGPM module.');
28
52
  }
29
- const info = project.getModuleInfo();
30
53
  const installed = project.getRequiredModules();
54
+ // Non-interactive mode: --set / --add / --remove let CI and scripts change
55
+ // dependencies without a prompt. --set replaces the whole set; --add/--remove
56
+ // mutate the current set.
57
+ const setProvided = argv.set !== undefined;
58
+ const addList = parseList(argv.add);
59
+ const removeList = parseList(argv.remove);
60
+ if (setProvided || addList.length > 0 || removeList.length > 0) {
61
+ const base = setProvided ? parseList(argv.set) : [...installed];
62
+ const withAdds = [...base];
63
+ for (const ext of addList) {
64
+ if (!withAdds.includes(ext))
65
+ withAdds.push(ext);
66
+ }
67
+ const next = withAdds.filter((ext) => !removeList.includes(ext));
68
+ project.setModuleDependencies(next);
69
+ return;
70
+ }
71
+ const info = project.getModuleInfo();
31
72
  const available = await project.getAvailableModules();
32
- const filtered = available.filter(name => name !== info.extname);
73
+ const filtered = available.filter((name) => name !== info.extname);
33
74
  const questions = [
34
75
  {
35
76
  name: 'extensions',
36
- message: 'Which modules does this one depend on?',
77
+ message: 'Which extensions / modules does this one depend on?',
37
78
  type: 'checkbox',
38
79
  allowCustomOptions: true,
39
80
  options: filtered,
@@ -42,7 +83,7 @@ exports.default = async (argv, prompter, _options) => {
42
83
  ];
43
84
  const answers = await prompter.prompt(argv, questions);
44
85
  const selected = answers.extensions
45
- .filter(opt => opt.selected)
46
- .map(opt => opt.name);
86
+ .filter((opt) => opt.selected)
87
+ .map((opt) => opt.name);
47
88
  project.setModuleDependencies(selected);
48
89
  };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A template/boilerplate source, recorded on a workspace so that modules
3
+ * created inside it inherit the same repo without re-specifying flags.
4
+ */
5
+ export interface BoilerplateSource {
6
+ repo: string;
7
+ branch?: string;
8
+ dir?: string;
9
+ }
10
+ /**
11
+ * Resolve the template repo for an `init` invocation from its flags.
12
+ *
13
+ * Precedence: explicit `--repo` wins, then `--pglite` (sugar for the pglite
14
+ * boilerplates repo), otherwise the default repo.
15
+ */
16
+ export declare function resolveInitTemplateRepo(flags: {
17
+ repo?: unknown;
18
+ pglite?: boolean;
19
+ }): {
20
+ templateRepo: string;
21
+ repoWasExplicit: boolean;
22
+ };
23
+ /**
24
+ * Record the boilerplate source on a freshly scaffolded workspace's `pgpm.json`
25
+ * so that `pgpm init` (module) inside it inherits the same template repo.
26
+ * Only writes into an existing pgpm.json (pgpm.config.js workspaces are skipped).
27
+ */
28
+ export declare function persistBoilerplateSource(workspaceDir: string, source: BoilerplateSource): void;
29
+ /**
30
+ * Read a previously recorded boilerplate source from the enclosing workspace's
31
+ * config (see {@link persistBoilerplateSource}).
32
+ */
33
+ export declare function readBoilerplateSource(cwd: string): BoilerplateSource | undefined;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveInitTemplateRepo = resolveInitTemplateRepo;
7
+ exports.persistBoilerplateSource = persistBoilerplateSource;
8
+ exports.readBoilerplateSource = readBoilerplateSource;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const core_1 = require("@pgpmjs/core");
12
+ /**
13
+ * Resolve the template repo for an `init` invocation from its flags.
14
+ *
15
+ * Precedence: explicit `--repo` wins, then `--pglite` (sugar for the pglite
16
+ * boilerplates repo), otherwise the default repo.
17
+ */
18
+ function resolveInitTemplateRepo(flags) {
19
+ const pglite = Boolean(flags.pglite);
20
+ const repoWasExplicit = typeof flags.repo === 'string' || pglite;
21
+ const templateRepo = typeof flags.repo === 'string'
22
+ ? flags.repo
23
+ : pglite
24
+ ? core_1.TEMPLATE_REPOS.pglite
25
+ : core_1.DEFAULT_TEMPLATE_REPO;
26
+ return { templateRepo, repoWasExplicit };
27
+ }
28
+ /**
29
+ * Record the boilerplate source on a freshly scaffolded workspace's `pgpm.json`
30
+ * so that `pgpm init` (module) inside it inherits the same template repo.
31
+ * Only writes into an existing pgpm.json (pgpm.config.js workspaces are skipped).
32
+ */
33
+ function persistBoilerplateSource(workspaceDir, source) {
34
+ const configPath = path_1.default.join(workspaceDir, 'pgpm.json');
35
+ if (!fs_1.default.existsSync(configPath))
36
+ return;
37
+ try {
38
+ const raw = fs_1.default.readFileSync(configPath, 'utf8');
39
+ const config = JSON.parse(raw);
40
+ config.boilerplates = {
41
+ repo: source.repo,
42
+ ...(source.branch ? { branch: source.branch } : {}),
43
+ ...(source.dir ? { dir: source.dir } : {}),
44
+ };
45
+ fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
46
+ }
47
+ catch {
48
+ // Non-fatal: inheritance is a convenience, not a correctness requirement.
49
+ }
50
+ }
51
+ /**
52
+ * Read a previously recorded boilerplate source from the enclosing workspace's
53
+ * config (see {@link persistBoilerplateSource}).
54
+ */
55
+ function readBoilerplateSource(cwd) {
56
+ const project = new core_1.PgpmPackage(cwd);
57
+ const recorded = project.config?.boilerplates;
58
+ if (recorded?.repo) {
59
+ return { repo: recorded.repo, branch: recorded.branch, dir: recorded.dir };
60
+ }
61
+ return undefined;
62
+ }
@@ -11,6 +11,7 @@ const core_1 = require("@pgpmjs/core");
11
11
  const env_1 = require("@pgpmjs/env");
12
12
  const types_1 = require("@pgpmjs/types");
13
13
  const inquirerer_1 = require("inquirerer");
14
+ const boilerplate_1 = require("./boilerplate");
14
15
  const DEFAULT_MOTD = `
15
16
  | _ _
16
17
  === |.===. '\\-//\`
@@ -31,23 +32,33 @@ Options:
31
32
  --help, -h Show this help message
32
33
  --cwd <directory> Working directory (default: current directory)
33
34
  --repo <repo> Template repo (default: https://github.com/constructive-io/pgpm-boilerplates.git)
35
+ --pglite Use the PGlite boilerplates (in-process WASM Postgres, no server/Docker).
36
+ Sugar for --repo https://github.com/constructive-io/pglite-boilerplates.git.
37
+ Recorded on the workspace so later \`init\` calls inherit it automatically.
34
38
  --from-branch <branch> Branch/tag to use when cloning repo
35
39
  --dir <variant> Template variant directory (e.g., supabase, drizzle)
36
40
  --template, -t <path> Full template path (e.g., pnpm/module) - combines dir and fromPath
37
41
  --boilerplate Prompt to select from available boilerplates
38
42
  --create-workspace, -w Create a workspace first, then create the module inside it
39
43
  --use-skills Use npx skills CLI for skill installation (slower, writes skills-lock.json)
44
+ --extensions <a,b> Extensions to require in the new module (default: none).
45
+ Add them later instead with \`${binaryName} extension\`.
46
+ --with-extensions Prompt interactively for extensions during module init
40
47
 
41
48
  Examples:
42
- ${binaryName} init Initialize new module (default)
49
+ ${binaryName} init Initialize new module (default, no extensions)
43
50
  ${binaryName} init workspace Initialize new workspace
44
51
  ${binaryName} init module Initialize new module explicitly
52
+ ${binaryName} init --extensions uuid-ossp,citext Initialize module requiring extensions
53
+ ${binaryName} init --with-extensions Pick extensions interactively
45
54
  ${binaryName} init workspace --dir <variant> Use variant templates
46
55
  ${binaryName} init --template pnpm/module Use full template path (dir + type)
47
56
  ${binaryName} init --boilerplate Select from available boilerplates
48
57
  ${binaryName} init --repo owner/repo Use templates from GitHub repository
49
58
  ${binaryName} init --repo owner/repo --from-branch develop Use specific branch
50
59
  ${binaryName} init --dir pnpm -w Create pnpm workspace + module in one command
60
+ ${binaryName} init workspace --pglite Create a PGlite (in-process) workspace
61
+ ${binaryName} init --pglite Create a PGlite module (inherited automatically inside a --pglite workspace)
51
62
  `;
52
63
  };
53
64
  exports.createInitUsageText = createInitUsageText;
@@ -61,7 +72,13 @@ exports.default = async (argv, prompter, _options) => {
61
72
  };
62
73
  async function handleInit(argv, prompter) {
63
74
  const { cwd = process.cwd() } = argv;
64
- const templateRepo = argv.repo ?? core_1.DEFAULT_TEMPLATE_REPO;
75
+ // `--pglite` is sugar for the pglite boilerplates repo; `--repo` overrides it.
76
+ // `repoWasExplicit` means a module created inside a workspace should NOT
77
+ // inherit the workspace's recorded repo (the user pinned one).
78
+ const { templateRepo, repoWasExplicit } = (0, boilerplate_1.resolveInitTemplateRepo)({
79
+ repo: argv.repo,
80
+ pglite: Boolean(argv.pglite),
81
+ });
65
82
  const branch = argv.fromBranch;
66
83
  const noTty = Boolean(argv.noTty || argv['no-tty'] || argv.tty === false || process.env.CI === 'true');
67
84
  const useBoilerplatePrompt = Boolean(argv.boilerplate);
@@ -122,6 +139,7 @@ async function handleInit(argv, prompter) {
122
139
  noTty,
123
140
  cwd,
124
141
  useNpxSkills,
142
+ repoWasExplicit,
125
143
  });
126
144
  }
127
145
  // Default to module init (for 'module' type, 'generic' type, or unknown types)
@@ -135,6 +153,7 @@ async function handleInit(argv, prompter) {
135
153
  requiresWorkspace: inspection.config?.requiresWorkspace,
136
154
  createWorkspace,
137
155
  useNpxSkills,
156
+ repoWasExplicit,
138
157
  }, wasExplicitModuleRequest);
139
158
  }
140
159
  async function handleBoilerplateInit(argv, prompter, ctx) {
@@ -305,6 +324,16 @@ async function handleWorkspaceInit(argv, prompter, ctx) {
305
324
  cwd: ctx.cwd,
306
325
  prompter
307
326
  });
327
+ // Record a non-default template source on the workspace so that modules
328
+ // created later inside it (`pgpm init`) inherit the same boilerplate repo
329
+ // without re-specifying `--pglite`/`--repo`.
330
+ if (ctx.templateRepo !== core_1.DEFAULT_TEMPLATE_REPO) {
331
+ (0, boilerplate_1.persistBoilerplateSource)(targetPath, {
332
+ repo: ctx.templateRepo,
333
+ branch: ctx.branch,
334
+ dir: ctx.dir,
335
+ });
336
+ }
308
337
  // Check for .motd file and print it, or use default ASCII art
309
338
  const motdPath = path_1.default.join(targetPath, '.motd');
310
339
  let motd = DEFAULT_MOTD;
@@ -339,9 +368,14 @@ async function handleWorkspaceInit(argv, prompter, ctx) {
339
368
  }
340
369
  function resolveWorkspaceTemplateRepo(options) {
341
370
  const { templateRepo, branch, dir, workspaceType, cwd } = options;
342
- // Determine the dir to use for workspace template
343
- // If dir is specified, use it; otherwise use the workspaceType as the dir variant
344
- const workspaceDir = dir || workspaceType;
371
+ // Determine the dir to use for workspace template.
372
+ // - Explicit --dir always wins.
373
+ // - For the default repo (which holds several families), fall back to the
374
+ // workspaceType (pgpm/pnpm) as the variant dir.
375
+ // - For a custom/single-family repo (e.g. --pglite), leave dir undefined so
376
+ // the repo's own .boilerplates.json resolves the family (e.g. `pglite/`).
377
+ const isDefaultRepo = templateRepo === core_1.DEFAULT_TEMPLATE_REPO;
378
+ const workspaceDir = dir || (isDefaultRepo ? workspaceType : undefined);
345
379
  // Try to find workspace template in the specified repo
346
380
  try {
347
381
  const inspection = (0, core_1.inspectTemplate)({
@@ -373,6 +407,17 @@ function resolveWorkspaceTemplateRepo(options) {
373
407
  };
374
408
  }
375
409
  async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest = false) {
410
+ // Inherit the boilerplate source recorded on the enclosing workspace (e.g. by
411
+ // `pgpm init workspace --pglite`) unless the user pinned one explicitly. This
412
+ // makes a bare `pgpm init` inside a pglite workspace scaffold pglite modules.
413
+ if (!ctx.repoWasExplicit) {
414
+ const inherited = (0, boilerplate_1.readBoilerplateSource)(ctx.cwd);
415
+ if (inherited) {
416
+ ctx.templateRepo = inherited.repo;
417
+ ctx.branch = inherited.branch;
418
+ ctx.dir = inherited.dir;
419
+ }
420
+ }
376
421
  // Determine workspace requirement (defaults to 'pgpm' for backward compatibility)
377
422
  const workspaceType = ctx.requiresWorkspace ?? 'pgpm';
378
423
  // Whether this is a pgpm-managed template (creates pgpm.plan, .control files)
@@ -411,6 +456,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
411
456
  dir: workspaceTemplateConfig.dir,
412
457
  noTty: ctx.noTty,
413
458
  cwd: ctx.cwd,
459
+ repoWasExplicit: ctx.repoWasExplicit,
414
460
  });
415
461
  // Update context to point to new workspace and continue with module creation
416
462
  const newCwd = workspaceResult.cwd;
@@ -454,6 +500,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
454
500
  dir: ctx.dir,
455
501
  noTty: ctx.noTty,
456
502
  cwd: ctx.cwd,
503
+ repoWasExplicit: ctx.repoWasExplicit,
457
504
  });
458
505
  }
459
506
  }
@@ -479,8 +526,24 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
479
526
  type: 'text',
480
527
  },
481
528
  ];
482
- // Only ask for extensions if this is a pgpm template
483
- if (isPgpmTemplate && project.workspacePath) {
529
+ // Extensions are opt-in: a bare `pgpm init` scaffolds a module with no
530
+ // extensions (no `requires` line). Extensions are added explicitly later via
531
+ // `pgpm extension`, or up front via `--extensions a,b` (non-interactive) or
532
+ // `--with-extensions` (interactive picker). This matches the zero-config init
533
+ // of comparable tools (sqitch, prisma, drizzle, dbmate, ...).
534
+ // Normalize `--extensions a,b` (a comma string from the CLI) into an array so
535
+ // it is consumed the same way as a programmatic string[].
536
+ if (typeof argv.extensions === 'string') {
537
+ argv.extensions = argv.extensions
538
+ .split(',')
539
+ .map((s) => s.trim())
540
+ .filter(Boolean);
541
+ }
542
+ const extensionsProvided = argv.extensions !== undefined;
543
+ const wantExtensionsPrompt = Boolean(argv.withExtensions || argv['with-extensions']);
544
+ if (isPgpmTemplate &&
545
+ project.workspacePath &&
546
+ (extensionsProvided || wantExtensionsPrompt)) {
484
547
  const availExtensions = await project.getAvailableModules();
485
548
  moduleQuestions.push({
486
549
  name: 'extensions',
@@ -488,8 +551,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
488
551
  options: availExtensions,
489
552
  type: 'checkbox',
490
553
  allowCustomOptions: true,
491
- required: true,
492
- default: ['plpgsql', 'uuid-ossp'],
554
+ default: [],
493
555
  });
494
556
  }
495
557
  const answers = await prompter.prompt(argv, moduleQuestions);
@@ -4,15 +4,39 @@ Extension Command:
4
4
 
5
5
  pgpm extension [OPTIONS]
6
6
 
7
- Manage module dependencies.
7
+ Manage the extensions / module dependencies of the current module
8
+ (the \`requires\` line of its .control file).
8
9
 
9
10
  Options:
10
11
  --help, -h Show this help message
11
12
  --cwd <directory> Working directory (default: current directory)
13
+ --add <a,b> Add one or more dependencies (non-interactive)
14
+ --remove <a,b> Remove one or more dependencies (non-interactive)
15
+ --set <a,b> Replace all dependencies with this exact set (non-interactive)
12
16
 
13
17
  Examples:
14
- pgpm extension Manage dependencies for current module
18
+ pgpm extension Manage dependencies interactively
19
+ pgpm extension --add uuid-ossp Add a dependency
20
+ pgpm extension --add pgcrypto,citext Add several dependencies
21
+ pgpm extension --remove uuid-ossp Remove a dependency
22
+ pgpm extension --set plpgsql Replace the dependency set
15
23
  `;
24
+ /**
25
+ * Parse a flag that may be a comma-separated string, a single value, or an
26
+ * array (repeated flags) into a clean string[].
27
+ */
28
+ const parseList = (value) => {
29
+ if (Array.isArray(value)) {
30
+ return value.flatMap((v) => parseList(v));
31
+ }
32
+ if (typeof value === 'string') {
33
+ return value
34
+ .split(',')
35
+ .map((s) => s.trim())
36
+ .filter(Boolean);
37
+ }
38
+ return [];
39
+ };
16
40
  export default async (argv, prompter, _options) => {
17
41
  // Show usage if explicitly requested
18
42
  if (argv.help || argv.h) {
@@ -24,14 +48,31 @@ export default async (argv, prompter, _options) => {
24
48
  if (!project.isInModule()) {
25
49
  throw new Error('You must run this command inside a PGPM module.');
26
50
  }
27
- const info = project.getModuleInfo();
28
51
  const installed = project.getRequiredModules();
52
+ // Non-interactive mode: --set / --add / --remove let CI and scripts change
53
+ // dependencies without a prompt. --set replaces the whole set; --add/--remove
54
+ // mutate the current set.
55
+ const setProvided = argv.set !== undefined;
56
+ const addList = parseList(argv.add);
57
+ const removeList = parseList(argv.remove);
58
+ if (setProvided || addList.length > 0 || removeList.length > 0) {
59
+ const base = setProvided ? parseList(argv.set) : [...installed];
60
+ const withAdds = [...base];
61
+ for (const ext of addList) {
62
+ if (!withAdds.includes(ext))
63
+ withAdds.push(ext);
64
+ }
65
+ const next = withAdds.filter((ext) => !removeList.includes(ext));
66
+ project.setModuleDependencies(next);
67
+ return;
68
+ }
69
+ const info = project.getModuleInfo();
29
70
  const available = await project.getAvailableModules();
30
- const filtered = available.filter(name => name !== info.extname);
71
+ const filtered = available.filter((name) => name !== info.extname);
31
72
  const questions = [
32
73
  {
33
74
  name: 'extensions',
34
- message: 'Which modules does this one depend on?',
75
+ message: 'Which extensions / modules does this one depend on?',
35
76
  type: 'checkbox',
36
77
  allowCustomOptions: true,
37
78
  options: filtered,
@@ -40,7 +81,7 @@ export default async (argv, prompter, _options) => {
40
81
  ];
41
82
  const answers = await prompter.prompt(argv, questions);
42
83
  const selected = answers.extensions
43
- .filter(opt => opt.selected)
44
- .map(opt => opt.name);
84
+ .filter((opt) => opt.selected)
85
+ .map((opt) => opt.name);
45
86
  project.setModuleDependencies(selected);
46
87
  };
@@ -0,0 +1,54 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { DEFAULT_TEMPLATE_REPO, PgpmPackage, TEMPLATE_REPOS } from '@pgpmjs/core';
4
+ /**
5
+ * Resolve the template repo for an `init` invocation from its flags.
6
+ *
7
+ * Precedence: explicit `--repo` wins, then `--pglite` (sugar for the pglite
8
+ * boilerplates repo), otherwise the default repo.
9
+ */
10
+ export function resolveInitTemplateRepo(flags) {
11
+ const pglite = Boolean(flags.pglite);
12
+ const repoWasExplicit = typeof flags.repo === 'string' || pglite;
13
+ const templateRepo = typeof flags.repo === 'string'
14
+ ? flags.repo
15
+ : pglite
16
+ ? TEMPLATE_REPOS.pglite
17
+ : DEFAULT_TEMPLATE_REPO;
18
+ return { templateRepo, repoWasExplicit };
19
+ }
20
+ /**
21
+ * Record the boilerplate source on a freshly scaffolded workspace's `pgpm.json`
22
+ * so that `pgpm init` (module) inside it inherits the same template repo.
23
+ * Only writes into an existing pgpm.json (pgpm.config.js workspaces are skipped).
24
+ */
25
+ export function persistBoilerplateSource(workspaceDir, source) {
26
+ const configPath = path.join(workspaceDir, 'pgpm.json');
27
+ if (!fs.existsSync(configPath))
28
+ return;
29
+ try {
30
+ const raw = fs.readFileSync(configPath, 'utf8');
31
+ const config = JSON.parse(raw);
32
+ config.boilerplates = {
33
+ repo: source.repo,
34
+ ...(source.branch ? { branch: source.branch } : {}),
35
+ ...(source.dir ? { dir: source.dir } : {}),
36
+ };
37
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
38
+ }
39
+ catch {
40
+ // Non-fatal: inheritance is a convenience, not a correctness requirement.
41
+ }
42
+ }
43
+ /**
44
+ * Read a previously recorded boilerplate source from the enclosing workspace's
45
+ * config (see {@link persistBoilerplateSource}).
46
+ */
47
+ export function readBoilerplateSource(cwd) {
48
+ const project = new PgpmPackage(cwd);
49
+ const recorded = project.config?.boilerplates;
50
+ if (recorded?.repo) {
51
+ return { repo: recorded.repo, branch: recorded.branch, dir: recorded.dir };
52
+ }
53
+ return undefined;
54
+ }
@@ -5,6 +5,7 @@ import { DEFAULT_TEMPLATE_REPO, DEFAULT_TEMPLATE_TOOL_NAME, inspectTemplate, Pgp
5
5
  import { resolveWorkspaceByType } from '@pgpmjs/env';
6
6
  import { errors } from '@pgpmjs/types';
7
7
  import { registerDefaultResolver } from 'inquirerer';
8
+ import { persistBoilerplateSource, readBoilerplateSource, resolveInitTemplateRepo, } from './boilerplate';
8
9
  const DEFAULT_MOTD = `
9
10
  | _ _
10
11
  === |.===. '\\-//\`
@@ -25,23 +26,33 @@ Options:
25
26
  --help, -h Show this help message
26
27
  --cwd <directory> Working directory (default: current directory)
27
28
  --repo <repo> Template repo (default: https://github.com/constructive-io/pgpm-boilerplates.git)
29
+ --pglite Use the PGlite boilerplates (in-process WASM Postgres, no server/Docker).
30
+ Sugar for --repo https://github.com/constructive-io/pglite-boilerplates.git.
31
+ Recorded on the workspace so later \`init\` calls inherit it automatically.
28
32
  --from-branch <branch> Branch/tag to use when cloning repo
29
33
  --dir <variant> Template variant directory (e.g., supabase, drizzle)
30
34
  --template, -t <path> Full template path (e.g., pnpm/module) - combines dir and fromPath
31
35
  --boilerplate Prompt to select from available boilerplates
32
36
  --create-workspace, -w Create a workspace first, then create the module inside it
33
37
  --use-skills Use npx skills CLI for skill installation (slower, writes skills-lock.json)
38
+ --extensions <a,b> Extensions to require in the new module (default: none).
39
+ Add them later instead with \`${binaryName} extension\`.
40
+ --with-extensions Prompt interactively for extensions during module init
34
41
 
35
42
  Examples:
36
- ${binaryName} init Initialize new module (default)
43
+ ${binaryName} init Initialize new module (default, no extensions)
37
44
  ${binaryName} init workspace Initialize new workspace
38
45
  ${binaryName} init module Initialize new module explicitly
46
+ ${binaryName} init --extensions uuid-ossp,citext Initialize module requiring extensions
47
+ ${binaryName} init --with-extensions Pick extensions interactively
39
48
  ${binaryName} init workspace --dir <variant> Use variant templates
40
49
  ${binaryName} init --template pnpm/module Use full template path (dir + type)
41
50
  ${binaryName} init --boilerplate Select from available boilerplates
42
51
  ${binaryName} init --repo owner/repo Use templates from GitHub repository
43
52
  ${binaryName} init --repo owner/repo --from-branch develop Use specific branch
44
53
  ${binaryName} init --dir pnpm -w Create pnpm workspace + module in one command
54
+ ${binaryName} init workspace --pglite Create a PGlite (in-process) workspace
55
+ ${binaryName} init --pglite Create a PGlite module (inherited automatically inside a --pglite workspace)
45
56
  `;
46
57
  };
47
58
  export default async (argv, prompter, _options) => {
@@ -54,7 +65,13 @@ export default async (argv, prompter, _options) => {
54
65
  };
55
66
  async function handleInit(argv, prompter) {
56
67
  const { cwd = process.cwd() } = argv;
57
- const templateRepo = argv.repo ?? DEFAULT_TEMPLATE_REPO;
68
+ // `--pglite` is sugar for the pglite boilerplates repo; `--repo` overrides it.
69
+ // `repoWasExplicit` means a module created inside a workspace should NOT
70
+ // inherit the workspace's recorded repo (the user pinned one).
71
+ const { templateRepo, repoWasExplicit } = resolveInitTemplateRepo({
72
+ repo: argv.repo,
73
+ pglite: Boolean(argv.pglite),
74
+ });
58
75
  const branch = argv.fromBranch;
59
76
  const noTty = Boolean(argv.noTty || argv['no-tty'] || argv.tty === false || process.env.CI === 'true');
60
77
  const useBoilerplatePrompt = Boolean(argv.boilerplate);
@@ -115,6 +132,7 @@ async function handleInit(argv, prompter) {
115
132
  noTty,
116
133
  cwd,
117
134
  useNpxSkills,
135
+ repoWasExplicit,
118
136
  });
119
137
  }
120
138
  // Default to module init (for 'module' type, 'generic' type, or unknown types)
@@ -128,6 +146,7 @@ async function handleInit(argv, prompter) {
128
146
  requiresWorkspace: inspection.config?.requiresWorkspace,
129
147
  createWorkspace,
130
148
  useNpxSkills,
149
+ repoWasExplicit,
131
150
  }, wasExplicitModuleRequest);
132
151
  }
133
152
  async function handleBoilerplateInit(argv, prompter, ctx) {
@@ -298,6 +317,16 @@ async function handleWorkspaceInit(argv, prompter, ctx) {
298
317
  cwd: ctx.cwd,
299
318
  prompter
300
319
  });
320
+ // Record a non-default template source on the workspace so that modules
321
+ // created later inside it (`pgpm init`) inherit the same boilerplate repo
322
+ // without re-specifying `--pglite`/`--repo`.
323
+ if (ctx.templateRepo !== DEFAULT_TEMPLATE_REPO) {
324
+ persistBoilerplateSource(targetPath, {
325
+ repo: ctx.templateRepo,
326
+ branch: ctx.branch,
327
+ dir: ctx.dir,
328
+ });
329
+ }
301
330
  // Check for .motd file and print it, or use default ASCII art
302
331
  const motdPath = path.join(targetPath, '.motd');
303
332
  let motd = DEFAULT_MOTD;
@@ -332,9 +361,14 @@ async function handleWorkspaceInit(argv, prompter, ctx) {
332
361
  }
333
362
  function resolveWorkspaceTemplateRepo(options) {
334
363
  const { templateRepo, branch, dir, workspaceType, cwd } = options;
335
- // Determine the dir to use for workspace template
336
- // If dir is specified, use it; otherwise use the workspaceType as the dir variant
337
- const workspaceDir = dir || workspaceType;
364
+ // Determine the dir to use for workspace template.
365
+ // - Explicit --dir always wins.
366
+ // - For the default repo (which holds several families), fall back to the
367
+ // workspaceType (pgpm/pnpm) as the variant dir.
368
+ // - For a custom/single-family repo (e.g. --pglite), leave dir undefined so
369
+ // the repo's own .boilerplates.json resolves the family (e.g. `pglite/`).
370
+ const isDefaultRepo = templateRepo === DEFAULT_TEMPLATE_REPO;
371
+ const workspaceDir = dir || (isDefaultRepo ? workspaceType : undefined);
338
372
  // Try to find workspace template in the specified repo
339
373
  try {
340
374
  const inspection = inspectTemplate({
@@ -366,6 +400,17 @@ function resolveWorkspaceTemplateRepo(options) {
366
400
  };
367
401
  }
368
402
  async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest = false) {
403
+ // Inherit the boilerplate source recorded on the enclosing workspace (e.g. by
404
+ // `pgpm init workspace --pglite`) unless the user pinned one explicitly. This
405
+ // makes a bare `pgpm init` inside a pglite workspace scaffold pglite modules.
406
+ if (!ctx.repoWasExplicit) {
407
+ const inherited = readBoilerplateSource(ctx.cwd);
408
+ if (inherited) {
409
+ ctx.templateRepo = inherited.repo;
410
+ ctx.branch = inherited.branch;
411
+ ctx.dir = inherited.dir;
412
+ }
413
+ }
369
414
  // Determine workspace requirement (defaults to 'pgpm' for backward compatibility)
370
415
  const workspaceType = ctx.requiresWorkspace ?? 'pgpm';
371
416
  // Whether this is a pgpm-managed template (creates pgpm.plan, .control files)
@@ -404,6 +449,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
404
449
  dir: workspaceTemplateConfig.dir,
405
450
  noTty: ctx.noTty,
406
451
  cwd: ctx.cwd,
452
+ repoWasExplicit: ctx.repoWasExplicit,
407
453
  });
408
454
  // Update context to point to new workspace and continue with module creation
409
455
  const newCwd = workspaceResult.cwd;
@@ -447,6 +493,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
447
493
  dir: ctx.dir,
448
494
  noTty: ctx.noTty,
449
495
  cwd: ctx.cwd,
496
+ repoWasExplicit: ctx.repoWasExplicit,
450
497
  });
451
498
  }
452
499
  }
@@ -472,8 +519,24 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
472
519
  type: 'text',
473
520
  },
474
521
  ];
475
- // Only ask for extensions if this is a pgpm template
476
- if (isPgpmTemplate && project.workspacePath) {
522
+ // Extensions are opt-in: a bare `pgpm init` scaffolds a module with no
523
+ // extensions (no `requires` line). Extensions are added explicitly later via
524
+ // `pgpm extension`, or up front via `--extensions a,b` (non-interactive) or
525
+ // `--with-extensions` (interactive picker). This matches the zero-config init
526
+ // of comparable tools (sqitch, prisma, drizzle, dbmate, ...).
527
+ // Normalize `--extensions a,b` (a comma string from the CLI) into an array so
528
+ // it is consumed the same way as a programmatic string[].
529
+ if (typeof argv.extensions === 'string') {
530
+ argv.extensions = argv.extensions
531
+ .split(',')
532
+ .map((s) => s.trim())
533
+ .filter(Boolean);
534
+ }
535
+ const extensionsProvided = argv.extensions !== undefined;
536
+ const wantExtensionsPrompt = Boolean(argv.withExtensions || argv['with-extensions']);
537
+ if (isPgpmTemplate &&
538
+ project.workspacePath &&
539
+ (extensionsProvided || wantExtensionsPrompt)) {
477
540
  const availExtensions = await project.getAvailableModules();
478
541
  moduleQuestions.push({
479
542
  name: 'extensions',
@@ -481,8 +544,7 @@ async function handleModuleInit(argv, prompter, ctx, wasExplicitModuleRequest =
481
544
  options: availExtensions,
482
545
  type: 'checkbox',
483
546
  allowCustomOptions: true,
484
- required: true,
485
- default: ['plpgsql', 'uuid-ossp'],
547
+ default: [],
486
548
  });
487
549
  }
488
550
  const answers = await prompter.prompt(argv, moduleQuestions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgpm",
3
- "version": "4.35.0",
3
+ "version": "4.36.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,19 +46,19 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@inquirerer/utils": "^3.3.9",
49
- "@pgpmjs/core": "^6.27.2",
50
- "@pgpmjs/env": "^2.26.2",
51
- "@pgpmjs/export": "^0.26.6",
52
- "@pgpmjs/logger": "^2.13.0",
53
- "@pgpmjs/types": "^2.32.0",
49
+ "@pgpmjs/core": "^6.28.1",
50
+ "@pgpmjs/env": "^2.26.4",
51
+ "@pgpmjs/export": "^0.26.8",
52
+ "@pgpmjs/logger": "^2.13.1",
53
+ "@pgpmjs/types": "^2.33.1",
54
54
  "@pgsql/quotes": "^17.1.0",
55
55
  "appstash": "^0.7.0",
56
56
  "find-and-require-package-json": "^0.9.1",
57
57
  "genomic": "^5.6.2",
58
58
  "inquirerer": "^4.9.1",
59
59
  "js-yaml": "^4.1.0",
60
- "pg-cache": "^3.14.1",
61
- "pg-env": "^1.17.0",
60
+ "pg-cache": "^3.14.3",
61
+ "pg-env": "^1.17.1",
62
62
  "pgsql-deparser": "^17.18.3",
63
63
  "semver": "^7.8.1",
64
64
  "shelljs": "^0.10.0",
@@ -76,5 +76,5 @@
76
76
  "pg",
77
77
  "pgsql"
78
78
  ],
79
- "gitHead": "87e65cd31d4e23c367ca4d398416b19ac863c7de"
79
+ "gitHead": "43b73b3014cb9388ca3218611455c7e0f202f042"
80
80
  }