Skip to content

Commit 9a803ab

Browse files
feat(create-gen-app): replace .questions.json with .boilerplate.json format
- Replace .questions.json/.js support with .boilerplate.json/.js - Skip .boilerplate.json, .boilerplate.js, and .boilerplates.json from output - Update tests to use pgpm-boilerplates-testing repo for isolation - Update README documentation for new format BREAKING CHANGE: .questions.json/.js files are no longer supported. Use .boilerplate.json/.js instead. Co-Authored-By: Dan Lynch <[email protected]>
1 parent 64ad2e1 commit 9a803ab

File tree

6 files changed

+51
-28
lines changed

6 files changed

+51
-28
lines changed

packages/create-gen-app-test/src/test-utils/integration-helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { CacheManager, GitCloner } from 'create-gen-app';
66

77
export const TEST_REPO =
88
process.env.CREATE_GEN_TEST_REPO ??
9-
'https://github.com/constructive-io/pgpm-boilerplates.git';
9+
'https://github.com/constructive-io/pgpm-boilerplates-testing.git';
1010
export const TEST_BRANCH =
1111
process.env.CREATE_GEN_TEST_BRANCH ?? 'main';
1212
export const TEST_TEMPLATE_DIR = (() => {

packages/create-gen-app/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ export const author = "____fullName____";
114114

115115
### Custom Questions
116116

117-
Create a `.questions.json`:
117+
Create a `.boilerplate.json`:
118118

119119
```json
120120
{
121+
"type": "module",
121122
"questions": [
122123
{
123124
"name": "____fullName____",
@@ -135,7 +136,9 @@ Create a `.questions.json`:
135136
}
136137
```
137138

138-
Or `.questions.js` for dynamic logic. Question names can use `____var____` or plain `VAR`; they'll be normalized automatically.
139+
Or `.boilerplate.js` for dynamic logic. Question names can use `____var____` or plain `VAR`; they'll be normalized automatically.
140+
141+
Note: `.boilerplate.json`, `.boilerplate.js`, and `.boilerplates.json` files are automatically excluded from the generated output.
139142

140143
### License Templates
141144

packages/create-gen-app/__tests__/create-gen.test.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,9 @@ describe('create-gen-app', () => {
9494
);
9595
});
9696

97-
it('should load questions from .questions.json', async () => {
98-
const questions = {
97+
it('should load questions from .boilerplate.json', async () => {
98+
const boilerplate = {
99+
type: 'module',
99100
questions: [
100101
{
101102
name: 'projectName',
@@ -111,8 +112,8 @@ describe('create-gen-app', () => {
111112
};
112113

113114
fs.writeFileSync(
114-
path.join(testTempDir, '.questions.json'),
115-
JSON.stringify(questions, null, 2)
115+
path.join(testTempDir, '.boilerplate.json'),
116+
JSON.stringify(boilerplate, null, 2)
116117
);
117118

118119
const result = await extractVariables(testTempDir);
@@ -122,9 +123,10 @@ describe('create-gen-app', () => {
122123
expect(result.projectQuestions?.questions[0].name).toBe('projectName');
123124
});
124125

125-
it('should load questions from .questions.js', async () => {
126-
const questionsContent = `
126+
it('should load questions from .boilerplate.js', async () => {
127+
const boilerplateContent = `
127128
module.exports = {
129+
type: 'module',
128130
questions: [
129131
{
130132
name: 'projectName',
@@ -136,8 +138,8 @@ module.exports = {
136138
`;
137139

138140
fs.writeFileSync(
139-
path.join(testTempDir, '.questions.js'),
140-
questionsContent
141+
path.join(testTempDir, '.boilerplate.js'),
142+
boilerplateContent
141143
);
142144

143145
const result = await extractVariables(testTempDir);
@@ -157,9 +159,9 @@ module.exports = {
157159
expect(result.projectQuestions).toBeNull();
158160
});
159161

160-
it('should skip .questions.json and .questions.js from variable extraction', async () => {
162+
it('should skip .boilerplate.json and .boilerplate.js from variable extraction', async () => {
161163
fs.writeFileSync(
162-
path.join(testTempDir, '.questions.json'),
164+
path.join(testTempDir, '.boilerplate.json'),
163165
'{"questions": [{"name": "____shouldNotExtract____"}]}'
164166
);
165167

@@ -486,10 +488,14 @@ module.exports = {
486488
expect(content).toBe('export const name = "auth";');
487489
});
488490

489-
it('should skip .questions.json and .questions.js files', async () => {
491+
it('should skip .boilerplate.json and .boilerplate.js files', async () => {
490492
fs.writeFileSync(
491-
path.join(testTempDir, '.questions.json'),
492-
'{"questions": []}'
493+
path.join(testTempDir, '.boilerplate.json'),
494+
'{"type": "module", "questions": []}'
495+
);
496+
fs.writeFileSync(
497+
path.join(testTempDir, '.boilerplates.json'),
498+
'{"dir": "default"}'
493499
);
494500
fs.writeFileSync(path.join(testTempDir, 'README.md'), 'Regular file');
495501

@@ -501,7 +507,10 @@ module.exports = {
501507
{}
502508
);
503509

504-
expect(fs.existsSync(path.join(testOutputDir, '.questions.json'))).toBe(
510+
expect(fs.existsSync(path.join(testOutputDir, '.boilerplate.json'))).toBe(
511+
false
512+
);
513+
expect(fs.existsSync(path.join(testOutputDir, '.boilerplates.json'))).toBe(
505514
false
506515
);
507516
expect(fs.existsSync(path.join(testOutputDir, 'README.md'))).toBe(true);

packages/create-gen-app/src/template/extract.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ export async function extractVariables(
3535
await walkDirectory(templateDir, async (filePath) => {
3636
const relativePath = path.relative(templateDir, filePath);
3737

38+
// Skip boilerplate configuration files from variable extraction
3839
if (
39-
relativePath === '.questions.json' ||
40-
relativePath === '.questions.js'
40+
relativePath === '.boilerplate.json' ||
41+
relativePath === '.boilerplate.js' ||
42+
relativePath === '.boilerplates.json'
4143
) {
4244
return;
4345
}
@@ -144,36 +146,40 @@ async function walkDirectory(
144146
}
145147

146148
/**
147-
* Load project questions from .questions.json or .questions.js
149+
* Load project questions from .boilerplate.json or .boilerplate.js
148150
* @param templateDir - Path to the template directory
149151
* @returns Questions object or null if not found
150152
*/
151153
async function loadProjectQuestions(
152154
templateDir: string
153155
): Promise<Questions | null> {
154-
const jsonPath = path.join(templateDir, '.questions.json');
156+
const jsonPath = path.join(templateDir, '.boilerplate.json');
155157
if (fs.existsSync(jsonPath)) {
156158
try {
157159
const content = fs.readFileSync(jsonPath, 'utf8');
158-
const questions = JSON.parse(content);
160+
const parsed = JSON.parse(content);
161+
// .boilerplate.json has { questions: [...] } structure
162+
const questions = parsed.questions ? { questions: parsed.questions } : parsed;
159163
return validateQuestions(questions) ? normalizeQuestions(questions) : null;
160164
} catch (error) {
161165
const errorMessage =
162166
error instanceof Error ? error.message : String(error);
163-
console.warn(`Failed to parse .questions.json: ${errorMessage}`);
167+
console.warn(`Failed to parse .boilerplate.json: ${errorMessage}`);
164168
}
165169
}
166170

167-
const jsPath = path.join(templateDir, '.questions.js');
171+
const jsPath = path.join(templateDir, '.boilerplate.js');
168172
if (fs.existsSync(jsPath)) {
169173
try {
170174
const module = require(jsPath);
171-
const questions = module.default || module;
175+
const exported = module.default || module;
176+
// .boilerplate.js can export { questions: [...] } or just the questions array
177+
const questions = exported.questions ? { questions: exported.questions } : exported;
172178
return validateQuestions(questions) ? normalizeQuestions(questions) : null;
173179
} catch (error) {
174180
const errorMessage =
175181
error instanceof Error ? error.message : String(error);
176-
console.warn(`Failed to load .questions.js: ${errorMessage}`);
182+
console.warn(`Failed to load .boilerplate.js: ${errorMessage}`);
177183
}
178184
}
179185

packages/create-gen-app/src/template/replace.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ async function walkAndReplace(
5656
for (const entry of entries) {
5757
const sourceEntryPath = path.join(currentSource, entry.name);
5858

59-
if (entry.name === '.questions.json' || entry.name === '.questions.js') {
59+
// Skip template configuration files - they should not be copied to output
60+
if (
61+
entry.name === '.boilerplate.json' ||
62+
entry.name === '.boilerplate.js' ||
63+
entry.name === '.boilerplates.json'
64+
) {
6065
continue;
6166
}
6267

packages/create-gen-app/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Question } from 'inquirerer';
22

33
/**
4-
* Questions configuration that can be loaded from .questions.json or .questions.js
4+
* Questions configuration that can be loaded from .boilerplate.json or .boilerplate.js
55
* @typedef {Object} Questions
66
* @property {Question[]} questions - Array of inquirerer questions
77
*/

0 commit comments

Comments
 (0)