create-gen-app 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Dan Lynch <pyramation@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # create-gen-app
2
+
3
+ <p align="center" width="100%">
4
+ <img height="90" src="https://user-images.githubusercontent.com/545047/190171475-b416f99e-2831-4786-9ba3-a7ff4d95b0d3.svg" />
5
+ </p>
6
+
7
+ <p align="center" width="100%">
8
+
9
+ <a href="https://github.com/pyramation/inquirerer/actions/workflows/run-tests.yml">
10
+ <img height="20" src="https://github.com/pyramation/inquirerer/actions/workflows/run-tests.yml/badge.svg" />
11
+ </a>
12
+ <a href="https://github.com/pyramation/inquirerer/blob/main/LICENSE"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
13
+ <a href="https://www.npmjs.com/package/inquirerer"><img height="20" src="https://img.shields.io/npm/dt/inquirerer"></a>
14
+ <a href="https://www.npmjs.com/package/inquirerer"><img height="20" src="https://img.shields.io/github/package-json/v/pyramation/inquirerer?filename=packages%2Finquirerer%2Fpackage.json"></a>
15
+ </p>
16
+
17
+ A TypeScript library for cloning and customizing template repositories with variable replacement.
18
+
19
+ ## Features
20
+
21
+ - Clone GitHub repositories or any git URL
22
+ - Extract template variables from filenames and file contents using `__VARIABLE__` syntax
23
+ - Load custom questions from `.questions.json` or `.questions.js` files
24
+ - Interactive prompts using inquirerer with CLI argument support
25
+ - Stream-based file processing for efficient variable replacement
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install create-gen-app
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### Basic Usage
36
+
37
+ ```typescript
38
+ import { createGen } from 'create-gen-app';
39
+
40
+ await createGen({
41
+ templateUrl: 'https://github.com/user/template-repo',
42
+ outputDir: './my-new-project',
43
+ argv: {
44
+ PROJECT_NAME: 'my-project',
45
+ AUTHOR: 'John Doe'
46
+ }
47
+ });
48
+ ```
49
+
50
+ ### Template Variables
51
+
52
+ Variables in your template should be wrapped in double underscores:
53
+
54
+ **Filename variables:**
55
+ ```
56
+ __PROJECT_NAME__/
57
+ __MODULE_NAME__.ts
58
+ ```
59
+
60
+ **Content variables:**
61
+ ```typescript
62
+ // __MODULE_NAME__.ts
63
+ export const projectName = "__PROJECT_NAME__";
64
+ export const author = "__AUTHOR__";
65
+ ```
66
+
67
+ ### Custom Questions
68
+
69
+ Create a `.questions.json` file in your template repository:
70
+
71
+ ```json
72
+ {
73
+ "questions": [
74
+ {
75
+ "name": "PROJECT_NAME",
76
+ "type": "text",
77
+ "message": "What is your project name?",
78
+ "required": true
79
+ },
80
+ {
81
+ "name": "AUTHOR",
82
+ "type": "text",
83
+ "message": "Who is the author?"
84
+ }
85
+ ]
86
+ }
87
+ ```
88
+
89
+ Or use `.questions.js` for dynamic questions:
90
+
91
+ ```javascript
92
+ /**
93
+ * @typedef {Object} Questions
94
+ * @property {Array} questions - Array of question objects
95
+ */
96
+
97
+ module.exports = {
98
+ questions: [
99
+ {
100
+ name: 'PROJECT_NAME',
101
+ type: 'text',
102
+ message: 'What is your project name?',
103
+ required: true
104
+ }
105
+ ]
106
+ };
107
+ ```
108
+
109
+ ## API
110
+
111
+ ### `createGen(options: CreateGenOptions): Promise<string>`
112
+
113
+ Main function to create a project from a template.
114
+
115
+ **Options:**
116
+ - `templateUrl` (string): URL or path to the template repository
117
+ - `outputDir` (string): Destination directory for the generated project
118
+ - `argv` (Record<string, any>): Command-line arguments to pre-populate answers
119
+ - `noTty` (boolean): Whether to disable TTY mode for non-interactive usage
120
+
121
+ ### `extractVariables(templateDir: string): Promise<ExtractedVariables>`
122
+
123
+ Extract all variables from a template directory.
124
+
125
+ ### `promptUser(extractedVariables: ExtractedVariables, argv?: Record<string, any>, noTty?: boolean): Promise<Record<string, any>>`
126
+
127
+ Prompt the user for variable values using inquirerer.
128
+
129
+ ### `replaceVariables(templateDir: string, outputDir: string, extractedVariables: ExtractedVariables, answers: Record<string, any>): Promise<void>`
130
+
131
+ Replace variables in all files and filenames.
132
+
133
+ ## Variable Naming Rules
134
+
135
+ Variables can contain:
136
+ - Letters (a-z, A-Z)
137
+ - Numbers (0-9)
138
+ - Underscores (_)
139
+ - Must start with a letter or underscore
140
+
141
+ Examples of valid variables:
142
+ - `__PROJECT_NAME__`
143
+ - `__author__`
144
+ - `__CamelCase__`
145
+ - `__snake_case__`
146
+ - `__VERSION_1__`
147
+
148
+ ## License
149
+
150
+ MIT
package/clone.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Clone a repository to a temporary directory
3
+ * @param url - Repository URL (GitHub or any git URL)
4
+ * @returns Path to the cloned repository
5
+ */
6
+ export declare function cloneRepo(url: string): Promise<string>;
package/clone.js ADDED
@@ -0,0 +1,71 @@
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 (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.cloneRepo = void 0;
27
+ const child_process_1 = require("child_process");
28
+ const fs = __importStar(require("fs"));
29
+ const os = __importStar(require("os"));
30
+ const path = __importStar(require("path"));
31
+ /**
32
+ * Clone a repository to a temporary directory
33
+ * @param url - Repository URL (GitHub or any git URL)
34
+ * @returns Path to the cloned repository
35
+ */
36
+ async function cloneRepo(url) {
37
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'create-gen-'));
38
+ try {
39
+ const gitUrl = normalizeGitUrl(url);
40
+ (0, child_process_1.execSync)(`git clone ${gitUrl} ${tempDir}`, {
41
+ stdio: 'inherit'
42
+ });
43
+ const gitDir = path.join(tempDir, '.git');
44
+ if (fs.existsSync(gitDir)) {
45
+ fs.rmSync(gitDir, { recursive: true, force: true });
46
+ }
47
+ return tempDir;
48
+ }
49
+ catch (error) {
50
+ if (fs.existsSync(tempDir)) {
51
+ fs.rmSync(tempDir, { recursive: true, force: true });
52
+ }
53
+ const errorMessage = error instanceof Error ? error.message : String(error);
54
+ throw new Error(`Failed to clone repository: ${errorMessage}`);
55
+ }
56
+ }
57
+ exports.cloneRepo = cloneRepo;
58
+ /**
59
+ * Normalize a URL to a git-cloneable format
60
+ * @param url - Input URL
61
+ * @returns Normalized git URL
62
+ */
63
+ function normalizeGitUrl(url) {
64
+ if (url.startsWith('git@') || url.startsWith('https://') || url.startsWith('http://')) {
65
+ return url;
66
+ }
67
+ if (/^[\w-]+\/[\w-]+$/.test(url)) {
68
+ return `https://github.com/${url}.git`;
69
+ }
70
+ return url;
71
+ }
package/esm/clone.js ADDED
@@ -0,0 +1,44 @@
1
+ import { execSync } from 'child_process';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+ /**
6
+ * Clone a repository to a temporary directory
7
+ * @param url - Repository URL (GitHub or any git URL)
8
+ * @returns Path to the cloned repository
9
+ */
10
+ export async function cloneRepo(url) {
11
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'create-gen-'));
12
+ try {
13
+ const gitUrl = normalizeGitUrl(url);
14
+ execSync(`git clone ${gitUrl} ${tempDir}`, {
15
+ stdio: 'inherit'
16
+ });
17
+ const gitDir = path.join(tempDir, '.git');
18
+ if (fs.existsSync(gitDir)) {
19
+ fs.rmSync(gitDir, { recursive: true, force: true });
20
+ }
21
+ return tempDir;
22
+ }
23
+ catch (error) {
24
+ if (fs.existsSync(tempDir)) {
25
+ fs.rmSync(tempDir, { recursive: true, force: true });
26
+ }
27
+ const errorMessage = error instanceof Error ? error.message : String(error);
28
+ throw new Error(`Failed to clone repository: ${errorMessage}`);
29
+ }
30
+ }
31
+ /**
32
+ * Normalize a URL to a git-cloneable format
33
+ * @param url - Input URL
34
+ * @returns Normalized git URL
35
+ */
36
+ function normalizeGitUrl(url) {
37
+ if (url.startsWith('git@') || url.startsWith('https://') || url.startsWith('http://')) {
38
+ return url;
39
+ }
40
+ if (/^[\w-]+\/[\w-]+$/.test(url)) {
41
+ return `https://github.com/${url}.git`;
42
+ }
43
+ return url;
44
+ }
package/esm/extract.js ADDED
@@ -0,0 +1,140 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ /**
4
+ * Pattern to match __VARIABLE__ in filenames and content
5
+ */
6
+ const VARIABLE_PATTERN = /__([A-Za-z_][A-Za-z0-9_]*)__/g;
7
+ /**
8
+ * Extract all variables from a template directory
9
+ * @param templateDir - Path to the template directory
10
+ * @returns Extracted variables including file replacers, content replacers, and project questions
11
+ */
12
+ export async function extractVariables(templateDir) {
13
+ const fileReplacers = [];
14
+ const contentReplacers = [];
15
+ const fileReplacerVars = new Set();
16
+ const contentReplacerVars = new Set();
17
+ const projectQuestions = await loadProjectQuestions(templateDir);
18
+ await walkDirectory(templateDir, async (filePath) => {
19
+ const relativePath = path.relative(templateDir, filePath);
20
+ if (relativePath === '.questions.json' || relativePath === '.questions.js') {
21
+ return;
22
+ }
23
+ const matches = relativePath.matchAll(VARIABLE_PATTERN);
24
+ for (const match of matches) {
25
+ const varName = match[1];
26
+ if (!fileReplacerVars.has(varName)) {
27
+ fileReplacerVars.add(varName);
28
+ fileReplacers.push({
29
+ variable: varName,
30
+ pattern: new RegExp(`__${varName}__`, 'g')
31
+ });
32
+ }
33
+ }
34
+ const contentVars = await extractFromFileContent(filePath);
35
+ for (const varName of contentVars) {
36
+ if (!contentReplacerVars.has(varName)) {
37
+ contentReplacerVars.add(varName);
38
+ contentReplacers.push({
39
+ variable: varName,
40
+ pattern: new RegExp(`__${varName}__`, 'g')
41
+ });
42
+ }
43
+ }
44
+ });
45
+ return {
46
+ fileReplacers,
47
+ contentReplacers,
48
+ projectQuestions
49
+ };
50
+ }
51
+ /**
52
+ * Extract variables from file content using streams
53
+ * @param filePath - Path to the file
54
+ * @returns Set of variable names found in the file
55
+ */
56
+ async function extractFromFileContent(filePath) {
57
+ const variables = new Set();
58
+ return new Promise((resolve) => {
59
+ const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
60
+ let buffer = '';
61
+ stream.on('data', (chunk) => {
62
+ buffer += chunk.toString();
63
+ const lines = buffer.split('\n');
64
+ buffer = lines.pop() || ''; // Keep the last incomplete line in buffer
65
+ for (const line of lines) {
66
+ const matches = line.matchAll(VARIABLE_PATTERN);
67
+ for (const match of matches) {
68
+ variables.add(match[1]);
69
+ }
70
+ }
71
+ });
72
+ stream.on('end', () => {
73
+ const matches = buffer.matchAll(VARIABLE_PATTERN);
74
+ for (const match of matches) {
75
+ variables.add(match[1]);
76
+ }
77
+ resolve(variables);
78
+ });
79
+ stream.on('error', () => {
80
+ resolve(variables);
81
+ });
82
+ });
83
+ }
84
+ /**
85
+ * Walk through a directory recursively
86
+ * @param dir - Directory to walk
87
+ * @param callback - Callback function for each file
88
+ */
89
+ async function walkDirectory(dir, callback) {
90
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
91
+ for (const entry of entries) {
92
+ const fullPath = path.join(dir, entry.name);
93
+ if (entry.isDirectory()) {
94
+ await walkDirectory(fullPath, callback);
95
+ }
96
+ else if (entry.isFile()) {
97
+ await callback(fullPath);
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * Load project questions from .questions.json or .questions.js
103
+ * @param templateDir - Path to the template directory
104
+ * @returns Questions object or null if not found
105
+ */
106
+ async function loadProjectQuestions(templateDir) {
107
+ const jsonPath = path.join(templateDir, '.questions.json');
108
+ if (fs.existsSync(jsonPath)) {
109
+ try {
110
+ const content = fs.readFileSync(jsonPath, 'utf8');
111
+ const questions = JSON.parse(content);
112
+ return validateQuestions(questions) ? questions : null;
113
+ }
114
+ catch (error) {
115
+ const errorMessage = error instanceof Error ? error.message : String(error);
116
+ console.warn(`Failed to parse .questions.json: ${errorMessage}`);
117
+ }
118
+ }
119
+ const jsPath = path.join(templateDir, '.questions.js');
120
+ if (fs.existsSync(jsPath)) {
121
+ try {
122
+ const module = require(jsPath);
123
+ const questions = module.default || module;
124
+ return validateQuestions(questions) ? questions : null;
125
+ }
126
+ catch (error) {
127
+ const errorMessage = error instanceof Error ? error.message : String(error);
128
+ console.warn(`Failed to load .questions.js: ${errorMessage}`);
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ /**
134
+ * Validate that the questions object has the correct structure
135
+ * @param obj - Object to validate
136
+ * @returns True if valid, false otherwise
137
+ */
138
+ function validateQuestions(obj) {
139
+ return obj && typeof obj === 'object' && Array.isArray(obj.questions);
140
+ }
package/esm/index.js ADDED
@@ -0,0 +1,40 @@
1
+ import * as fs from 'fs';
2
+ import { cloneRepo } from './clone';
3
+ import { extractVariables } from './extract';
4
+ import { promptUser } from './prompt';
5
+ import { replaceVariables } from './replace';
6
+ export * from './clone';
7
+ export * from './extract';
8
+ export * from './prompt';
9
+ export * from './replace';
10
+ export * from './types';
11
+ /**
12
+ * Create a new project from a template repository
13
+ * @param options - Options for creating the project
14
+ * @returns Path to the generated project
15
+ */
16
+ export async function createGen(options) {
17
+ const { templateUrl, outputDir, argv = {}, noTty = false } = options;
18
+ console.log(`Cloning template from ${templateUrl}...`);
19
+ const tempDir = await cloneRepo(templateUrl);
20
+ try {
21
+ console.log('Extracting template variables...');
22
+ const extractedVariables = await extractVariables(tempDir);
23
+ console.log(`Found ${extractedVariables.fileReplacers.length} file replacers`);
24
+ console.log(`Found ${extractedVariables.contentReplacers.length} content replacers`);
25
+ if (extractedVariables.projectQuestions) {
26
+ console.log(`Found ${extractedVariables.projectQuestions.questions.length} project questions`);
27
+ }
28
+ console.log('Prompting for variable values...');
29
+ const answers = await promptUser(extractedVariables, argv, noTty);
30
+ console.log(`Generating project in ${outputDir}...`);
31
+ await replaceVariables(tempDir, outputDir, extractedVariables, answers);
32
+ console.log('Project created successfully!');
33
+ return outputDir;
34
+ }
35
+ finally {
36
+ if (fs.existsSync(tempDir)) {
37
+ fs.rmSync(tempDir, { recursive: true, force: true });
38
+ }
39
+ }
40
+ }
package/esm/prompt.js ADDED
@@ -0,0 +1,57 @@
1
+ import { Inquirerer } from 'inquirerer';
2
+ /**
3
+ * Generate questions from extracted variables
4
+ * @param extractedVariables - Variables extracted from the template
5
+ * @returns Array of questions to prompt the user
6
+ */
7
+ export function generateQuestions(extractedVariables) {
8
+ const questions = [];
9
+ const askedVariables = new Set();
10
+ if (extractedVariables.projectQuestions) {
11
+ for (const question of extractedVariables.projectQuestions.questions) {
12
+ questions.push(question);
13
+ askedVariables.add(question.name);
14
+ }
15
+ }
16
+ for (const replacer of extractedVariables.fileReplacers) {
17
+ if (!askedVariables.has(replacer.variable)) {
18
+ questions.push({
19
+ name: replacer.variable,
20
+ type: 'text',
21
+ message: `Enter value for ${replacer.variable}:`,
22
+ required: true
23
+ });
24
+ askedVariables.add(replacer.variable);
25
+ }
26
+ }
27
+ for (const replacer of extractedVariables.contentReplacers) {
28
+ if (!askedVariables.has(replacer.variable)) {
29
+ questions.push({
30
+ name: replacer.variable,
31
+ type: 'text',
32
+ message: `Enter value for ${replacer.variable}:`,
33
+ required: true
34
+ });
35
+ askedVariables.add(replacer.variable);
36
+ }
37
+ }
38
+ return questions;
39
+ }
40
+ /**
41
+ * Prompt the user for variable values
42
+ * @param extractedVariables - Variables extracted from the template
43
+ * @param argv - Command-line arguments to pre-populate answers
44
+ * @param noTty - Whether to disable TTY mode
45
+ * @returns Answers from the user
46
+ */
47
+ export async function promptUser(extractedVariables, argv = {}, noTty = false) {
48
+ const questions = generateQuestions(extractedVariables);
49
+ if (questions.length === 0) {
50
+ return argv;
51
+ }
52
+ const prompter = new Inquirerer({
53
+ noTty
54
+ });
55
+ const answers = await prompter.prompt(argv, questions);
56
+ return answers;
57
+ }
package/esm/replace.js ADDED
@@ -0,0 +1,82 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { Transform } from 'stream';
4
+ import { pipeline } from 'stream/promises';
5
+ /**
6
+ * Replace variables in all files in the template directory
7
+ * @param templateDir - Path to the template directory
8
+ * @param outputDir - Path to the output directory
9
+ * @param extractedVariables - Variables extracted from the template
10
+ * @param answers - User answers for variable values
11
+ */
12
+ export async function replaceVariables(templateDir, outputDir, extractedVariables, answers) {
13
+ if (!fs.existsSync(outputDir)) {
14
+ fs.mkdirSync(outputDir, { recursive: true });
15
+ }
16
+ await walkAndReplace(templateDir, outputDir, extractedVariables, answers);
17
+ }
18
+ /**
19
+ * Walk through directory and replace variables in files and filenames
20
+ * @param sourceDir - Source directory
21
+ * @param destDir - Destination directory
22
+ * @param extractedVariables - Variables extracted from the template
23
+ * @param answers - User answers for variable values
24
+ * @param sourceRelativePath - Current relative path in source (for recursion)
25
+ * @param destRelativePath - Current relative path in destination (for recursion)
26
+ */
27
+ async function walkAndReplace(sourceDir, destDir, extractedVariables, answers, sourceRelativePath = '', destRelativePath = '') {
28
+ const currentSource = path.join(sourceDir, sourceRelativePath);
29
+ const entries = fs.readdirSync(currentSource, { withFileTypes: true });
30
+ for (const entry of entries) {
31
+ const sourceEntryPath = path.join(currentSource, entry.name);
32
+ if (entry.name === '.questions.json' || entry.name === '.questions.js') {
33
+ continue;
34
+ }
35
+ let newName = entry.name;
36
+ for (const replacer of extractedVariables.fileReplacers) {
37
+ if (answers[replacer.variable] !== undefined) {
38
+ newName = newName.replace(replacer.pattern, String(answers[replacer.variable]));
39
+ }
40
+ }
41
+ const destEntryPath = path.join(destDir, destRelativePath, newName);
42
+ if (entry.isDirectory()) {
43
+ if (!fs.existsSync(destEntryPath)) {
44
+ fs.mkdirSync(destEntryPath, { recursive: true });
45
+ }
46
+ await walkAndReplace(sourceDir, destDir, extractedVariables, answers, path.join(sourceRelativePath, entry.name), path.join(destRelativePath, newName));
47
+ }
48
+ else if (entry.isFile()) {
49
+ await replaceInFile(sourceEntryPath, destEntryPath, extractedVariables, answers);
50
+ }
51
+ }
52
+ }
53
+ /**
54
+ * Replace variables in a file using streams
55
+ * @param sourcePath - Source file path
56
+ * @param destPath - Destination file path
57
+ * @param extractedVariables - Variables extracted from the template
58
+ * @param answers - User answers for variable values
59
+ */
60
+ async function replaceInFile(sourcePath, destPath, extractedVariables, answers) {
61
+ const destDir = path.dirname(destPath);
62
+ if (!fs.existsSync(destDir)) {
63
+ fs.mkdirSync(destDir, { recursive: true });
64
+ }
65
+ const replaceTransform = new Transform({
66
+ transform(chunk, encoding, callback) {
67
+ let content = chunk.toString();
68
+ for (const replacer of extractedVariables.contentReplacers) {
69
+ if (answers[replacer.variable] !== undefined) {
70
+ content = content.replace(replacer.pattern, String(answers[replacer.variable]));
71
+ }
72
+ }
73
+ callback(null, Buffer.from(content));
74
+ }
75
+ });
76
+ try {
77
+ await pipeline(fs.createReadStream(sourcePath), replaceTransform, fs.createWriteStream(destPath));
78
+ }
79
+ catch (error) {
80
+ fs.copyFileSync(sourcePath, destPath);
81
+ }
82
+ }
package/esm/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/extract.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { ExtractedVariables } from './types';
2
+ /**
3
+ * Extract all variables from a template directory
4
+ * @param templateDir - Path to the template directory
5
+ * @returns Extracted variables including file replacers, content replacers, and project questions
6
+ */
7
+ export declare function extractVariables(templateDir: string): Promise<ExtractedVariables>;
package/extract.js ADDED
@@ -0,0 +1,167 @@
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 (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.extractVariables = void 0;
27
+ const fs = __importStar(require("fs"));
28
+ const path = __importStar(require("path"));
29
+ /**
30
+ * Pattern to match __VARIABLE__ in filenames and content
31
+ */
32
+ const VARIABLE_PATTERN = /__([A-Za-z_][A-Za-z0-9_]*)__/g;
33
+ /**
34
+ * Extract all variables from a template directory
35
+ * @param templateDir - Path to the template directory
36
+ * @returns Extracted variables including file replacers, content replacers, and project questions
37
+ */
38
+ async function extractVariables(templateDir) {
39
+ const fileReplacers = [];
40
+ const contentReplacers = [];
41
+ const fileReplacerVars = new Set();
42
+ const contentReplacerVars = new Set();
43
+ const projectQuestions = await loadProjectQuestions(templateDir);
44
+ await walkDirectory(templateDir, async (filePath) => {
45
+ const relativePath = path.relative(templateDir, filePath);
46
+ if (relativePath === '.questions.json' || relativePath === '.questions.js') {
47
+ return;
48
+ }
49
+ const matches = relativePath.matchAll(VARIABLE_PATTERN);
50
+ for (const match of matches) {
51
+ const varName = match[1];
52
+ if (!fileReplacerVars.has(varName)) {
53
+ fileReplacerVars.add(varName);
54
+ fileReplacers.push({
55
+ variable: varName,
56
+ pattern: new RegExp(`__${varName}__`, 'g')
57
+ });
58
+ }
59
+ }
60
+ const contentVars = await extractFromFileContent(filePath);
61
+ for (const varName of contentVars) {
62
+ if (!contentReplacerVars.has(varName)) {
63
+ contentReplacerVars.add(varName);
64
+ contentReplacers.push({
65
+ variable: varName,
66
+ pattern: new RegExp(`__${varName}__`, 'g')
67
+ });
68
+ }
69
+ }
70
+ });
71
+ return {
72
+ fileReplacers,
73
+ contentReplacers,
74
+ projectQuestions
75
+ };
76
+ }
77
+ exports.extractVariables = extractVariables;
78
+ /**
79
+ * Extract variables from file content using streams
80
+ * @param filePath - Path to the file
81
+ * @returns Set of variable names found in the file
82
+ */
83
+ async function extractFromFileContent(filePath) {
84
+ const variables = new Set();
85
+ return new Promise((resolve) => {
86
+ const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
87
+ let buffer = '';
88
+ stream.on('data', (chunk) => {
89
+ buffer += chunk.toString();
90
+ const lines = buffer.split('\n');
91
+ buffer = lines.pop() || ''; // Keep the last incomplete line in buffer
92
+ for (const line of lines) {
93
+ const matches = line.matchAll(VARIABLE_PATTERN);
94
+ for (const match of matches) {
95
+ variables.add(match[1]);
96
+ }
97
+ }
98
+ });
99
+ stream.on('end', () => {
100
+ const matches = buffer.matchAll(VARIABLE_PATTERN);
101
+ for (const match of matches) {
102
+ variables.add(match[1]);
103
+ }
104
+ resolve(variables);
105
+ });
106
+ stream.on('error', () => {
107
+ resolve(variables);
108
+ });
109
+ });
110
+ }
111
+ /**
112
+ * Walk through a directory recursively
113
+ * @param dir - Directory to walk
114
+ * @param callback - Callback function for each file
115
+ */
116
+ async function walkDirectory(dir, callback) {
117
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
118
+ for (const entry of entries) {
119
+ const fullPath = path.join(dir, entry.name);
120
+ if (entry.isDirectory()) {
121
+ await walkDirectory(fullPath, callback);
122
+ }
123
+ else if (entry.isFile()) {
124
+ await callback(fullPath);
125
+ }
126
+ }
127
+ }
128
+ /**
129
+ * Load project questions from .questions.json or .questions.js
130
+ * @param templateDir - Path to the template directory
131
+ * @returns Questions object or null if not found
132
+ */
133
+ async function loadProjectQuestions(templateDir) {
134
+ const jsonPath = path.join(templateDir, '.questions.json');
135
+ if (fs.existsSync(jsonPath)) {
136
+ try {
137
+ const content = fs.readFileSync(jsonPath, 'utf8');
138
+ const questions = JSON.parse(content);
139
+ return validateQuestions(questions) ? questions : null;
140
+ }
141
+ catch (error) {
142
+ const errorMessage = error instanceof Error ? error.message : String(error);
143
+ console.warn(`Failed to parse .questions.json: ${errorMessage}`);
144
+ }
145
+ }
146
+ const jsPath = path.join(templateDir, '.questions.js');
147
+ if (fs.existsSync(jsPath)) {
148
+ try {
149
+ const module = require(jsPath);
150
+ const questions = module.default || module;
151
+ return validateQuestions(questions) ? questions : null;
152
+ }
153
+ catch (error) {
154
+ const errorMessage = error instanceof Error ? error.message : String(error);
155
+ console.warn(`Failed to load .questions.js: ${errorMessage}`);
156
+ }
157
+ }
158
+ return null;
159
+ }
160
+ /**
161
+ * Validate that the questions object has the correct structure
162
+ * @param obj - Object to validate
163
+ * @returns True if valid, false otherwise
164
+ */
165
+ function validateQuestions(obj) {
166
+ return obj && typeof obj === 'object' && Array.isArray(obj.questions);
167
+ }
package/index.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { CreateGenOptions } from './types';
2
+ export * from './clone';
3
+ export * from './extract';
4
+ export * from './prompt';
5
+ export * from './replace';
6
+ export * from './types';
7
+ /**
8
+ * Create a new project from a template repository
9
+ * @param options - Options for creating the project
10
+ * @returns Path to the generated project
11
+ */
12
+ export declare function createGen(options: CreateGenOptions): Promise<string>;
package/index.js ADDED
@@ -0,0 +1,70 @@
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 (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
26
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.createGen = void 0;
30
+ const fs = __importStar(require("fs"));
31
+ const clone_1 = require("./clone");
32
+ const extract_1 = require("./extract");
33
+ const prompt_1 = require("./prompt");
34
+ const replace_1 = require("./replace");
35
+ __exportStar(require("./clone"), exports);
36
+ __exportStar(require("./extract"), exports);
37
+ __exportStar(require("./prompt"), exports);
38
+ __exportStar(require("./replace"), exports);
39
+ __exportStar(require("./types"), exports);
40
+ /**
41
+ * Create a new project from a template repository
42
+ * @param options - Options for creating the project
43
+ * @returns Path to the generated project
44
+ */
45
+ async function createGen(options) {
46
+ const { templateUrl, outputDir, argv = {}, noTty = false } = options;
47
+ console.log(`Cloning template from ${templateUrl}...`);
48
+ const tempDir = await (0, clone_1.cloneRepo)(templateUrl);
49
+ try {
50
+ console.log('Extracting template variables...');
51
+ const extractedVariables = await (0, extract_1.extractVariables)(tempDir);
52
+ console.log(`Found ${extractedVariables.fileReplacers.length} file replacers`);
53
+ console.log(`Found ${extractedVariables.contentReplacers.length} content replacers`);
54
+ if (extractedVariables.projectQuestions) {
55
+ console.log(`Found ${extractedVariables.projectQuestions.questions.length} project questions`);
56
+ }
57
+ console.log('Prompting for variable values...');
58
+ const answers = await (0, prompt_1.promptUser)(extractedVariables, argv, noTty);
59
+ console.log(`Generating project in ${outputDir}...`);
60
+ await (0, replace_1.replaceVariables)(tempDir, outputDir, extractedVariables, answers);
61
+ console.log('Project created successfully!');
62
+ return outputDir;
63
+ }
64
+ finally {
65
+ if (fs.existsSync(tempDir)) {
66
+ fs.rmSync(tempDir, { recursive: true, force: true });
67
+ }
68
+ }
69
+ }
70
+ exports.createGen = createGen;
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "create-gen-app",
3
+ "version": "0.1.0",
4
+ "author": "Dan Lynch <pyramation@gmail.com>",
5
+ "description": "Clone and customize template repositories",
6
+ "main": "index.js",
7
+ "module": "esm/index.js",
8
+ "types": "index.d.ts",
9
+ "homepage": "https://github.com/pyramation/inquirerer",
10
+ "license": "SEE LICENSE IN LICENSE",
11
+ "publishConfig": {
12
+ "access": "public",
13
+ "directory": "dist"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/pyramation/inquirerer"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/pyramation/inquirerer/issues"
21
+ },
22
+ "scripts": {
23
+ "copy": "copyfiles -f ../../LICENSE README.md package.json dist",
24
+ "clean": "del dist/**",
25
+ "prepare": "npm run build",
26
+ "build": "npm run clean; tsc; tsc -p tsconfig.esm.json; npm run copy",
27
+ "dev": "ts-node dev/index",
28
+ "test": "jest",
29
+ "test:watch": "jest --watch"
30
+ },
31
+ "dependencies": {
32
+ "inquirerer": "^2.1.0"
33
+ },
34
+ "keywords": [],
35
+ "gitHead": "e1b5c305be94ffa5f7c0664ad2d4e12f66ac046f"
36
+ }
package/prompt.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { 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 noTty - Whether to disable TTY mode
14
+ * @returns Answers from the user
15
+ */
16
+ export declare function promptUser(extractedVariables: ExtractedVariables, argv?: Record<string, any>, noTty?: boolean): Promise<Record<string, any>>;
package/prompt.js ADDED
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promptUser = exports.generateQuestions = void 0;
4
+ const inquirerer_1 = require("inquirerer");
5
+ /**
6
+ * Generate questions from extracted variables
7
+ * @param extractedVariables - Variables extracted from the template
8
+ * @returns Array of questions to prompt the user
9
+ */
10
+ function generateQuestions(extractedVariables) {
11
+ const questions = [];
12
+ const askedVariables = new Set();
13
+ if (extractedVariables.projectQuestions) {
14
+ for (const question of extractedVariables.projectQuestions.questions) {
15
+ questions.push(question);
16
+ askedVariables.add(question.name);
17
+ }
18
+ }
19
+ for (const replacer of extractedVariables.fileReplacers) {
20
+ if (!askedVariables.has(replacer.variable)) {
21
+ questions.push({
22
+ name: replacer.variable,
23
+ type: 'text',
24
+ message: `Enter value for ${replacer.variable}:`,
25
+ required: true
26
+ });
27
+ askedVariables.add(replacer.variable);
28
+ }
29
+ }
30
+ for (const replacer of extractedVariables.contentReplacers) {
31
+ if (!askedVariables.has(replacer.variable)) {
32
+ questions.push({
33
+ name: replacer.variable,
34
+ type: 'text',
35
+ message: `Enter value for ${replacer.variable}:`,
36
+ required: true
37
+ });
38
+ askedVariables.add(replacer.variable);
39
+ }
40
+ }
41
+ return questions;
42
+ }
43
+ exports.generateQuestions = generateQuestions;
44
+ /**
45
+ * Prompt the user for variable values
46
+ * @param extractedVariables - Variables extracted from the template
47
+ * @param argv - Command-line arguments to pre-populate answers
48
+ * @param noTty - Whether to disable TTY mode
49
+ * @returns Answers from the user
50
+ */
51
+ async function promptUser(extractedVariables, argv = {}, noTty = false) {
52
+ const questions = generateQuestions(extractedVariables);
53
+ if (questions.length === 0) {
54
+ return argv;
55
+ }
56
+ const prompter = new inquirerer_1.Inquirerer({
57
+ noTty
58
+ });
59
+ const answers = await prompter.prompt(argv, questions);
60
+ return answers;
61
+ }
62
+ exports.promptUser = promptUser;
package/replace.d.ts ADDED
@@ -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>;
package/replace.js ADDED
@@ -0,0 +1,109 @@
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 (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.replaceVariables = void 0;
27
+ const fs = __importStar(require("fs"));
28
+ const path = __importStar(require("path"));
29
+ const stream_1 = require("stream");
30
+ const promises_1 = require("stream/promises");
31
+ /**
32
+ * Replace variables in all files in the template directory
33
+ * @param templateDir - Path to the template directory
34
+ * @param outputDir - Path to the output directory
35
+ * @param extractedVariables - Variables extracted from the template
36
+ * @param answers - User answers for variable values
37
+ */
38
+ async function replaceVariables(templateDir, outputDir, extractedVariables, answers) {
39
+ if (!fs.existsSync(outputDir)) {
40
+ fs.mkdirSync(outputDir, { recursive: true });
41
+ }
42
+ await walkAndReplace(templateDir, outputDir, extractedVariables, answers);
43
+ }
44
+ exports.replaceVariables = replaceVariables;
45
+ /**
46
+ * Walk through directory and replace variables in files and filenames
47
+ * @param sourceDir - Source directory
48
+ * @param destDir - Destination directory
49
+ * @param extractedVariables - Variables extracted from the template
50
+ * @param answers - User answers for variable values
51
+ * @param sourceRelativePath - Current relative path in source (for recursion)
52
+ * @param destRelativePath - Current relative path in destination (for recursion)
53
+ */
54
+ async function walkAndReplace(sourceDir, destDir, extractedVariables, answers, sourceRelativePath = '', destRelativePath = '') {
55
+ const currentSource = path.join(sourceDir, sourceRelativePath);
56
+ const entries = fs.readdirSync(currentSource, { withFileTypes: true });
57
+ for (const entry of entries) {
58
+ const sourceEntryPath = path.join(currentSource, entry.name);
59
+ if (entry.name === '.questions.json' || entry.name === '.questions.js') {
60
+ continue;
61
+ }
62
+ let newName = entry.name;
63
+ for (const replacer of extractedVariables.fileReplacers) {
64
+ if (answers[replacer.variable] !== undefined) {
65
+ newName = newName.replace(replacer.pattern, String(answers[replacer.variable]));
66
+ }
67
+ }
68
+ const destEntryPath = path.join(destDir, destRelativePath, newName);
69
+ if (entry.isDirectory()) {
70
+ if (!fs.existsSync(destEntryPath)) {
71
+ fs.mkdirSync(destEntryPath, { recursive: true });
72
+ }
73
+ await walkAndReplace(sourceDir, destDir, extractedVariables, answers, path.join(sourceRelativePath, entry.name), path.join(destRelativePath, newName));
74
+ }
75
+ else if (entry.isFile()) {
76
+ await replaceInFile(sourceEntryPath, destEntryPath, extractedVariables, answers);
77
+ }
78
+ }
79
+ }
80
+ /**
81
+ * Replace variables in a file using streams
82
+ * @param sourcePath - Source file path
83
+ * @param destPath - Destination file path
84
+ * @param extractedVariables - Variables extracted from the template
85
+ * @param answers - User answers for variable values
86
+ */
87
+ async function replaceInFile(sourcePath, destPath, extractedVariables, answers) {
88
+ const destDir = path.dirname(destPath);
89
+ if (!fs.existsSync(destDir)) {
90
+ fs.mkdirSync(destDir, { recursive: true });
91
+ }
92
+ const replaceTransform = new stream_1.Transform({
93
+ transform(chunk, encoding, callback) {
94
+ let content = chunk.toString();
95
+ for (const replacer of extractedVariables.contentReplacers) {
96
+ if (answers[replacer.variable] !== undefined) {
97
+ content = content.replace(replacer.pattern, String(answers[replacer.variable]));
98
+ }
99
+ }
100
+ callback(null, Buffer.from(content));
101
+ }
102
+ });
103
+ try {
104
+ await (0, promises_1.pipeline)(fs.createReadStream(sourcePath), replaceTransform, fs.createWriteStream(destPath));
105
+ }
106
+ catch (error) {
107
+ fs.copyFileSync(sourcePath, destPath);
108
+ }
109
+ }
package/types.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ import { Question } from 'inquirerer';
2
+ /**
3
+ * Questions configuration that can be loaded from .questions.json or .questions.js
4
+ * @typedef {Object} Questions
5
+ * @property {Question[]} questions - Array of inquirerer questions
6
+ */
7
+ export interface Questions {
8
+ questions: Question[];
9
+ }
10
+ /**
11
+ * Variable extracted from filename patterns like __VARIABLE__
12
+ */
13
+ export interface FileReplacer {
14
+ variable: string;
15
+ pattern: RegExp;
16
+ }
17
+ /**
18
+ * Variable extracted from file content patterns like __VARIABLE__
19
+ */
20
+ export interface ContentReplacer {
21
+ variable: string;
22
+ pattern: RegExp;
23
+ }
24
+ /**
25
+ * Options for creating a new project from a template
26
+ */
27
+ export interface CreateGenOptions {
28
+ /**
29
+ * URL or path to the template repository
30
+ */
31
+ templateUrl: string;
32
+ /**
33
+ * Destination directory for the generated project
34
+ */
35
+ outputDir: string;
36
+ /**
37
+ * Command-line arguments to pre-populate answers
38
+ */
39
+ argv?: Record<string, any>;
40
+ /**
41
+ * Whether to use TTY for interactive prompts
42
+ */
43
+ noTty?: boolean;
44
+ }
45
+ /**
46
+ * Result of extracting variables from a template
47
+ */
48
+ export interface ExtractedVariables {
49
+ fileReplacers: FileReplacer[];
50
+ contentReplacers: ContentReplacer[];
51
+ projectQuestions: Questions | null;
52
+ }
53
+ /**
54
+ * Context for processing a template
55
+ */
56
+ export interface TemplateContext {
57
+ tempDir: string;
58
+ extractedVariables: ExtractedVariables;
59
+ answers: Record<string, any>;
60
+ }
package/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });