create-gen-app 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -114,10 +114,11 @@ export const author = "____fullName____";
114
114
 
115
115
  ### Custom Questions
116
116
 
117
- Create a `.questions.json`:
117
+ Create a `.boilerplate.json`:
118
118
 
119
119
  ```json
120
120
  {
121
+ "type": "module",
121
122
  "questions": [
122
123
  {
123
124
  "name": "____fullName____",
@@ -135,7 +136,9 @@ Create a `.questions.json`:
135
136
  }
136
137
  ```
137
138
 
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.
139
142
 
140
143
  ### License Templates
141
144
 
package/esm/index.js CHANGED
@@ -1,21 +1,21 @@
1
- import { extractVariables } from "./template/extract";
2
- import { promptUser } from "./template/prompt";
3
- import { replaceVariables } from "./template/replace";
1
+ import { extractVariables } from './template/extract';
2
+ import { promptUser } from './template/prompt';
3
+ import { replaceVariables } from './template/replace';
4
4
  // Export new modular classes
5
- export * from "./cache/cache-manager";
6
- export * from "./cache/types";
7
- export * from "./git/git-cloner";
8
- export * from "./git/types";
9
- export * from "./template/templatizer";
10
- export * from "./template/types";
11
- export * from "./utils/npm-version-check";
12
- export * from "./utils/types";
5
+ export * from './cache/cache-manager';
6
+ export * from './cache/types';
7
+ export * from './git/git-cloner';
8
+ export * from './git/types';
9
+ export * from './template/templatizer';
10
+ export * from './template/types';
11
+ export * from './utils/npm-version-check';
12
+ export * from './utils/types';
13
13
  // Export template processing functions
14
- export * from "./template/extract";
15
- export * from "./template/prompt";
16
- export * from "./template/replace";
14
+ export * from './template/extract';
15
+ export * from './template/prompt';
16
+ export * from './template/replace';
17
17
  // Export shared types
18
- export * from "./types";
18
+ export * from './types';
19
19
  // DEPRECATED: Legacy exports for backward compatibility (will be removed in future)
20
20
  // Use CacheManager, GitCloner, and Templatizer classes instead
21
21
  export { extractVariables, promptUser, replaceVariables };
package/esm/licenses.js CHANGED
@@ -1,19 +1,19 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
3
  const PLACEHOLDER_PATTERN = /{{(\w+)}}/g;
4
4
  let cachedTemplates = null;
5
- export const LICENSE_VALUE_KEYS = ["LICENSE", "license"];
5
+ export const LICENSE_VALUE_KEYS = ['LICENSE', 'license'];
6
6
  export const LICENSE_AUTHOR_KEYS = [
7
- "USERFULLNAME",
8
- "AUTHOR",
9
- "AUTHORFULLNAME",
10
- "USERNAME",
11
- "fullName",
12
- "author",
13
- "authorFullName",
14
- "userName",
7
+ 'USERFULLNAME',
8
+ 'AUTHOR',
9
+ 'AUTHORFULLNAME',
10
+ 'USERNAME',
11
+ 'fullName',
12
+ 'author',
13
+ 'authorFullName',
14
+ 'userName',
15
15
  ];
