pgpm 1.4.2 → 2.1.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.
@@ -1,8 +1,8 @@
1
1
  import { CLIOptions, Inquirerer } from 'inquirerer';
2
2
  export declare const createInitUsageText: (binaryName: string, productLabel?: string) => string;
3
3
  declare const _default: (argv: Partial<Record<string, any>>, prompter: Inquirerer, _options: CLIOptions) => Promise<{
4
- [x: string]: any;
5
- } | {
6
4
  cwd: string;
5
+ } | {
6
+ [x: string]: any;
7
7
  }>;
8
8
  export default _default;
@@ -4,29 +4,42 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createInitUsageText = void 0;
7
- const module_1 = __importDefault(require("./module"));
8
- const workspace_1 = __importDefault(require("./workspace"));
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const core_1 = require("@pgpmjs/core");
10
+ const types_1 = require("@pgpmjs/types");
11
+ const inquirerer_1 = require("inquirerer");
12
+ const DEFAULT_MOTD = `
13
+ | _ _
14
+ === |.===. '\\-//\`
15
+ (o o) {}o o{} (o o)
16
+ ooO--(_)--Ooo-ooO--(_)--Ooo-ooO--(_)--Ooo-
17
+ `;
9
18
  const createInitUsageText = (binaryName, productLabel) => {
10
19
  const displayName = productLabel ?? binaryName;
11
20
  return `
12
21
  Init Command:
13
22
 
14
- ${binaryName} init [OPTIONS] [workspace]
23
+ ${binaryName} init [OPTIONS] [<fromPath>]
15
24
 
16
- Initialize ${displayName} workspace or module.
25
+ Initialize ${displayName} from a template. The template's type (workspace/module)
26
+ determines the behavior automatically.
17
27
 
18
28
  Options:
19
29
  --help, -h Show this help message
20
30
  --cwd <directory> Working directory (default: current directory)
21
31
  --repo <repo> Template repo (default: https://github.com/constructive-io/pgpm-boilerplates.git)
22
- --template-path <path> Template sub-path (default: workspace/module) or local path override
23
32
  --from-branch <branch> Branch/tag to use when cloning repo
33
+ --dir <variant> Template variant directory (e.g., supabase, drizzle)
34
+ --boilerplate Prompt to select from available boilerplates
24
35
 
25
36
  Examples:
26
- ${binaryName} init Initialize new module in existing workspace
37
+ ${binaryName} init Initialize new module (default)
27
38
  ${binaryName} init workspace Initialize new workspace
39
+ ${binaryName} init module Initialize new module explicitly
40
+ ${binaryName} init workspace --dir <variant> Use variant templates
41
+ ${binaryName} init --boilerplate Select from available boilerplates
28
42
  ${binaryName} init --repo owner/repo Use templates from GitHub repository
29
- ${binaryName} init --template-path ./custom-templates Use templates from local path
30
43
  ${binaryName} init --repo owner/repo --from-branch develop Use specific branch
31
44
  `;
32
45
  };
@@ -37,16 +50,256 @@ exports.default = async (argv, prompter, _options) => {
37
50
  console.log((0, exports.createInitUsageText)('pgpm'));
38
51
  process.exit(0);
39
52
  }
40
- return handlePromptFlow(argv, prompter);
53
+ return handleInit(argv, prompter);
41
54
  };
