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.
- package/README.md +154 -1125
- package/cache/cache-manager.d.ts +60 -0
- package/cache/cache-manager.js +228 -0
- package/cache/types.d.ts +22 -0
- package/esm/cache/cache-manager.js +191 -0
- package/esm/git/git-cloner.js +92 -0
- package/esm/index.js +41 -4
- package/esm/licenses.js +120 -0
- package/esm/scaffolder/index.js +2 -0
- package/esm/scaffolder/template-scaffolder.js +310 -0
- package/esm/scaffolder/types.js +1 -0
- package/esm/template/extract.js +162 -0
- package/esm/template/prompt.js +103 -0
- package/esm/template/replace.js +110 -0
- package/esm/template/templatizer.js +73 -0
- package/esm/template/types.js +1 -0
- package/esm/types.js +1 -0
- package/esm/utils/npm-version-check.js +52 -0
- package/esm/utils/types.js +1 -0
- package/git/git-cloner.d.ts +32 -0
- package/git/git-cloner.js +129 -0
- package/git/types.d.ts +15 -0
- package/index.d.ts +29 -4
- package/index.js +43 -4
- package/licenses-templates/APACHE-2.0.txt +18 -0
- package/licenses-templates/BSD-3-CLAUSE.txt +28 -0
- package/licenses-templates/CLOSED.txt +20 -0
- package/licenses-templates/GPL-3.0.txt +18 -0
- package/licenses-templates/ISC.txt +16 -0
- package/licenses-templates/MIT.txt +22 -0
- package/licenses-templates/MPL-2.0.txt +8 -0
- package/licenses-templates/UNLICENSE.txt +22 -0
- package/licenses.d.ts +18 -0
- package/licenses.js +162 -0
- package/package.json +9 -14
- package/scaffolder/index.d.ts +2 -0
- package/{question → scaffolder}/index.js +1 -0
- package/scaffolder/template-scaffolder.d.ts +91 -0
- package/scaffolder/template-scaffolder.js +347 -0
- package/scaffolder/types.d.ts +191 -0
- package/scaffolder/types.js +2 -0
- package/template/extract.d.ts +7 -0
- package/template/extract.js +198 -0
- package/template/prompt.d.ts +19 -0
- package/template/prompt.js +107 -0
- package/template/replace.d.ts +9 -0
- package/template/replace.js +146 -0
- package/template/templatizer.d.ts +33 -0
- package/template/templatizer.js +110 -0
- package/template/types.d.ts +18 -0
- package/template/types.js +2 -0
- package/types.d.ts +99 -0
- package/types.js +2 -0
- package/utils/npm-version-check.d.ts +17 -0
- package/utils/npm-version-check.js +57 -0
- package/utils/types.d.ts +6 -0
- package/utils/types.js +2 -0
- package/commander.d.ts +0 -21
- package/commander.js +0 -57
- package/esm/commander.js +0 -50
- package/esm/keypress.js +0 -95
- package/esm/prompt.js +0 -1024
- package/esm/question/index.js +0 -1
- package/esm/resolvers/date.js +0 -11
- package/esm/resolvers/git.js +0 -26
- package/esm/resolvers/index.js +0 -103
- package/esm/resolvers/npm.js +0 -24
- package/esm/resolvers/workspace.js +0 -141
- package/esm/utils.js +0 -12
- package/keypress.d.ts +0 -45
- package/keypress.js +0 -99
- package/prompt.d.ts +0 -116
- package/prompt.js +0 -1032
- package/question/index.d.ts +0 -1
- package/question/types.d.ts +0 -65
- package/resolvers/date.d.ts +0 -5
- package/resolvers/date.js +0 -14
- package/resolvers/git.d.ts +0 -11
- package/resolvers/git.js +0 -30
- package/resolvers/index.d.ts +0 -63
- package/resolvers/index.js +0 -111
- package/resolvers/npm.d.ts +0 -10
- package/resolvers/npm.js +0 -28
- package/resolvers/types.d.ts +0 -12
- package/resolvers/workspace.d.ts +0 -6
- package/resolvers/workspace.js +0 -144
- package/utils.d.ts +0 -2
- package/utils.js +0 -16
- /package/{question → cache}/types.js +0 -0
- /package/esm/{question → cache}/types.js +0 -0
- /package/esm/{resolvers → git}/types.js +0 -0
- /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
|
+
}
|