16
- export const LICENSE_EMAIL_KEYS = ["USEREMAIL", "EMAIL", "email", "userEmail"];
16
+ export const LICENSE_EMAIL_KEYS = ['USEREMAIL', 'EMAIL', 'email', 'userEmail'];
17
17
  export function isSupportedLicense(name) {
18
18
  if (!name) {
19
19
  return false;
@@ -31,18 +31,18 @@ export function renderLicense(licenseName, context) {
31
31
  }
32
32
  const ctx = {
33
33
  year: context.year ?? new Date().getFullYear().toString(),
34
- author: context.author ?? "Unknown Author",
35
- email: context.email ?? "",
34
+ author: context.author ?? 'Unknown Author',
35
+ email: context.email ?? '',
36
36
  };
37
- const emailLine = ctx.email ? ` <${ctx.email}>` : "";
37
+ const emailLine = ctx.email ? ` <${ctx.email}>` : '';
38
38
  return template.replace(PLACEHOLDER_PATTERN, (_, rawKey) => {
39
39
  const key = rawKey.toUpperCase();
40
- if (key === "EMAIL_LINE") {
40
+ if (key === 'EMAIL_LINE') {
41
41
  return emailLine;
42
42
  }
43
43
  const normalizedKey = key.toLowerCase();
44
44
  const value = ctx[normalizedKey];
45
- return value || "";
45
+ return value || '';
46
46
  });
47
47
  }
48
48
  export function listSupportedLicenses() {
@@ -74,19 +74,19 @@ function loadLicenseTemplates() {
74
74
  if (!stats.isFile()) {
75
75
  continue;
76
76
  }
77
- if (path.extname(file).toLowerCase() !== ".txt") {
77
+ if (path.extname(file).toLowerCase() !== '.txt') {
78
78
  continue;
79
79
  }
80
80
  const key = path.basename(file, path.extname(file)).toUpperCase();
81
- templates[key] = fs.readFileSync(fullPath, "utf8");
81
+ templates[key] = fs.readFileSync(fullPath, 'utf8');
82
82
  }
83
83
  cachedTemplates = templates;
84
84
  return cachedTemplates;
85
85
  }
86
86
  function findTemplatesDir() {
87
87
  const candidates = [
88
- path.resolve(__dirname, "..", "licenses-templates"),
89
- path.resolve(__dirname, "licenses-templates"),
88
+ path.resolve(__dirname, '..', 'licenses-templates'),
89
+ path.resolve(__dirname, 'licenses-templates'),
90
90
  ];
91
91
  for (const candidate of candidates) {
92
92
  if (fs.existsSync(candidate)) {
@@ -98,7 +98,7 @@ function findTemplatesDir() {
98
98
  function getAnswerValue(answers, keys) {
99
99
  for (const key of keys) {
100
100
  const value = answers?.[key];
101
- if (typeof value === "string" && value.trim() !== "") {
101
+ if (typeof value === 'string' && value.trim() !== '') {
102
102
  return value;
103
103
  }
104
104
  }
@@ -1,10 +1,10 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- const PLACEHOLDER_BOUNDARY = "____";
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ const PLACEHOLDER_BOUNDARY = '____';
4
4
  /**
5
5
  * Pattern to match ____variable____ in filenames and content
6
6
  */
7
- const VARIABLE_PATTERN = new RegExp(`${PLACEHOLDER_BOUNDARY}([A-Za-z_][A-Za-z0-9_]*)${PLACEHOLDER_BOUNDARY}`, "g");
7
+ const VARIABLE_PATTERN = new RegExp(`${PLACEHOLDER_BOUNDARY}([A-Za-z_][A-Za-z0-9_]*)${PLACEHOLDER_BOUNDARY}`, 'g');
8
8
  /**
9
9
  * Extract all variables from a template directory
10
10
  * @param templateDir - Path to the template directory
@@ -18,8 +18,10 @@ export async function extractVariables(templateDir) {
18
18
  const projectQuestions = await loadProjectQuestions(templateDir);
19
19
  await walkDirectory(templateDir, async (filePath) => {
20
20
  const relativePath = path.relative(templateDir, filePath);
21
- if (relativePath === ".questions.json" ||
22
- relativePath === ".questions.js") {
21
+ // Skip boilerplate configuration files from variable extraction
22
+ if (relativePath === '.boilerplate.json' ||
23
+ relativePath === '.boilerplate.js' ||
24
+ relativePath === '.boilerplates.json') {
23
25
  return;
24
26
  }
25
27
  const matches = relativePath.matchAll(VARIABLE_PATTERN);
@@ -29,7 +31,7 @@ export async function extractVariables(templateDir) {
29
31
  fileReplacerVars.add(varName);
30
32
  fileReplacers.push({
31
33
  variable: varName,
32
- pattern: new RegExp(`${PLACEHOLDER_BOUNDARY}${varName}${PLACEHOLDER_BOUNDARY}`, "g"),
34
+ pattern: new RegExp(`${PLACEHOLDER_BOUNDARY}${varName}${PLACEHOLDER_BOUNDARY}`, 'g'),
33
35
  });
34
36
  }
35
37
  }
@@ -39,7 +41,7 @@ export async function extractVariables(templateDir) {
39
41
  contentReplacerVars.add(varName);
40
42
  contentReplacers.push({
41
43
  variable: varName,
42
- pattern: new RegExp(`${PLACEHOLDER_BOUNDARY}${varName}${PLACEHOLDER_BOUNDARY}`, "g"),
44
+ pattern: new RegExp(`${PLACEHOLDER_BOUNDARY}${varName}${PLACEHOLDER_BOUNDARY}`, 'g'),
43
45
  });
44
46
  }
45
47
  }
@@ -58,12 +60,12 @@ export async function extractVariables(templateDir) {
58
60
  async function extractFromFileContent(filePath) {
59
61
  const variables = new Set();
60
62
  return new Promise((resolve) => {
61
- const stream = fs.createReadStream(filePath, { encoding: "utf8" });
62
- let buffer = "";
63
- stream.on("data", (chunk) => {
63
+ const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
64
+ let buffer = '';
65
+ stream.on('data', (chunk) => {
64
66
  buffer += chunk.toString();
65
- const lines = buffer.split("\n");
66
- buffer = lines.pop() || ""; // Keep the last incomplete line in buffer
67
+ const lines = buffer.split('\n');
68
+ buffer = lines.pop() || ''; // Keep the last incomplete line in buffer
67
69
  for (const line of lines) {
68
70
  const matches = line.matchAll(VARIABLE_PATTERN);
69
71
  for (const match of matches) {
@@ -71,14 +73,14 @@ async function extractFromFileContent(filePath) {
71
73
  }
72
74
  }
73
75
  });
74
- stream.on("end", () => {
76
+ stream.on('end', () => {
75
77
  const matches = buffer.matchAll(VARIABLE_PATTERN);
76
78
  for (const match of matches) {
77
79
  variables.add(match[1]);
78
80
  }
79
81
  resolve(variables);
80
82
  });
81
- stream.on("error", () => {
83
+ stream.on('error', () => {
82
84
  resolve(variables);
83
85
  });
84
86
  });
@@ -101,33 +103,37 @@ async function walkDirectory(dir, callback) {
101
103
  }
102
104
  }
103
105
  /**
104
- * Load project questions from .questions.json or .questions.js
106
+ * Load project questions from .boilerplate.json or .boilerplate.js
105
107
  * @param templateDir - Path to the template directory
106
108
  * @returns Questions object or null if not found
107
109
  */
108
110
  async function loadProjectQuestions(templateDir) {
109
- const jsonPath = path.join(templateDir, ".questions.json");
111
+ const jsonPath = path.join(templateDir, '.boilerplate.json');
110
112
  if (fs.existsSync(jsonPath)) {
111
113
  try {
112
- const content = fs.readFileSync(jsonPath, "utf8");
113
- const questions = JSON.parse(content);
114
+ const content = fs.readFileSync(jsonPath, 'utf8');
115
+ const parsed = JSON.parse(content);
116
+ // .boilerplate.json has { questions: [...] } structure
117
+ const questions = parsed.questions ? { questions: parsed.questions } : parsed;
114
118
  return validateQuestions(questions) ? normalizeQuestions(questions) : null;
115
119
  }
116
120
  catch (error) {
117
121
  const errorMessage = error instanceof Error ? error.message : String(error);
118
- console.warn(`Failed to parse .questions.json: ${errorMessage}`);
122
+ console.warn(`Failed to parse .boilerplate.json: ${errorMessage}`);
119
123
  }
120
124
  }
121
- const jsPath = path.join(templateDir, ".questions.js");
125
+ const jsPath = path.join(templateDir, '.boilerplate.js');
122
126
  if (fs.existsSync(jsPath)) {
123
127
  try {
124
128
  const module = require(jsPath);
125
- const questions = module.default || module;
129
+ const exported = module.default || module;
130
+ // .boilerplate.js can export { questions: [...] } or just the questions array
131
+ const questions = exported.questions ? { questions: exported.questions } : exported;
126
132
  return validateQuestions(questions) ? normalizeQuestions(questions) : null;
127
133
  }
128
134
  catch (error) {
129
135
  const errorMessage = error instanceof Error ? error.message : String(error);
130
- console.warn(`Failed to load .questions.js: ${errorMessage}`);
136
+ console.warn(`Failed to load .boilerplate.js: ${errorMessage}`);
131
137
  }
132
138
  }
133
139
  return null;
@@ -152,5 +158,5 @@ function normalizeQuestions(questions) {
152
158
  * @returns True if valid, false otherwise
153
159
  */
154
160
  function validateQuestions(obj) {
155
- return obj && typeof obj === "object" && Array.isArray(obj.questions);
161
+ return obj && typeof obj === 'object' && Array.isArray(obj.questions);
156
162
  }
@@ -1,5 +1,5 @@
1
- import { Inquirerer } from "inquirerer";
2
- const PLACEHOLDER_BOUNDARY = "____";
1
+ import { Inquirerer } from 'inquirerer';
2
+ const PLACEHOLDER_BOUNDARY = '____';
3
3
  /**
4
4
  * Generate questions from extracted variables
5
5
  * @param extractedVariables - Variables extracted from the template
@@ -20,7 +20,7 @@ export function generateQuestions(extractedVariables) {
20
20
  if (!askedVariables.has(replacer.variable)) {
21
21
  questions.push({
22
22
  name: replacer.variable,
23
- type: "text",
23
+ type: 'text',
24
24
  message: `Enter value for ${replacer.variable}:`,
25
25
  required: true
26
26
  });
@@ -31,7 +31,7 @@ export function generateQuestions(extractedVariables) {
31
31
  if (!askedVariables.has(replacer.variable)) {
32
32
  questions.push({
33
33
  name: replacer.variable,
34
- type: "text",
34
+ type: 'text',
35
35
  message: `Enter value for ${replacer.variable}:`,
36
36
  required: true
37
37
  });
@@ -45,7 +45,7 @@ function normalizeQuestionName(name) {
45
45
  name.endsWith(PLACEHOLDER_BOUNDARY)) {
46
46
  return name.slice(PLACEHOLDER_BOUNDARY.length, -PLACEHOLDER_BOUNDARY.length);
47
47
  }
48
- if (name.startsWith("__") && name.endsWith("__")) {
48
+ if (name.startsWith('__') && name.endsWith('__')) {
49
49
  return name.slice(2, -2);
50
50
  }
51
51
  return name;
@@ -74,7 +74,7 @@ export async function promptUser(extractedVariables, argv = {}, noTty = false) {
74
74
  };
75
75
  }
76
76
  finally {
77
- if (typeof prompter.close === "function") {
77
+ if (typeof prompter.close === 'function') {
78
78
  prompter.close();
79
79
  }
80
80
  }
@@ -31,7 +31,10 @@ async function walkAndReplace(sourceDir, destDir, extractedVariables, answers, s
31
31
  const entries = fs.readdirSync(currentSource, { withFileTypes: true });
32
32
  for (const entry of entries) {
33
33
  const sourceEntryPath = path.join(currentSource, entry.name);
34
- if (entry.name === '.questions.json' || entry.name === '.questions.js') {
34
+ // Skip template configuration files - they should not be copied to output
35
+ if (entry.name === '.boilerplate.json' ||
36
+ entry.name === '.boilerplate.js' ||
37
+ entry.name === '.boilerplates.json') {
35
38
  continue;
36
39
  }
37
40
  let newName = entry.name;
@@ -62,8 +65,8 @@ async function ensureLicenseFile(outputDir, answers) {
62
65
  console.warn(`[create-gen-app] License "${selectedLicense}" is not supported by the built-in templates. Leaving template LICENSE file as-is.`);
63
66
  return;
64
67
  }
65
- const author = findLicenseAuthor(answers) ?? "Unknown Author";
66
- const email = findLicenseEmail(answers) ?? "";
68
+ const author = findLicenseAuthor(answers) ?? 'Unknown Author';
69
+ const email = findLicenseEmail(answers) ?? '';
67
70
  const content = renderLicense(selectedLicense, {
68
71
  author: String(author),
69
72
  email: String(email || ''),
package/index.d.ts CHANGED
@@ -1,19 +1,19 @@
1
- import { extractVariables } from "./template/extract";
2
- import { promptUser } from "./template/prompt";
3
- import { replaceVariables } from "./template/replace";
4
- import { CreateGenOptions } from "./types";
5
- export * from "./cache/cache-manager";
6
- export * from "./cache/types";
7
- export * from "./git/git-cloner";
8
- export * from "./git/types";
9
- export * from "./template/templatizer";
10
- export * from "./template/types";
11
- export * from "./utils/npm-version-check";
12
- export * from "./utils/types";
13
- export * from "./template/extract";
14
- export * from "./template/prompt";
15
- export * from "./template/replace";
16
- export * from "./types";
1
+ import { extractVariables } from './template/extract';
2
+ import { promptUser } from './template/prompt';
3
+ import { replaceVariables } from './template/replace';
4
+ import { CreateGenOptions } from './types';
5
+ export * from './cache/cache-manager';
6
+ export * from './cache/types';
7
+ export * from './git/git-cloner';
8
+ export * from './git/types';
9
+ export * from './template/templatizer';
10
+ export * from './template/types';
11
+ export * from './utils/npm-version-check';
12
+ export * from './utils/types';
13
+ export * from './template/extract';
14
+ export * from './template/prompt';
15
+ export * from './template/replace';
16
+ export * from './types';
17
17
  export { extractVariables, promptUser, replaceVariables };
18
18
  /**
19
19
  * @deprecated This function is deprecated and will be removed in the next major version.
package/licenses.js CHANGED
@@ -44,18 +44,18 @@ const fs = __importStar(require("fs"));
44
44
  const path = __importStar(require("path"));
45
45
  const PLACEHOLDER_PATTERN = /{{(\w+)}}/g;
46
46
  let cachedTemplates = null;
47
- exports.LICENSE_VALUE_KEYS = ["LICENSE", "license"];
47
+ exports.LICENSE_VALUE_KEYS = ['LICENSE', 'license'];
48
48
  exports.LICENSE_AUTHOR_KEYS = [
49
- "USERFULLNAME",
50
- "AUTHOR",
51
- "AUTHORFULLNAME",
52
- "USERNAME",
53
- "fullName",
54
- "author",
55
- "authorFullName",
56
- "userName",
49
+ 'USERFULLNAME',
50
+ 'AUTHOR',
51
+ 'AUTHORFULLNAME',
52
+ 'USERNAME',
53
+ 'fullName',
54
+ 'author',
55
+ 'authorFullName',
56
+ 'userName',
57
57
  ];
58
- exports.LICENSE_EMAIL_KEYS = ["USEREMAIL", "EMAIL", "email", "userEmail"];
58
+ exports.LICENSE_EMAIL_KEYS = ['USEREMAIL', 'EMAIL', 'email', 'userEmail'];
59
59
  function isSupportedLicense(name) {
60
60
  if (!name) {
61
61
  return false;
@@ -73,18 +73,18 @@ function renderLicense(licenseName, context) {
73
73
  }
74
74
  const ctx = {
75
75
  year: context.year ?? new Date().getFullYear().toString(),
76
- author: context.author ?? "Unknown Author",
77
- email: context.email ?? "",
76
+ author: context.author ?? 'Unknown Author',
77
+ email: context.email ?? '',
78
78
  };
79
- const emailLine = ctx.email ? ` <${ctx.email}>` : "";
79
+ const emailLine = ctx.email ? ` <${ctx.email}>` : '';
80
80
  return template.replace(PLACEHOLDER_PATTERN, (_, rawKey) => {
81
81
  const key = rawKey.toUpperCase();
82
- if (key === "EMAIL_LINE") {
82
+ if (key === 'EMAIL_LINE') {
83
83
  return emailLine;
84
84
  }
85
85
  const normalizedKey = key.toLowerCase();
86
86
  const value = ctx[normalizedKey];
87
- return value || "";
87
+ return value || '';
88
88
  });
89
89
  }
90
90
  function listSupportedLicenses() {
@@ -116,19 +116,19 @@ function loadLicenseTemplates() {
116
116
  if (!stats.isFile()) {
117
117
  continue;
118
118
  }
119
- if (path.extname(file).toLowerCase() !== ".txt") {
119
+ if (path.extname(file).toLowerCase() !== '.txt') {
120
120
  continue;
121
121
  }
122
122
  const key = path.basename(file, path.extname(file)).toUpperCase();
123
- templates[key] = fs.readFileSync(fullPath, "utf8");
123
+ templates[key] = fs.readFileSync(fullPath, 'utf8');
124
124
  }
125
125
  cachedTemplates = templates;
126
126
  return cachedTemplates;
127
127
  }
128
128
  function findTemplatesDir() {
129
129
  const candidates = [
130
- path.resolve(__dirname, "..", "licenses-templates"),
131
- path.resolve(__dirname, "licenses-templates"),
130
+ path.resolve(__dirname, '..', 'licenses-templates'),
131
+ path.resolve(__dirname, 'licenses-templates'),
132
132
  ];
133
133
  for (const candidate of candidates) {
134
134
  if (fs.existsSync(candidate)) {
@@ -140,7 +140,7 @@ function findTemplatesDir() {
140
140
  function getAnswerValue(answers, keys) {
141
141
  for (const key of keys) {
142
142
  const value = answers?.[key];
143
- if (typeof value === "string" && value.trim() !== "") {
143
+ if (typeof value === 'string' && value.trim() !== '') {
144
144
  return value;
145
145
  }
146
146
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-gen-app",
3
- "version": "0.3.5",
4
- "author": "Constructive <pyramation@constructive.io>",
3
+ "version": "0.4.0",
4
+ "author": "Constructive <developers@constructive.io>",
5
5
  "description": "Clone and customize template repositories with variable replacement",
6
6
  "main": "index.js",
7
7
  "module": "esm/index.js",
@@ -28,13 +28,13 @@
28
28
  "test:watch": "jest --watch"
29
29
  },
30
30
  "dependencies": {
31
- "appstash": "0.2.5",
32
- "inquirerer": "2.1.13"
31
+ "appstash": "0.2.6",
32
+ "inquirerer": "2.1.14"
33
33
  },
34
34
  "devDependencies": {
35
35
  "copyfiles": "^2.4.1",
36
36
  "makage": "0.1.8"
37
37
  },
38
38
  "keywords": [],
39
- "gitHead": "b22eaa525718494342576c1739b2b062c0ad6498"
39
+ "gitHead": "9aac458b3809f4f800af22120337291041fe2144"
40
40
  }
@@ -1,4 +1,4 @@
1
- import { ExtractedVariables } from "../types";
1
+ import { ExtractedVariables } from '../types';
2
2
  /**
3
3
  * Extract all variables from a template directory
4
4
  * @param templateDir - Path to the template directory
@@ -36,11 +36,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.extractVariables = extractVariables;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
- const PLACEHOLDER_BOUNDARY = "____";
39
+ const PLACEHOLDER_BOUNDARY = '____';
40
40
  /**
41
41
  * Pattern to match ____variable____ in filenames and content
42
42
  */
43
- const VARIABLE_PATTERN = new RegExp(`${PLACEHOLDER_BOUNDARY}([A-Za-z_][A-Za-z0-9_]*)${PLACEHOLDER_BOUNDARY}`, "g");
43
+ const VARIABLE_PATTERN = new RegExp(`${PLACEHOLDER_BOUNDARY}([A-Za-z_][A-Za-z0-9_]*)${PLACEHOLDER_BOUNDARY}`, 'g');
44
44
  /**
45
45
  * Extract all variables from a template directory
46
46
  * @param templateDir - Path to the template directory
@@ -54,8 +54,10 @@ async function extractVariables(templateDir) {
54
54
  const projectQuestions = await loadProjectQuestions(templateDir);
55
55
  await walkDirectory(templateDir, async (filePath) => {
56
56
  const relativePath = path.relative(templateDir, filePath);
57
- if (relativePath === ".questions.json" ||
58
- relativePath === ".questions.js") {
57
+ // Skip boilerplate configuration files from variable extraction
58
+ if (relativePath === '.boilerplate.json' ||
59
+ relativePath === '.boilerplate.js' ||
60
+ relativePath === '.boilerplates.json') {
59
61
  return;
60
62
  }
61
63
  const matches = relativePath.matchAll(VARIABLE_PATTERN);
@@ -65,7 +67,7 @@ async function extractVariables(templateDir) {
65
67
  fileReplacerVars.add(varName);
66
68
  fileReplacers.push({
67
69
  variable: varName,
68
- pattern: new RegExp(`${PLACEHOLDER_BOUNDARY}${varName}${PLACEHOLDER_BOUNDARY}`, "g"),
70
+ pattern: new RegExp(`${PLACEHOLDER_BOUNDARY}${varName}${PLACEHOLDER_BOUNDARY}`, 'g'),
69
71
  });
70
72
  }
71
73
  }
@@ -75,7 +77,7 @@ async function extractVariables(templateDir) {
75
77
  contentReplacerVars.add(varName);
76
78
  contentReplacers.push({
77
79
  variable: varName,
78
- pattern: new RegExp(`${PLACEHOLDER_BOUNDARY}${varName}${PLACEHOLDER_BOUNDARY}`, "g"),
80
+ pattern: new RegExp(`${PLACEHOLDER_BOUNDARY}${varName}${PLACEHOLDER_BOUNDARY}`, 'g'),
79
81
  });
80
82
  }
81
83
  }
@@ -94,12 +96,12 @@ async function extractVariables(templateDir) {
94
96
  async function extractFromFileContent(filePath) {
95
97
  const variables = new Set();
96
98
  return new Promise((resolve) => {
97
- const stream = fs.createReadStream(filePath, { encoding: "utf8" });
98
- let buffer = "";
99
- stream.on("data", (chunk) => {
99
+ const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
100
+ let buffer = '';
101
+ stream.on('data', (chunk) => {
100
102
  buffer += chunk.toString();
101
- const lines = buffer.split("\n");
102
- buffer = lines.pop() || ""; // Keep the last incomplete line in buffer
103
+ const lines = buffer.split('\n');
104
+ buffer = lines.pop() || ''; // Keep the last incomplete line in buffer
103
105
  for (const line of lines) {
104
106
  const matches = line.matchAll(VARIABLE_PATTERN);
105
107
  for (const match of matches) {
@@ -107,14 +109,14 @@ async function extractFromFileContent(filePath) {
107
109
  }
108
110
  }
109
111
  });
110
- stream.on("end", () => {
112
+ stream.on('end', () => {
111
113
  const matches = buffer.matchAll(VARIABLE_PATTERN);
112
114
  for (const match of matches) {
113
115
  variables.add(match[1]);
114
116
  }
115
117
  resolve(variables);
116
118
  });
117
- stream.on("error", () => {
119
+ stream.on('error', () => {
118
120
  resolve(variables);
119
121
  });
120
122
  });
@@ -137,33 +139,37 @@ async function walkDirectory(dir, callback) {
137
139
  }
138
140
  }
139
141
  /**
140
- * Load project questions from .questions.json or .questions.js
142
+ * Load project questions from .boilerplate.json or .boilerplate.js
141
143
  * @param templateDir - Path to the template directory
142
144
  * @returns Questions object or null if not found
143
145
  */
144
146
  async function loadProjectQuestions(templateDir) {
145
- const jsonPath = path.join(templateDir, ".questions.json");
147
+ const jsonPath = path.join(templateDir, '.boilerplate.json');
146
148
  if (fs.existsSync(jsonPath)) {
147
149
  try {
148
- const content = fs.readFileSync(jsonPath, "utf8");
149
- const questions = JSON.parse(content);
150
+ const content = fs.readFileSync(jsonPath, 'utf8');
151
+ const parsed = JSON.parse(content);
152
+ // .boilerplate.json has { questions: [...] } structure
153
+ const questions = parsed.questions ? { questions: parsed.questions } : parsed;
150
154
  return validateQuestions(questions) ? normalizeQuestions(questions) : null;
151
155
  }
152
156
  catch (error) {
153
157
  const errorMessage = error instanceof Error ? error.message : String(error);
154
- console.warn(`Failed to parse .questions.json: ${errorMessage}`);
158
+ console.warn(`Failed to parse .boilerplate.json: ${errorMessage}`);
155
159
  }
156
160
  }
157
- const jsPath = path.join(templateDir, ".questions.js");
161
+ const jsPath = path.join(templateDir, '.boilerplate.js');
158
162
  if (fs.existsSync(jsPath)) {
159
163
  try {
160
164
  const module = require(jsPath);
161
- const questions = module.default || module;
165
+ const exported = module.default || module;
166
+ // .boilerplate.js can export { questions: [...] } or just the questions array
167
+ const questions = exported.questions ? { questions: exported.questions } : exported;
162
168
  return validateQuestions(questions) ? normalizeQuestions(questions) : null;
163
169
  }
164
170
  catch (error) {
165
171
  const errorMessage = error instanceof Error ? error.message : String(error);
166
- console.warn(`Failed to load .questions.js: ${errorMessage}`);
172
+ console.warn(`Failed to load .boilerplate.js: ${errorMessage}`);
167
173
  }
168
174
  }
169
175
  return null;
@@ -188,5 +194,5 @@ function normalizeQuestions(questions) {
188
194
  * @returns True if valid, false otherwise
189
195
  */
190
196
  function validateQuestions(obj) {
191
- return obj && typeof obj === "object" && Array.isArray(obj.questions);
197
+ return obj && typeof obj === 'object' && Array.isArray(obj.questions);
192
198
  }
@@ -1,5 +1,5 @@
1
- import { Question } from "inquirerer";
2
- import { ExtractedVariables } from "../types";
1
+ import { Question } from 'inquirerer';
2
+ import { ExtractedVariables } from '../types';
3
3
  /**
4
4
  * Generate questions from extracted variables
5
5
  * @param extractedVariables - Variables extracted from the template
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateQuestions = generateQuestions;
4
4
  exports.promptUser = promptUser;
5
5
  const inquirerer_1 = require("inquirerer");
6
- const PLACEHOLDER_BOUNDARY = "____";
6
+ const PLACEHOLDER_BOUNDARY = '____';
7
7
  /**
8
8
  * Generate questions from extracted variables
9
9
  * @param extractedVariables - Variables extracted from the template
@@ -24,7 +24,7 @@ function generateQuestions(extractedVariables) {
24
24
  if (!askedVariables.has(replacer.variable)) {
25
25
  questions.push({
26
26
  name: replacer.variable,
27
- type: "text",
27
+ type: 'text',
28
28
  message: `Enter value for ${replacer.variable}:`,
29
29
  required: true
30
30
  });
@@ -35,7 +35,7 @@ function generateQuestions(extractedVariables) {
35
35
  if (!askedVariables.has(replacer.variable)) {
36
36
  questions.push({
37
37
  name: replacer.variable,
38
- type: "text",
38
+ type: 'text',
39
39
  message: `Enter value for ${replacer.variable}:`,
40
40
  required: true
41
41
  });
@@ -49,7 +49,7 @@ function normalizeQuestionName(name) {
49
49
  name.endsWith(PLACEHOLDER_BOUNDARY)) {
50
50
  return name.slice(PLACEHOLDER_BOUNDARY.length, -PLACEHOLDER_BOUNDARY.length);
51
51
  }
52
- if (name.startsWith("__") && name.endsWith("__")) {
52
+ if (name.startsWith('__') && name.endsWith('__')) {
53
53
  return name.slice(2, -2);
54
54
  }
55
55
  return name;
@@ -78,7 +78,7 @@ async function promptUser(extractedVariables, argv = {}, noTty = false) {
78
78
  };
79
79
  }
80
80
  finally {
81
- if (typeof prompter.close === "function") {
81
+ if (typeof prompter.close === 'function') {
82
82
  prompter.close();
83
83
  }
84
84
  }
@@ -67,7 +67,10 @@ async function walkAndReplace(sourceDir, destDir, extractedVariables, answers, s
67
67
  const entries = fs.readdirSync(currentSource, { withFileTypes: true });
68
68
  for (const entry of entries) {
69
69
  const sourceEntryPath = path.join(currentSource, entry.name);
70
- if (entry.name === '.questions.json' || entry.name === '.questions.js') {
70
+ // Skip template configuration files - they should not be copied to output
71
+ if (entry.name === '.boilerplate.json' ||
72
+ entry.name === '.boilerplate.js' ||
73
+ entry.name === '.boilerplates.json') {
71
74
  continue;
72
75
  }
73
76
  let newName = entry.name;
@@ -98,8 +101,8 @@ async function ensureLicenseFile(outputDir, answers) {
98
101
  console.warn(`[create-gen-app] License "${selectedLicense}" is not supported by the built-in templates. Leaving template LICENSE file as-is.`);
99
102
  return;
100
103
  }
101
- const author = (0, licenses_1.findLicenseAuthor)(answers) ?? "Unknown Author";
102
- const email = (0, licenses_1.findLicenseEmail)(answers) ?? "";
104
+ const author = (0, licenses_1.findLicenseAuthor)(answers) ?? 'Unknown Author';
105
+ const email = (0, licenses_1.findLicenseEmail)(answers) ?? '';
103
106
  const content = (0, licenses_1.renderLicense)(selectedLicense, {
104
107
  author: String(author),
105
108
  email: String(email || ''),
package/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Question } from 'inquirerer';
2
2
  /**
3
- * Questions configuration that can be loaded from .questions.json or .questions.js
3
+ * Questions configuration that can be loaded from .boilerplate.json or .boilerplate.js
4
4
  * @typedef {Object} Questions
5
5
  * @property {Question[]} questions - Array of inquirerer questions
6
6
  */