42
- async function handlePromptFlow(argv, prompter) {
43
- const firstArg = argv._?.[0] || undefined;
44
- if (firstArg === 'workspace') {
45
- const nextArgv = {
55
+ async function handleInit(argv, prompter) {
56
+ const { cwd = process.cwd() } = argv;
57
+ const templateRepo = argv.repo ?? core_1.DEFAULT_TEMPLATE_REPO;
58
+ const branch = argv.fromBranch;
59
+ const dir = argv.dir;
60
+ const noTty = Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true');
61
+ const useBoilerplatePrompt = Boolean(argv.boilerplate);
62
+ // Get fromPath from first positional arg
63
+ const positionalFromPath = argv._?.[0];
64
+ // Handle --boilerplate flag: separate path from regular init
65
+ if (useBoilerplatePrompt) {
66
+ return handleBoilerplateInit(argv, prompter, {
67
+ positionalFromPath,
68
+ templateRepo,
69
+ branch,
70
+ dir,
71
+ noTty,
72
+ cwd,
73
+ });
74
+ }
75
+ // Regular init path: default to 'module' if no fromPath provided
76
+ const fromPath = positionalFromPath || 'module';
77
+ // Inspect the template to get its type
78
+ const inspection = (0, core_1.inspectTemplate)({
79
+ fromPath,
80
+ templateRepo,
81
+ branch,
82
+ dir,
83
+ toolName: core_1.DEFAULT_TEMPLATE_TOOL_NAME,
84
+ cwd,
85
+ });
86
+ const templateType = inspection.config?.type;
87
+ // Branch based on template type
88
+ if (templateType === 'workspace') {
89
+ return handleWorkspaceInit(argv, prompter, {
90
+ fromPath,
91
+ templateRepo,
92
+ branch,
93
+ dir,
94
+ noTty,
95
+ cwd,
96
+ });
97
+ }
98
+ // Default to module init (for 'module' type or unknown types)
99
+ return handleModuleInit(argv, prompter, {
100
+ fromPath,
101
+ templateRepo,
102
+ branch,
103
+ dir,
104
+ noTty,
105
+ cwd,
106
+ });
107
+ }
108
+ async function handleBoilerplateInit(argv, prompter, ctx) {
109
+ let fromPath;
110
+ if (ctx.positionalFromPath) {
111
+ // If a positional fromPath was provided with --boilerplate, use it directly
112
+ fromPath = ctx.positionalFromPath;
113
+ }
114
+ else {
115
+ // No positional arg: prompt user to select from available boilerplates
116
+ if (ctx.noTty) {
117
+ throw new Error('Cannot use --boilerplate without a <fromPath> argument in non-interactive mode. ' +
118
+ 'Please specify a boilerplate explicitly, e.g., `pgpm init workspace --boilerplate`');
119
+ }
120
+ // Inspect without fromPath to get the template directory for scanning
121
+ const initialInspection = (0, core_1.inspectTemplate)({
122
+ templateRepo: ctx.templateRepo,
123
+ branch: ctx.branch,
124
+ dir: ctx.dir,
125
+ toolName: core_1.DEFAULT_TEMPLATE_TOOL_NAME,
126
+ cwd: ctx.cwd,
127
+ });
128
+ // Resolve the base directory for scanning boilerplates:
129
+ // - If --dir is specified, use the resolvedTemplatePath (bypasses .boilerplates.json)
130
+ // - Otherwise, use .boilerplates.json's dir (defaults to repo root if missing)
131
+ const baseDir = ctx.dir
132
+ ? initialInspection.resolvedTemplatePath
133
+ : (0, core_1.resolveBoilerplateBaseDir)(initialInspection.templateDir);
134
+ const boilerplates = (0, core_1.scanBoilerplates)(baseDir);
135
+ if (boilerplates.length === 0) {
136
+ throw new Error(`No boilerplates found in the template repository.\n` +
137
+ `Checked directory: ${baseDir}\n` +
138
+ `Make sure the repository contains boilerplate directories with .boilerplate.json files.`);
139
+ }
140
+ const boilerplateQuestion = [
141
+ {
142
+ name: 'selectedBoilerplate',
143
+ message: 'Select a boilerplate',
144
+ type: 'autocomplete',
145
+ options: boilerplates.map((bp) => bp.name),
146
+ required: true,
147
+ },
148
+ ];
149
+ const boilerplateAnswer = await prompter.prompt(argv, boilerplateQuestion);
150
+ fromPath = boilerplateAnswer.selectedBoilerplate;
151
+ }
152
+ // Inspect the selected template to get its type
153
+ const inspection = (0, core_1.inspectTemplate)({
154
+ fromPath,
155
+ templateRepo: ctx.templateRepo,
156
+ branch: ctx.branch,
157
+ dir: ctx.dir,
158
+ toolName: core_1.DEFAULT_TEMPLATE_TOOL_NAME,
159
+ cwd: ctx.cwd,
160
+ });
161
+ const templateType = inspection.config?.type;
162
+ // Branch based on template type
163
+ if (templateType === 'workspace') {
164
+ return handleWorkspaceInit(argv, prompter, {
165
+ fromPath,
166
+ templateRepo: ctx.templateRepo,
167
+ branch: ctx.branch,
168
+ dir: ctx.dir,
169
+ noTty: ctx.noTty,
170
+ cwd: ctx.cwd,
171
+ });
172
+ }
173
+ // Default to module init (for 'module' type or unknown types)
174
+ return handleModuleInit(argv, prompter, {
175
+ fromPath,
176
+ templateRepo: ctx.templateRepo,
177
+ branch: ctx.branch,
178
+ dir: ctx.dir,
179
+ noTty: ctx.noTty,
180
+ cwd: ctx.cwd,
181
+ });
182
+ }
183
+ async function handleWorkspaceInit(argv, prompter, ctx) {
184
+ const workspaceQuestions = [
185
+ {
186
+ name: 'name',
187
+ message: 'Enter workspace name',
188
+ required: true,
189
+ type: 'text'
190
+ }
191
+ ];
192
+ const answers = await prompter.prompt(argv, workspaceQuestions);
193
+ const targetPath = path_1.default.join(ctx.cwd, (0, core_1.sluggify)(answers.name));
194
+ // Register workspace.dirname resolver so boilerplate templates can use it via defaultFrom/setFrom
195
+ const dirName = path_1.default.basename(targetPath);
196
+ (0, inquirerer_1.registerDefaultResolver)('workspace.dirname', () => dirName);
197
+ await (0, core_1.scaffoldTemplate)({
198
+ fromPath: ctx.fromPath,
199
+ outputDir: targetPath,
200
+ templateRepo: ctx.templateRepo,
201
+ branch: ctx.branch,
202
+ dir: ctx.dir,
203
+ answers: {
46
204
  ...argv,
47
- _: (argv._ ?? []).slice(1)
48
- };
49
- return (0, workspace_1.default)(nextArgv, prompter);
205
+ ...answers,
206
+ workspaceName: answers.name
207
+ },
208
+ toolName: core_1.DEFAULT_TEMPLATE_TOOL_NAME,
209
+ noTty: ctx.noTty,
210
+ cwd: ctx.cwd,
211
+ prompter
212
+ });
213
+ // Check for .motd file and print it, or use default ASCII art
214
+ const motdPath = path_1.default.join(targetPath, '.motd');
215
+ let motd = DEFAULT_MOTD;
216
+ if (fs_1.default.existsSync(motdPath)) {
217
+ try {
218
+ motd = fs_1.default.readFileSync(motdPath, 'utf8');
219
+ fs_1.default.unlinkSync(motdPath);
220
+ }
221
+ catch {
222
+ // Ignore errors reading/deleting .motd
223
+ }
224
+ }
225
+ process.stdout.write(motd);
226
+ if (!motd.endsWith('\n')) {
227
+ process.stdout.write('\n');
228
+ }
229
+ process.stdout.write(`\n✨ Enjoy!\n\ncd ./${dirName}\n`);
230
+ return { ...argv, ...answers, cwd: targetPath };
231
+ }
232
+ async function handleModuleInit(argv, prompter, ctx) {
233
+ const project = new core_1.PgpmPackage(ctx.cwd);
234
+ if (!project.workspacePath) {
235
+ process.stderr.write('Not inside a PGPM workspace.\n');
236
+ throw types_1.errors.NOT_IN_WORKSPACE({});
237
+ }
238
+ if (!project.isInsideAllowedDirs(ctx.cwd) && !project.isInWorkspace() && !project.isParentOfAllowedDirs(ctx.cwd)) {
239
+ process.stderr.write('You must be inside the workspace root or a parent directory of modules (like packages/).\n');
240
+ throw types_1.errors.NOT_IN_WORKSPACE_MODULE({});
241
+ }
242
+ const availExtensions = project.getAvailableModules();
243
+ const moduleQuestions = [
244
+ {
245
+ name: 'moduleName',
246
+ message: 'Enter the module name',
247
+ required: true,
248
+ type: 'text',
249
+ },
250
+ {
251
+ name: 'extensions',
252
+ message: 'Which extensions?',
253
+ options: availExtensions,
254
+ type: 'checkbox',
255
+ allowCustomOptions: true,
256
+ required: true,
257
+ },
258
+ ];
259
+ const answers = await prompter.prompt(argv, moduleQuestions);
260
+ const modName = (0, core_1.sluggify)(answers.moduleName);
261
+ const extensions = answers.extensions
262
+ .filter((opt) => opt.selected)
263
+ .map((opt) => opt.name);
264
+ const templateAnswers = {
265
+ ...argv,
266
+ ...answers,
267
+ moduleName: modName,
268
+ packageIdentifier: argv.packageIdentifier || modName
269
+ };
270
+ await project.initModule({
271
+ name: modName,
272
+ description: answers.description || modName,
273
+ author: answers.author || modName,
274
+ extensions,
275
+ templateRepo: ctx.templateRepo,
276
+ templatePath: ctx.fromPath,
277
+ branch: ctx.branch,
278
+ dir: ctx.dir,
279
+ toolName: core_1.DEFAULT_TEMPLATE_TOOL_NAME,
280
+ answers: templateAnswers,
281
+ noTty: ctx.noTty
282
+ });
283
+ const isRoot = path_1.default.resolve(project.getWorkspacePath()) === path_1.default.resolve(ctx.cwd);
284
+ const modulePath = isRoot
285
+ ? path_1.default.join(ctx.cwd, 'packages', modName)
286
+ : path_1.default.join(ctx.cwd, modName);
287
+ const motdPath = path_1.default.join(modulePath, '.motd');
288
+ let motd = DEFAULT_MOTD;
289
+ if (fs_1.default.existsSync(motdPath)) {
290
+ try {
291
+ motd = fs_1.default.readFileSync(motdPath, 'utf8');
292
+ fs_1.default.unlinkSync(motdPath);
293
+ }
294
+ catch {
295
+ // Ignore errors reading/deleting .motd
296
+ }
297
+ }
298
+ process.stdout.write(motd);
299
+ if (!motd.endsWith('\n')) {
300
+ process.stdout.write('\n');
50
301
  }
51
- return (0, module_1.default)(argv, prompter);
302
+ const relPath = isRoot ? `packages/${modName}` : modName;
303
+ process.stdout.write(`\n✨ Enjoy!\n\ncd ./${relPath}\n`);
304
+ return { ...argv, ...answers };
52
305
  }
@@ -51,6 +51,7 @@ async function runModuleSetup(argv, prompter) {
51
51
  .map((opt) => opt.name);
52
52
  const templateRepo = argv.repo ?? core_1.DEFAULT_TEMPLATE_REPO;
53
53
  const templatePath = argv.templatePath;
54
+ const dir = argv.dir;
54
55
  const templateAnswers = {
55
56
  ...argv,
56
57
  ...answers,
@@ -65,6 +66,7 @@ async function runModuleSetup(argv, prompter) {
65
66
  templateRepo,
66
67
  templatePath,
67
68
  branch: argv.fromBranch,
69
+ dir,
68
70
  toolName: core_1.DEFAULT_TEMPLATE_TOOL_NAME,
69
71
  answers: templateAnswers,
70
72
  noTty: Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true')
@@ -35,12 +35,13 @@ async function runWorkspaceSetup(argv, prompter) {
35
35
  // This provides the intended workspace directory name before the folder is created
36
36
  const dirName = path_1.default.basename(targetPath);
37
37
  (0, inquirerer_1.registerDefaultResolver)('workspace.dirname', () => dirName);
38
+ const dir = argv.dir;
38
39
  await (0, core_1.scaffoldTemplate)({
39
- type: 'workspace',
40
+ fromPath: templatePath ?? 'workspace',
40
41
  outputDir: targetPath,
41
42
  templateRepo,
42
43
  branch: argv.fromBranch,
43
- templatePath,
44
+ dir,
44
45
  answers: {
45
46
  ...argv,
46
47
  ...answers,
@@ -48,7 +49,8 @@ async function runWorkspaceSetup(argv, prompter) {
48
49
  },
49
50
  toolName: core_1.DEFAULT_TEMPLATE_TOOL_NAME,
50
51
  noTty: Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true'),
51
- cwd
52
+ cwd,
53
+ prompter
52
54
  });
53
55
  // Check for .motd file and print it, or use default ASCII art
54
56
  const motdPath = path_1.default.join(targetPath, '.motd');
@@ -1,26 +1,39 @@
1
- import runModuleSetup from './module';
2
- import runWorkspaceSetup from './workspace';
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { DEFAULT_TEMPLATE_REPO, DEFAULT_TEMPLATE_TOOL_NAME, inspectTemplate, PgpmPackage, resolveBoilerplateBaseDir, scaffoldTemplate, scanBoilerplates, sluggify, } from '@pgpmjs/core';
4
+ import { errors } from '@pgpmjs/types';
5
+ import { registerDefaultResolver } from 'inquirerer';
6
+ const DEFAULT_MOTD = `
7
+ | _ _
8
+ === |.===. '\\-//\`
9
+ (o o) {}o o{} (o o)
10
+ ooO--(_)--Ooo-ooO--(_)--Ooo-ooO--(_)--Ooo-
11
+ `;
3
12
  export const createInitUsageText = (binaryName, productLabel) => {
4
13
  const displayName = productLabel ?? binaryName;
5
14
  return `
6
15
  Init Command:
7
16
 
8
- ${binaryName} init [OPTIONS] [workspace]
17
+ ${binaryName} init [OPTIONS] [<fromPath>]
9
18
 
10
- Initialize ${displayName} workspace or module.
19
+ Initialize ${displayName} from a template. The template's type (workspace/module)
20
+ determines the behavior automatically.
11
21
 
12
22
  Options:
13
23
  --help, -h Show this help message
14
24
  --cwd <directory> Working directory (default: current directory)
15
25
  --repo <repo> Template repo (default: https://github.com/constructive-io/pgpm-boilerplates.git)
16
- --template-path <path> Template sub-path (default: workspace/module) or local path override
17
26
  --from-branch <branch> Branch/tag to use when cloning repo
27
+ --dir <variant> Template variant directory (e.g., supabase, drizzle)
28
+ --boilerplate Prompt to select from available boilerplates
18
29
 
19
30
  Examples:
20
- ${binaryName} init Initialize new module in existing workspace
31
+ ${binaryName} init Initialize new module (default)
21
32
  ${binaryName} init workspace Initialize new workspace
33
+ ${binaryName} init module Initialize new module explicitly
34
+ ${binaryName} init workspace --dir <variant> Use variant templates
35
+ ${binaryName} init --boilerplate Select from available boilerplates
22
36
  ${binaryName} init --repo owner/repo Use templates from GitHub repository
23
- ${binaryName} init --template-path ./custom-templates Use templates from local path
24
37
  ${binaryName} init --repo owner/repo --from-branch develop Use specific branch
25
38
  `;
26
39
  };
@@ -30,16 +43,256 @@ export default async (argv, prompter, _options) => {
30
43
  console.log(createInitUsageText('pgpm'));
31
44
  process.exit(0);
32
45
  }
33
- return handlePromptFlow(argv, prompter);
46
+ return handleInit(argv, prompter);
34
47
  };
35
- async function handlePromptFlow(argv, prompter) {
36
- const firstArg = argv._?.[0] || undefined;
37
- if (firstArg === 'workspace') {
38
- const nextArgv = {
48
+ async function handleInit(argv, prompter) {
49
+ const { cwd = process.cwd() } = argv;
50
+ const templateRepo = argv.repo ?? DEFAULT_TEMPLATE_REPO;
51
+ const branch = argv.fromBranch;
52
+ const dir = argv.dir;
53
+ const noTty = Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true');
54
+ const useBoilerplatePrompt = Boolean(argv.boilerplate);
55
+ // Get fromPath from first positional arg
56
+ const positionalFromPath = argv._?.[0];
57
+ // Handle --boilerplate flag: separate path from regular init
58
+ if (useBoilerplatePrompt) {
59
+ return handleBoilerplateInit(argv, prompter, {
60
+ positionalFromPath,
61
+ templateRepo,
62
+ branch,
63
+ dir,
64
+ noTty,
65
+ cwd,
66
+ });
67
+ }
68
+ // Regular init path: default to 'module' if no fromPath provided
69
+ const fromPath = positionalFromPath || 'module';
70
+ // Inspect the template to get its type
71
+ const inspection = inspectTemplate({
72
+ fromPath,
73
+ templateRepo,
74
+ branch,
75
+ dir,
76
+ toolName: DEFAULT_TEMPLATE_TOOL_NAME,
77
+ cwd,
78
+ });
79
+ const templateType = inspection.config?.type;
80
+ // Branch based on template type
81
+ if (templateType === 'workspace') {
82
+ return handleWorkspaceInit(argv, prompter, {
83
+ fromPath,
84
+ templateRepo,
85
+ branch,
86
+ dir,
87
+ noTty,
88
+ cwd,
89
+ });
90
+ }
91
+ // Default to module init (for 'module' type or unknown types)
92
+ return handleModuleInit(argv, prompter, {
93
+ fromPath,
94
+ templateRepo,
95
+ branch,
96
+ dir,
97
+ noTty,
98
+ cwd,
99
+ });
100
+ }
101
+ async function handleBoilerplateInit(argv, prompter, ctx) {
102
+ let fromPath;
103
+ if (ctx.positionalFromPath) {
104
+ // If a positional fromPath was provided with --boilerplate, use it directly
105
+ fromPath = ctx.positionalFromPath;
106
+ }
107
+ else {
108
+ // No positional arg: prompt user to select from available boilerplates
109
+ if (ctx.noTty) {
110
+ throw new Error('Cannot use --boilerplate without a <fromPath> argument in non-interactive mode. ' +
111
+ 'Please specify a boilerplate explicitly, e.g., `pgpm init workspace --boilerplate`');
112
+ }
113
+ // Inspect without fromPath to get the template directory for scanning
114
+ const initialInspection = inspectTemplate({
115
+ templateRepo: ctx.templateRepo,
116
+ branch: ctx.branch,
117
+ dir: ctx.dir,
118
+ toolName: DEFAULT_TEMPLATE_TOOL_NAME,
119
+ cwd: ctx.cwd,
120
+ });
121
+ // Resolve the base directory for scanning boilerplates:
122
+ // - If --dir is specified, use the resolvedTemplatePath (bypasses .boilerplates.json)
123
+ // - Otherwise, use .boilerplates.json's dir (defaults to repo root if missing)
124
+ const baseDir = ctx.dir
125
+ ? initialInspection.resolvedTemplatePath
126
+ : resolveBoilerplateBaseDir(initialInspection.templateDir);
127
+ const boilerplates = scanBoilerplates(baseDir);
128
+ if (boilerplates.length === 0) {
129
+ throw new Error(`No boilerplates found in the template repository.\n` +
130
+ `Checked directory: ${baseDir}\n` +
131
+ `Make sure the repository contains boilerplate directories with .boilerplate.json files.`);
132
+ }
133
+ const boilerplateQuestion = [
134
+ {
135
+ name: 'selectedBoilerplate',
136
+ message: 'Select a boilerplate',
137
+ type: 'autocomplete',
138
+ options: boilerplates.map((bp) => bp.name),
139
+ required: true,
140
+ },
141
+ ];
142
+ const boilerplateAnswer = await prompter.prompt(argv, boilerplateQuestion);
143
+ fromPath = boilerplateAnswer.selectedBoilerplate;
144
+ }
145
+ // Inspect the selected template to get its type
146
+ const inspection = inspectTemplate({
147
+ fromPath,
148
+ templateRepo: ctx.templateRepo,
149
+ branch: ctx.branch,
150
+ dir: ctx.dir,
151
+ toolName: DEFAULT_TEMPLATE_TOOL_NAME,
152
+ cwd: ctx.cwd,
153
+ });
154
+ const templateType = inspection.config?.type;
155
+ // Branch based on template type
156
+ if (templateType === 'workspace') {
157
+ return handleWorkspaceInit(argv, prompter, {
158
+ fromPath,
159
+ templateRepo: ctx.templateRepo,
160
+ branch: ctx.branch,
161
+ dir: ctx.dir,
162
+ noTty: ctx.noTty,
163
+ cwd: ctx.cwd,
164
+ });
165
+ }
166
+ // Default to module init (for 'module' type or unknown types)
167
+ return handleModuleInit(argv, prompter, {
168
+ fromPath,
169
+ templateRepo: ctx.templateRepo,
170
+ branch: ctx.branch,
171
+ dir: ctx.dir,
172
+ noTty: ctx.noTty,
173
+ cwd: ctx.cwd,
174
+ });
175
+ }
176
+ async function handleWorkspaceInit(argv, prompter, ctx) {
177
+ const workspaceQuestions = [
178
+ {
179
+ name: 'name',
180
+ message: 'Enter workspace name',
181
+ required: true,
182
+ type: 'text'
183
+ }
184
+ ];
185
+ const answers = await prompter.prompt(argv, workspaceQuestions);
186
+ const targetPath = path.join(ctx.cwd, sluggify(answers.name));
187
+ // Register workspace.dirname resolver so boilerplate templates can use it via defaultFrom/setFrom
188
+ const dirName = path.basename(targetPath);
189
+ registerDefaultResolver('workspace.dirname', () => dirName);
190
+ await scaffoldTemplate({
191
+ fromPath: ctx.fromPath,
192
+ outputDir: targetPath,
193
+ templateRepo: ctx.templateRepo,
194
+ branch: ctx.branch,
195
+ dir: ctx.dir,
196
+ answers: {
39
197
  ...argv,
40
- _: (argv._ ?? []).slice(1)
41
- };
42
- return runWorkspaceSetup(nextArgv, prompter);
198
+ ...answers,
199
+ workspaceName: answers.name
200
+ },
201
+ toolName: DEFAULT_TEMPLATE_TOOL_NAME,
202
+ noTty: ctx.noTty,
203
+ cwd: ctx.cwd,
204
+ prompter
205
+ });
206
+ // Check for .motd file and print it, or use default ASCII art
207
+ const motdPath = path.join(targetPath, '.motd');
208
+ let motd = DEFAULT_MOTD;
209
+ if (fs.existsSync(motdPath)) {
210
+ try {
211
+ motd = fs.readFileSync(motdPath, 'utf8');
212
+ fs.unlinkSync(motdPath);
213
+ }
214
+ catch {
215
+ // Ignore errors reading/deleting .motd
216
+ }
217
+ }
218
+ process.stdout.write(motd);
219
+ if (!motd.endsWith('\n')) {
220
+ process.stdout.write('\n');
221
+ }
222
+ process.stdout.write(`\n✨ Enjoy!\n\ncd ./${dirName}\n`);
223
+ return { ...argv, ...answers, cwd: targetPath };
224
+ }
225
+ async function handleModuleInit(argv, prompter, ctx) {
226
+ const project = new PgpmPackage(ctx.cwd);
227
+ if (!project.workspacePath) {
228
+ process.stderr.write('Not inside a PGPM workspace.\n');
229
+ throw errors.NOT_IN_WORKSPACE({});
230
+ }
231
+ if (!project.isInsideAllowedDirs(ctx.cwd) && !project.isInWorkspace() && !project.isParentOfAllowedDirs(ctx.cwd)) {
232
+ process.stderr.write('You must be inside the workspace root or a parent directory of modules (like packages/).\n');
233
+ throw errors.NOT_IN_WORKSPACE_MODULE({});
234
+ }
235
+ const availExtensions = project.getAvailableModules();
236
+ const moduleQuestions = [
237
+ {
238
+ name: 'moduleName',
239
+ message: 'Enter the module name',
240
+ required: true,
241
+ type: 'text',
242
+ },
243
+ {
244
+ name: 'extensions',
245
+ message: 'Which extensions?',
246
+ options: availExtensions,
247
+ type: 'checkbox',
248
+ allowCustomOptions: true,
249
+ required: true,
250
+ },
251
+ ];
252
+ const answers = await prompter.prompt(argv, moduleQuestions);
253
+ const modName = sluggify(answers.moduleName);
254
+ const extensions = answers.extensions
255
+ .filter((opt) => opt.selected)
256
+ .map((opt) => opt.name);
257
+ const templateAnswers = {
258
+ ...argv,
259
+ ...answers,
260
+ moduleName: modName,
261
+ packageIdentifier: argv.packageIdentifier || modName
262
+ };
263
+ await project.initModule({
264
+ name: modName,
265
+ description: answers.description || modName,
266
+ author: answers.author || modName,
267
+ extensions,
268
+ templateRepo: ctx.templateRepo,
269
+ templatePath: ctx.fromPath,
270
+ branch: ctx.branch,
271
+ dir: ctx.dir,
272
+ toolName: DEFAULT_TEMPLATE_TOOL_NAME,
273
+ answers: templateAnswers,
274
+ noTty: ctx.noTty
275
+ });
276
+ const isRoot = path.resolve(project.getWorkspacePath()) === path.resolve(ctx.cwd);
277
+ const modulePath = isRoot
278
+ ? path.join(ctx.cwd, 'packages', modName)
279
+ : path.join(ctx.cwd, modName);
280
+ const motdPath = path.join(modulePath, '.motd');
281
+ let motd = DEFAULT_MOTD;
282
+ if (fs.existsSync(motdPath)) {
283
+ try {
284
+ motd = fs.readFileSync(motdPath, 'utf8');
285
+ fs.unlinkSync(motdPath);
286
+ }
287
+ catch {
288
+ // Ignore errors reading/deleting .motd
289
+ }
290
+ }
291
+ process.stdout.write(motd);
292
+ if (!motd.endsWith('\n')) {
293
+ process.stdout.write('\n');
43
294
  }
44
- return runModuleSetup(argv, prompter);
295
+ const relPath = isRoot ? `packages/${modName}` : modName;
296
+ process.stdout.write(`\n✨ Enjoy!\n\ncd ./${relPath}\n`);
297
+ return { ...argv, ...answers };
45
298
  }
@@ -45,6 +45,7 @@ export default async function runModuleSetup(argv, prompter) {
45
45
  .map((opt) => opt.name);
46
46
  const templateRepo = argv.repo ?? DEFAULT_TEMPLATE_REPO;
47
47
  const templatePath = argv.templatePath;
48
+ const dir = argv.dir;
48
49
  const templateAnswers = {
49
50
  ...argv,
50
51
  ...answers,
@@ -59,6 +60,7 @@ export default async function runModuleSetup(argv, prompter) {
59
60
  templateRepo,
60
61
  templatePath,
61
62
  branch: argv.fromBranch,
63
+ dir,
62
64
  toolName: DEFAULT_TEMPLATE_TOOL_NAME,
63
65
  answers: templateAnswers,
64
66
  noTty: Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true')
@@ -29,12 +29,13 @@ export default async function runWorkspaceSetup(argv, prompter) {
29
29
  // This provides the intended workspace directory name before the folder is created
30
30
  const dirName = path.basename(targetPath);
31
31
  registerDefaultResolver('workspace.dirname', () => dirName);
32
+ const dir = argv.dir;
32
33
  await scaffoldTemplate({
33
- type: 'workspace',
34
+ fromPath: templatePath ?? 'workspace',
34
35
  outputDir: targetPath,
35
36
  templateRepo,
36
37
  branch: argv.fromBranch,
37
- templatePath,
38
+ dir,
38
39
  answers: {
39
40
  ...argv,
40
41
  ...answers,
@@ -42,7 +43,8 @@ export default async function runWorkspaceSetup(argv, prompter) {
42
43
  },
43
44
  toolName: DEFAULT_TEMPLATE_TOOL_NAME,
44
45
  noTty: Boolean(argv.noTty || argv['no-tty'] || process.env.CI === 'true'),
45
- cwd
46
+ cwd,
47
+ prompter
46
48
  });
47
49
  // Check for .motd file and print it, or use default ASCII art
48
50
  const motdPath = path.join(targetPath, '.motd');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgpm",
3
- "version": "1.4.2",
3
+ "version": "2.1.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",
@@ -45,14 +45,14 @@
45
45
  "ts-node": "^10.9.2"
46
46
  },
47
47
  "dependencies": {
48
- "@pgpmjs/core": "^3.2.3",
48
+ "@pgpmjs/core": "^4.0.1",
49
49
  "@pgpmjs/env": "^2.8.8",
50
50
  "@pgpmjs/logger": "^1.3.5",
51
51
  "@pgpmjs/types": "^2.12.6",
52
52
  "appstash": "^0.2.6",
53
- "create-gen-app": "^0.6.4",
53
+ "create-gen-app": "^0.8.1",
54
54
  "find-and-require-package-json": "^0.8.2",
55
- "inquirerer": "^2.3.2",
55
+ "inquirerer": "^2.4.0",
56
56
  "js-yaml": "^4.1.0",
57
57
  "minimist": "^1.2.8",
58
58
  "pg-cache": "^1.6.9",
@@ -73,5 +73,5 @@
73
73
  "pg",
74
74
  "pgsql"
75
75
  ],
76
- "gitHead": "9ffc005dd4668e97082d9254ce5c8ff934b757e1"
76
+ "gitHead": "b4dd6e24bd84d79f3057d8555b850481b7d4e19a"
77
77
  }