genomic 4.0.2 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +154 -1125
  2. package/cache/cache-manager.d.ts +60 -0
  3. package/cache/cache-manager.js +228 -0
  4. package/cache/types.d.ts +22 -0
  5. package/esm/cache/cache-manager.js +191 -0
  6. package/esm/git/git-cloner.js +92 -0
  7. package/esm/index.js +41 -4
  8. package/esm/licenses.js +120 -0
  9. package/esm/scaffolder/index.js +2 -0
  10. package/esm/scaffolder/template-scaffolder.js +310 -0
  11. package/esm/scaffolder/types.js +1 -0
  12. package/esm/template/extract.js +162 -0
  13. package/esm/template/prompt.js +103 -0
  14. package/esm/template/replace.js +110 -0
  15. package/esm/template/templatizer.js +73 -0
  16. package/esm/template/types.js +1 -0
  17. package/esm/types.js +1 -0
  18. package/esm/utils/npm-version-check.js +52 -0
  19. package/esm/utils/types.js +1 -0
  20. package/git/git-cloner.d.ts +32 -0
  21. package/git/git-cloner.js +129 -0
  22. package/git/types.d.ts +15 -0
  23. package/index.d.ts +29 -4
  24. package/index.js +43 -4
  25. package/licenses-templates/APACHE-2.0.txt +18 -0
  26. package/licenses-templates/BSD-3-CLAUSE.txt +28 -0
  27. package/licenses-templates/CLOSED.txt +20 -0
  28. package/licenses-templates/GPL-3.0.txt +18 -0
  29. package/licenses-templates/ISC.txt +16 -0
  30. package/licenses-templates/MIT.txt +22 -0
  31. package/licenses-templates/MPL-2.0.txt +8 -0
  32. package/licenses-templates/UNLICENSE.txt +22 -0
  33. package/licenses.d.ts +18 -0
  34. package/licenses.js +162 -0
  35. package/package.json +9 -14
  36. package/scaffolder/index.d.ts +2 -0
  37. package/{question → scaffolder}/index.js +1 -0
  38. package/scaffolder/template-scaffolder.d.ts +91 -0
  39. package/scaffolder/template-scaffolder.js +347 -0
  40. package/scaffolder/types.d.ts +191 -0
  41. package/scaffolder/types.js +2 -0
  42. package/template/extract.d.ts +7 -0
  43. package/template/extract.js +198 -0
  44. package/template/prompt.d.ts +19 -0
  45. package/template/prompt.js +107 -0
  46. package/template/replace.d.ts +9 -0
  47. package/template/replace.js +146 -0
  48. package/template/templatizer.d.ts +33 -0
  49. package/template/templatizer.js +110 -0
  50. package/template/types.d.ts +18 -0
  51. package/template/types.js +2 -0
  52. package/types.d.ts +99 -0
  53. package/types.js +2 -0
  54. package/utils/npm-version-check.d.ts +17 -0
  55. package/utils/npm-version-check.js +57 -0
  56. package/utils/types.d.ts +6 -0
  57. package/utils/types.js +2 -0
  58. package/commander.d.ts +0 -21
  59. package/commander.js +0 -57
  60. package/esm/commander.js +0 -50
  61. package/esm/keypress.js +0 -95
  62. package/esm/prompt.js +0 -1024
  63. package/esm/question/index.js +0 -1
  64. package/esm/resolvers/date.js +0 -11
  65. package/esm/resolvers/git.js +0 -26
  66. package/esm/resolvers/index.js +0 -103
  67. package/esm/resolvers/npm.js +0 -24
  68. package/esm/resolvers/workspace.js +0 -141
  69. package/esm/utils.js +0 -12
  70. package/keypress.d.ts +0 -45
  71. package/keypress.js +0 -99
  72. package/prompt.d.ts +0 -116
  73. package/prompt.js +0 -1032
  74. package/question/index.d.ts +0 -1
  75. package/question/types.d.ts +0 -65
  76. package/resolvers/date.d.ts +0 -5
  77. package/resolvers/date.js +0 -14
  78. package/resolvers/git.d.ts +0 -11
  79. package/resolvers/git.js +0 -30
  80. package/resolvers/index.d.ts +0 -63
  81. package/resolvers/index.js +0 -111
  82. package/resolvers/npm.d.ts +0 -10
  83. package/resolvers/npm.js +0 -28
  84. package/resolvers/types.d.ts +0 -12
  85. package/resolvers/workspace.d.ts +0 -6
  86. package/resolvers/workspace.js +0 -144
  87. package/utils.d.ts +0 -2
  88. package/utils.js +0 -16
  89. /package/{question → cache}/types.js +0 -0
  90. /package/esm/{question → cache}/types.js +0 -0
  91. /package/esm/{resolvers → git}/types.js +0 -0
  92. /package/{resolvers → git}/types.js +0 -0
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.extractVariables = extractVariables;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const PLACEHOLDER_BOUNDARY = '____';
40
+ /**
41
+ * Pattern to match ____variable____ in filenames and content
42
+ */
43
+ const VARIABLE_PATTERN = new RegExp(`${PLACEHOLDER_BOUNDARY}([A-Za-z_][A-Za-z0-9_]*)${PLACEHOLDER_BOUNDARY}`, 'g');
44
+ /**
45
+ * Extract all variables from a template directory
46
+ * @param templateDir - Path to the template directory
47
+ * @returns Extracted variables including file replacers, content replacers, and project questions
48
+ */
49
+ async function extractVariables(templateDir) {
50
+ const fileReplacers = [];
51
+ const contentReplacers = [];
52
+ const fileReplacerVars = new Set();
53
+ const contentReplacerVars = new Set();
54
+ const projectQuestions = await loadProjectQuestions(templateDir);
55
+ await walkDirectory(templateDir, async (filePath) => {
56
+ const relativePath = path.relative(templateDir, filePath);
57
+ // Skip boilerplate configuration files from variable extraction
58
+ if (relativePath === '.boilerplate.json' ||
59
+ relativePath === '.boilerplate.js' ||
60
+ relativePath === '.boilerplates.json') {
61
+ return;
62
+ }
63
+ const matches = relativePath.matchAll(VARIABLE_PATTERN);
64
+ for (const match of matches) {
65
+ const varName = match[1];
66
+ if (!fileReplacerVars.has(varName)) {
67
+ fileReplacerVars.add(varName);
68
+ fileReplacers.push({
69
+ variable: varName,
70
+ pattern: new RegExp(`${PLACEHOLDER_BOUNDARY}${varName}${PLACEHOLDER_BOUNDARY}`, 'g'),
71
+ });
72
+ }
73
+ }
74
+ const contentVars = await extractFromFileContent(filePath);
75
+ for (const varName of contentVars) {
76
+ if (!contentReplacerVars.has(varName)) {
77
+ contentReplacerVars.add(varName);
78
+ contentReplacers.push({
79
+ variable: varName,
80
+ pattern: new RegExp(`${PLACEHOLDER_BOUNDARY}${varName}${PLACEHOLDER_BOUNDARY}`, 'g'),
81
+ });
82
+ }
83
+ }
84
+ });
85
+ return {
86
+ fileReplacers,
87
+ contentReplacers,
88
+ projectQuestions,
89
+ };
90
+ }
91
+ /**
92
+ * Extract variables from file content using streams
93
+ * @param filePath - Path to the file
94
+ * @returns Set of variable names found in the file
95
+ */
96
+ async function extractFromFileContent(filePath) {
97
+ const variables = new Set();
98
+ return new Promise((resolve) => {
99
+ const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
100
+ let buffer = '';
101
+ stream.on('data', (chunk) => {
102
+ buffer += chunk.toString();
103
+ const lines = buffer.split('\n');
104
+ buffer = lines.pop() || ''; // Keep the last incomplete line in buffer
105
+ for (const line of lines) {
106
+ const matches = line.matchAll(VARIABLE_PATTERN);
107
+ for (const match of matches) {
108
+ variables.add(match[1]);
109
+ }
110
+ }
111
+ });
112
+ stream.on('end', () => {
113
+ const matches = buffer.matchAll(VARIABLE_PATTERN);
114
+ for (const match of matches) {
115
+ variables.add(match[1]);
116
+ }
117
+ resolve(variables);
118
+ });
119
+ stream.on('error', () => {
120
+ resolve(variables);
121
+ });
122
+ });
123
+ }
124
+ /**
125
+ * Walk through a directory recursively
126
+ * @param dir - Directory to walk
127
+ * @param callback - Callback function for each file
128
+ */
129
+ async function walkDirectory(dir, callback) {
130
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
131
+ for (const entry of entries) {
132
+ const fullPath = path.join(dir, entry.name);
133
+ if (entry.isDirectory()) {
134
+ await walkDirectory(fullPath, callback);
135
+ }
136
+ else if (entry.isFile()) {
137
+ await callback(fullPath);
138
+ }
139
+ }
140
+ }
141
+ /**
142
+ * Load project questions from .boilerplate.json or .boilerplate.js
143
+ * @param templateDir - Path to the template directory
144
+ * @returns Questions object or null if not found
145
+ */
146
+ async function loadProjectQuestions(templateDir) {
147
+ const jsonPath = path.join(templateDir, '.boilerplate.json');
148
+ if (fs.existsSync(jsonPath)) {
149
+ try {
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;
154
+ return validateQuestions(questions) ? normalizeQuestions(questions) : null;
155
+ }
156
+ catch (error) {
157
+ const errorMessage = error instanceof Error ? error.message : String(error);
158
+ console.warn(`Failed to parse .boilerplate.json: ${errorMessage}`);
159
+ }
160
+ }
161
+ const jsPath = path.join(templateDir, '.boilerplate.js');
162
+ if (fs.existsSync(jsPath)) {
163
+ try {
164
+ const module = require(jsPath);
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;
168
+ return validateQuestions(questions) ? normalizeQuestions(questions) : null;
169
+ }
170
+ catch (error) {
171
+ const errorMessage = error instanceof Error ? error.message : String(error);
172
+ console.warn(`Failed to load .boilerplate.js: ${errorMessage}`);
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+ /**
178
+ * Normalize questions by ensuring all required fields have default values
179
+ * @param questions - Questions object to normalize
180
+ * @returns Normalized questions object
181
+ */
182
+ function normalizeQuestions(questions) {
183
+ return {
184
+ ...questions,
185
+ questions: questions.questions.map((q) => ({
186
+ ...q,
187
+ type: q.type || 'text'
188
+ }))
189
+ };
190
+ }
191
+ /**
192
+ * Validate that the questions object has the correct structure
193
+ * @param obj - Object to validate
194
+ * @returns True if valid, false otherwise
195
+ */
196
+ function validateQuestions(obj) {
197
+ return obj && typeof obj === 'object' && Array.isArray(obj.questions);
198
+ }
@@ -0,0 +1,19 @@
1
+ import { Inquirerer, Question } from 'inquirerer';
2
+ import { ExtractedVariables } from '../types';
3
+ /**
4
+ * Generate questions from extracted variables
5
+ * @param extractedVariables - Variables extracted from the template
6
+ * @returns Array of questions to prompt the user
7
+ */
8
+ export declare function generateQuestions(extractedVariables: ExtractedVariables): Question[];
9
+ /**
10
+ * Prompt the user for variable values
11
+ * @param extractedVariables - Variables extracted from the template
12
+ * @param argv - Command-line arguments to pre-populate answers
13
+ * @param existingPrompter - Optional existing Prompter instance to reuse.
14
+ * If provided, the caller retains ownership and must close it themselves.
15
+ * If not provided, a new instance is created and closed automatically.
16
+ * @param noTty - Whether to disable TTY mode (only used when creating a new prompter)
17
+ * @returns Answers from the user
18
+ */
19
+ export declare function promptUser(extractedVariables: ExtractedVariables, argv?: Record<string, any>, existingPrompter?: Inquirerer, noTty?: boolean): Promise<Record<string, any>>;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateQuestions = generateQuestions;
4
+ exports.promptUser = promptUser;
5
+ const inquirerer_1 = require("inquirerer");
6
+ const PLACEHOLDER_BOUNDARY = '____';
7
+ /**
8
+ * Generate questions from extracted variables
9
+ * @param extractedVariables - Variables extracted from the template
10
+ * @returns Array of questions to prompt the user
11
+ */
12
+ function generateQuestions(extractedVariables) {
13
+ const questions = [];
14
+ const askedVariables = new Set();
15
+ if (extractedVariables.projectQuestions) {
16
+ for (const question of extractedVariables.projectQuestions.questions) {
17
+ const normalizedName = normalizeQuestionName(question.name);
18
+ question.name = normalizedName;
19
+ questions.push(question);
20
+ askedVariables.add(normalizedName);
21
+ }
22
+ }
23
+ for (const replacer of extractedVariables.fileReplacers) {
24
+ if (!askedVariables.has(replacer.variable)) {
25
+ questions.push({
26
+ name: replacer.variable,
27
+ type: 'text',
28
+ message: `Enter value for ${replacer.variable}:`,
29
+ required: true
30
+ });
31
+ askedVariables.add(replacer.variable);
32
+ }
33
+ }
34
+ for (const replacer of extractedVariables.contentReplacers) {
35
+ if (!askedVariables.has(replacer.variable)) {
36
+ questions.push({
37
+ name: replacer.variable,
38
+ type: 'text',
39
+ message: `Enter value for ${replacer.variable}:`,
40
+ required: true
41
+ });
42
+ askedVariables.add(replacer.variable);
43
+ }
44
+ }
45
+ return questions;
46
+ }
47
+ function normalizeQuestionName(name) {
48
+ if (name.startsWith(PLACEHOLDER_BOUNDARY) &&
49
+ name.endsWith(PLACEHOLDER_BOUNDARY)) {
50
+ return name.slice(PLACEHOLDER_BOUNDARY.length, -PLACEHOLDER_BOUNDARY.length);
51
+ }
52
+ if (name.startsWith('__') && name.endsWith('__')) {
53
+ return name.slice(2, -2);
54
+ }
55
+ return name;
56
+ }
57
+ /**
58
+ * Prompt the user for variable values
59
+ * @param extractedVariables - Variables extracted from the template
60
+ * @param argv - Command-line arguments to pre-populate answers
61
+ * @param existingPrompter - Optional existing Prompter instance to reuse.
62
+ * If provided, the caller retains ownership and must close it themselves.
63
+ * If not provided, a new instance is created and closed automatically.
64
+ * @param noTty - Whether to disable TTY mode (only used when creating a new prompter)
65
+ * @returns Answers from the user
66
+ */
67
+ async function promptUser(extractedVariables, argv = {}, existingPrompter, noTty = false) {
68
+ const questions = generateQuestions(extractedVariables);
69
+ if (questions.length === 0) {
70
+ return argv;
71
+ }
72
+ const preparedArgv = mapArgvToQuestions(argv, questions);
73
+ // If an existing prompter is provided, use it (caller owns lifecycle)
74
+ // Otherwise, create a new one and close it when done
75
+ const prompter = existingPrompter ?? new inquirerer_1.Inquirerer({ noTty });
76
+ const shouldClose = !existingPrompter;
77
+ try {
78
+ const promptAnswers = await prompter.prompt(preparedArgv, questions);
79
+ return {
80
+ ...argv,
81
+ ...promptAnswers,
82
+ };
83
+ }
84
+ finally {
85
+ if (shouldClose) {
86
+ prompter.close();
87
+ }
88
+ }
89
+ }
90
+ function mapArgvToQuestions(argv, questions) {
91
+ if (!questions.length) {
92
+ return argv;
93
+ }
94
+ const prepared = { ...argv };
95
+ const argvEntries = Object.entries(argv);
96
+ for (const question of questions) {
97
+ const name = question.name;
98
+ if (prepared[name] !== undefined) {
99
+ continue;
100
+ }
101
+ const match = argvEntries.find(([key]) => key === name);
102
+ if (match) {
103
+ prepared[name] = match[1];
104
+ }
105
+ }
106
+ return prepared;
107
+ }
@@ -0,0 +1,9 @@
1
+ import { ExtractedVariables } from '../types';
2
+ /**
3
+ * Replace variables in all files in the template directory
4
+ * @param templateDir - Path to the template directory
5
+ * @param outputDir - Path to the output directory
6
+ * @param extractedVariables - Variables extracted from the template
7
+ * @param answers - User answers for variable values
8
+ */
9
+ export declare function replaceVariables(templateDir: string, outputDir: string, extractedVariables: ExtractedVariables, answers: Record<string, any>): Promise<void>;
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.replaceVariables = replaceVariables;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const stream_1 = require("stream");
40
+ const promises_1 = require("stream/promises");
41
+ const licenses_1 = require("../licenses");
42
+ /**
43
+ * Replace variables in all files in the template directory
44
+ * @param templateDir - Path to the template directory
45
+ * @param outputDir - Path to the output directory
46
+ * @param extractedVariables - Variables extracted from the template
47
+ * @param answers - User answers for variable values
48
+ */
49
+ async function replaceVariables(templateDir, outputDir, extractedVariables, answers) {
50
+ if (!fs.existsSync(outputDir)) {
51
+ fs.mkdirSync(outputDir, { recursive: true });
52
+ }
53
+ await walkAndReplace(templateDir, outputDir, extractedVariables, answers);
54
+ await ensureLicenseFile(outputDir, answers);
55
+ }
56
+ /**
57
+ * Walk through directory and replace variables in files and filenames
58
+ * @param sourceDir - Source directory
59
+ * @param destDir - Destination directory
60
+ * @param extractedVariables - Variables extracted from the template
61
+ * @param answers - User answers for variable values
62
+ * @param sourceRelativePath - Current relative path in source (for recursion)
63
+ * @param destRelativePath - Current relative path in destination (for recursion)
64
+ */
65
+ async function walkAndReplace(sourceDir, destDir, extractedVariables, answers, sourceRelativePath = '', destRelativePath = '') {
66
+ const currentSource = path.join(sourceDir, sourceRelativePath);
67
+ const entries = fs.readdirSync(currentSource, { withFileTypes: true });
68
+ for (const entry of entries) {
69
+ const sourceEntryPath = path.join(currentSource, entry.name);
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') {
74
+ continue;
75
+ }
76
+ let newName = entry.name;
77
+ for (const replacer of extractedVariables.fileReplacers) {
78
+ if (answers[replacer.variable] !== undefined) {
79
+ newName = newName.replace(replacer.pattern, String(answers[replacer.variable]));
80
+ }
81
+ }
82
+ const destEntryPath = path.join(destDir, destRelativePath, newName);
83
+ if (entry.isDirectory()) {
84
+ if (!fs.existsSync(destEntryPath)) {
85
+ fs.mkdirSync(destEntryPath, { recursive: true });
86
+ }
87
+ await walkAndReplace(sourceDir, destDir, extractedVariables, answers, path.join(sourceRelativePath, entry.name), path.join(destRelativePath, newName));
88
+ }
89
+ else if (entry.isFile()) {
90
+ await replaceInFile(sourceEntryPath, destEntryPath, extractedVariables, answers);
91
+ }
92
+ }
93
+ }
94
+ async function ensureLicenseFile(outputDir, answers) {
95
+ const licenseValue = (0, licenses_1.findLicenseValue)(answers);
96
+ if (typeof licenseValue !== 'string' || licenseValue.trim() === '') {
97
+ return;
98
+ }
99
+ const selectedLicense = licenseValue.trim();
100
+ if (!(0, licenses_1.isSupportedLicense)(selectedLicense)) {
101
+ console.warn(`[@genomic/scaffolds] License "${selectedLicense}" is not supported by the built-in templates. Leaving template LICENSE file as-is.`);
102
+ return;
103
+ }
104
+ const author = (0, licenses_1.findLicenseAuthor)(answers) ?? 'Unknown Author';
105
+ const email = (0, licenses_1.findLicenseEmail)(answers) ?? '';
106
+ const content = (0, licenses_1.renderLicense)(selectedLicense, {
107
+ author: String(author),
108
+ email: String(email || ''),
109
+ });
110
+ if (!content) {
111
+ return;
112
+ }
113
+ const licensePath = path.join(outputDir, 'LICENSE');
114
+ fs.mkdirSync(path.dirname(licensePath), { recursive: true });
115
+ fs.writeFileSync(licensePath, content.trimEnd() + '\n', 'utf8');
116
+ }
117
+ /**
118
+ * Replace variables in a file using streams
119
+ * @param sourcePath - Source file path
120
+ * @param destPath - Destination file path
121
+ * @param extractedVariables - Variables extracted from the template
122
+ * @param answers - User answers for variable values
123
+ */
124
+ async function replaceInFile(sourcePath, destPath, extractedVariables, answers) {
125
+ const destDir = path.dirname(destPath);
126
+ if (!fs.existsSync(destDir)) {
127
+ fs.mkdirSync(destDir, { recursive: true });
128
+ }
129
+ const replaceTransform = new stream_1.Transform({
130
+ transform(chunk, _encoding, callback) {
131
+ let content = chunk.toString();
132
+ for (const replacer of extractedVariables.contentReplacers) {
133
+ if (answers[replacer.variable] !== undefined) {
134
+ content = content.replace(replacer.pattern, String(answers[replacer.variable]));
135
+ }
136
+ }
137
+ callback(null, Buffer.from(content));
138
+ }
139
+ });
140
+ try {
141
+ await (0, promises_1.pipeline)(fs.createReadStream(sourcePath), replaceTransform, fs.createWriteStream(destPath));
142
+ }
143
+ catch (error) {
144
+ fs.copyFileSync(sourcePath, destPath);
145
+ }
146
+ }
@@ -0,0 +1,33 @@
1
+ import { ExtractedVariables } from '../types';
2
+ import { ProcessOptions, TemplatizerResult } from './types';
3
+ export declare class Templatizer {
4
+ constructor();
5
+ /**
6
+ * Process a local template directory (extract + prompt + replace)
7
+ * @param templateDir - Local directory path (MUST be local, NOT git URL)
8
+ * @param outputDir - Output directory for generated project
9
+ * @param options - Processing options (argv overrides, noTty, prompter)
10
+ * @returns Processing result
11
+ */
12
+ process(templateDir: string, outputDir: string, options?: ProcessOptions): Promise<TemplatizerResult>;
13
+ /**
14
+ * Extract variables from template directory
15
+ */
16
+ extract(templateDir: string): Promise<ExtractedVariables>;
17
+ /**
18
+ * Prompt user for variables
19
+ * @param extracted - Extracted variables from template
20
+ * @param argv - Pre-populated answers
21
+ * @param prompter - Optional existing Prompter instance to reuse
22
+ * @param noTty - Whether to disable TTY mode (only used when creating a new prompter)
23
+ */
24
+ prompt(extracted: ExtractedVariables, argv?: Record<string, any>, prompter?: import('inquirerer').Inquirerer, noTty?: boolean): Promise<Record<string, any>>;
25
+ /**
26
+ * Replace variables in template
27
+ */
28
+ replace(templateDir: string, outputDir: string, extracted: ExtractedVariables, answers: Record<string, any>): Promise<void>;
29
+ /**
30
+ * Validate template directory exists and has content
31
+ */
32
+ validateTemplateDir(templateDir: string): void;
33
+ }
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Templatizer = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const extract_1 = require("./extract");
40
+ const prompt_1 = require("./prompt");
41
+ const replace_1 = require("./replace");
42
+ class Templatizer {
43
+ constructor() {
44
+ // Pure template processor - no configuration needed
45
+ }
46
+ /**
47
+ * Process a local template directory (extract + prompt + replace)
48
+ * @param templateDir - Local directory path (MUST be local, NOT git URL)
49
+ * @param outputDir - Output directory for generated project
50
+ * @param options - Processing options (argv overrides, noTty, prompter)
51
+ * @returns Processing result
52
+ */
53
+ async process(templateDir, outputDir, options) {
54
+ this.validateTemplateDir(templateDir);
55
+ // Handle subdirectory within template
56
+ const actualTemplateDir = options?.fromPath
57
+ ? path.join(templateDir, options.fromPath)
58
+ : templateDir;
59
+ this.validateTemplateDir(actualTemplateDir);
60
+ // Extract variables
61
+ const variables = await this.extract(actualTemplateDir);
62
+ // Prompt for values (pass through optional prompter)
63
+ const answers = await this.prompt(variables, options?.argv, options?.prompter, options?.noTty);
64
+ // Replace variables
65
+ await this.replace(actualTemplateDir, outputDir, variables, answers);
66
+ return {
67
+ outputDir,
68
+ variables,
69
+ answers,
70
+ };
71
+ }
72
+ /**
73
+ * Extract variables from template directory
74
+ */
75
+ async extract(templateDir) {
76
+ return (0, extract_1.extractVariables)(templateDir);
77
+ }
78
+ /**
79
+ * Prompt user for variables
80
+ * @param extracted - Extracted variables from template
81
+ * @param argv - Pre-populated answers
82
+ * @param prompter - Optional existing Prompter instance to reuse
83
+ * @param noTty - Whether to disable TTY mode (only used when creating a new prompter)
84
+ */
85
+ async prompt(extracted, argv, prompter, noTty) {
86
+ return (0, prompt_1.promptUser)(extracted, argv ?? {}, prompter, noTty ?? false);
87
+ }
88
+ /**
89
+ * Replace variables in template
90
+ */
91
+ async replace(templateDir, outputDir, extracted, answers) {
92
+ return (0, replace_1.replaceVariables)(templateDir, outputDir, extracted, answers);
93
+ }
94
+ /**
95
+ * Validate template directory exists and has content
96
+ */
97
+ validateTemplateDir(templateDir) {
98
+ if (!fs.existsSync(templateDir)) {
99
+ throw new Error(`Template directory does not exist: ${templateDir}`);
100
+ }
101
+ if (!fs.statSync(templateDir).isDirectory()) {
102
+ throw new Error(`Template path is not a directory: ${templateDir}`);
103
+ }
104
+ const entries = fs.readdirSync(templateDir);
105
+ if (entries.length === 0) {
106
+ throw new Error(`Template directory is empty: ${templateDir}`);
107
+ }
108
+ }
109
+ }
110
+ exports.Templatizer = Templatizer;
@@ -0,0 +1,18 @@
1
+ import { Inquirerer } from 'inquirerer';
2
+ import { ExtractedVariables } from '../types';
3
+ export interface ProcessOptions {
4
+ argv?: Record<string, any>;
5
+ noTty?: boolean;
6
+ fromPath?: string;
7
+ /**
8
+ * Optional Prompter instance to reuse for prompting.
9
+ * If provided, the caller retains ownership and is responsible for closing it.
10
+ * If not provided, a new instance will be created and closed automatically.
11
+ */
12
+ prompter?: Inquirerer;
13
+ }
14
+ export interface TemplatizerResult {
15
+ outputDir: string;
16
+ variables: ExtractedVariables;
17
+ answers: Record<string, any>;
18
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });