pgpm 4.35.0 → 4.36.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/commands/extension.js +48 -7
- package/commands/init/boilerplate.d.ts +33 -0
- package/commands/init/boilerplate.js +62 -0
- package/commands/init/index.js +71 -9
- package/esm/commands/extension.js +48 -7
- package/esm/commands/init/boilerplate.js +54 -0
- package/esm/commands/init/index.js +71 -9
- package/package.json +7 -7
package/commands/extension.js
CHANGED
|
@@ -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
|
|
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
|
+
}
|
package/commands/init/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
344
|
-
|
|
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
|
-
//
|
|
483
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
337
|
-
|
|
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
|
-
//
|
|
476
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "4.36.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",
|
|
@@ -46,18 +46,18 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@inquirerer/utils": "^3.3.9",
|
|
49
|
-
"@pgpmjs/core": "^6.
|
|
50
|
-
"@pgpmjs/env": "^2.26.
|
|
51
|
-
"@pgpmjs/export": "^0.26.
|
|
49
|
+
"@pgpmjs/core": "^6.28.0",
|
|
50
|
+
"@pgpmjs/env": "^2.26.3",
|
|
51
|
+
"@pgpmjs/export": "^0.26.7",
|
|
52
52
|
"@pgpmjs/logger": "^2.13.0",
|
|
53
|
-
"@pgpmjs/types": "^2.
|
|
53
|
+
"@pgpmjs/types": "^2.33.0",
|
|
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.
|
|
60
|
+
"pg-cache": "^3.14.2",
|
|
61
61
|
"pg-env": "^1.17.0",
|
|
62
62
|
"pgsql-deparser": "^17.18.3",
|
|
63
63
|
"semver": "^7.8.1",
|
|
@@ -76,5 +76,5 @@
|
|
|
76
76
|
"pg",
|
|
77
77
|
"pgsql"
|
|
78
78
|
],
|
|
79
|
-
"gitHead": "
|
|
79
|
+
"gitHead": "e2a1337c7ec8366b28c10e550645e44cad6085ff"
|
|
80
80
|
}
|