genomic 4.0.1 → 5.0.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.
Files changed (92) hide show
  1. package/README.md +153 -1127
  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
package/README.md CHANGED
@@ -11,13 +11,19 @@
11
11
  <a href="https://github.com/constructive-io/dev-utils/blob/main/LICENSE">
12
12
  <img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/>
13
13
  </a>
14
- <a href="https://www.npmjs.com/package/genomic"><img height="20" src="https://img.shields.io/npm/dt/genomic"></a>
15
- <a href="https://www.npmjs.com/package/genomic"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/dev-utils?filename=packages%2Fgenomic%2Fpackage.json"></a>
14
+ <a href="https://www.npmjs.com/package/genomic"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/dev-utils?filename=packages%2Fscaffolds%2Fpackage.json"></a>
16
15
  </p>
17
16
 
18
- > Formerly [`inquirerer`](https://www.npmjs.com/package/inquirerer)
17
+ A TypeScript-first library for cloning template repositories, asking the user for variables, and generating a new project with sensible defaults.
19
18
 
20
- A powerful, TypeScript-first library for building beautiful command-line interfaces.Create interactive CLI tools with ease using intuitive prompts, validation, and rich user experiences.
19
+ ## Features
20
+
21
+ - Clone any Git repo (or GitHub `org/repo` shorthand) and optionally select a branch + subdirectory
22
+ - Extract template variables from filenames and file contents using the safer `____variable____` convention
23
+ - Merge auto-discovered variables with `.questions.{json,js}` (questions win)
24
+ - Interactive prompts powered by `genomic`, with flexible override mapping (`argv` support) and non-TTY mode for CI
25
+ - License scaffolding: choose from MIT, Apache-2.0, ISC, GPL-3.0, BSD-3-Clause, Unlicense, or MPL-2.0 and generate a populated `LICENSE`
26
+ - Built-in template caching powered by `appstash`, so repeat runs skip `git clone` (configurable via `cache` options; TTL is opt-in)
21
27
 
22
28
  ## Installation
23
29
 
@@ -25,1190 +31,210 @@ A powerful, TypeScript-first library for building beautiful command-line interfa
25
31
  npm install genomic
26
32
  ```
27
33
 
28
- ## Features
34
+ > **Note:** The published package is API-only. An internal CLI harness used for integration testing now lives in `packages/genomic-test/`.
29
35
 
30
- - 🔌 **CLI Builder** - Build command-line utilties fast
31
- - 🖊 **Multiple Question Types** - Support for text, autocomplete, checkbox, and confirm questions
32
- - 🤖 **Non-Interactive Mode** - Fallback to defaults for CI/CD environments, great for testing
33
- - ✅ **Smart Validation** - Built-in pattern matching, custom validators, and sanitizers
34
- - 🔀 **Conditional Logic** - Show/hide questions based on previous answers
35
- - 🎨 **Interactive UX** - Fuzzy search, keyboard navigation, and visual feedback
36
- - 🔄 **Dynamic Defaults** - Auto-populate defaults from git config, date/time, or custom resolvers
36
+ ## Library Usage
37
37
 
38
- ## Table of Contents
38
+ `genomic` provides both a high-level orchestrator and modular building blocks for template scaffolding.
39
39
 
40
- - [Quick Start](#quick-start)
41
- - [Core Concepts](#core-concepts)
42
- - [TypeScript Support](#typescript-support)
43
- - [Question Types](#question-types)
44
- - [Non-Interactive Mode](#non-interactive-mode)
45
- - [API Reference](#api-reference)
46
- - [Prompter Class](#genomic-class)
47
- - [Question Types](#question-types-1)
48
- - [Text Question](#text-question)
49
- - [Number Question](#number-question)
50
- - [Confirm Question](#confirm-question)
51
- - [List Question](#list-question)
52
- - [Autocomplete Question](#autocomplete-question)
53
- - [Checkbox Question](#checkbox-question)
54
- - [Advanced Question Options](#advanced-question-options)
55
- - [Positional Arguments](#positional-arguments)
56
- - [Real-World Examples](#real-world-examples)
57
- - [Project Setup Wizard](#project-setup-wizard)
58
- - [Configuration Builder](#configuration-builder)
59
- - [CLI with Commander Integration](#cli-with-commander-integration)
60
- - [Dynamic Dependencies](#dynamic-dependencies)
61
- - [Custom Validation](#custom-validation)
62
- - [Dynamic Defaults with Resolvers](#dynamic-defaults-with-resolvers)
63
- - [Built-in Resolvers](#built-in-resolvers)
64
- - [Custom Resolvers](#custom-resolvers)
65
- - [Resolver Examples](#resolver-examples)
66
- - [CLI Helper](#cli-helper)
67
- - [Developing](#developing)
40
+ ### Quick Start with TemplateScaffolder
68
41
 
69
- ## Quick Start
42
+ The easiest way to use `genomic` is with the `TemplateScaffolder` class, which combines caching, cloning, and template processing into a single API:
70
43
 
71
44
  ```typescript
72
- import { Prompter } from 'genomic';
45
+ import { TemplateScaffolder } from 'genomic';
73
46
 
74
- const prompter = new Prompter();
47
+ const scaffolder = new TemplateScaffolder({
48
+ toolName: 'my-cli', // Cache directory: ~/.my-cli/cache
49
+ defaultRepo: 'org/my-templates', // Default template repository
50
+ ttlMs: 7 * 24 * 60 * 60 * 1000, // Cache TTL: 1 week
51
+ });
75
52
 
76
- const answers = await prompter.prompt({}, [
77
- {
78
- type: 'text',
79
- name: 'username',
80
- message: 'What is your username?',
81
- required: true
82
- },
83
- {
84
- type: 'confirm',
85
- name: 'newsletter',
86
- message: 'Subscribe to our newsletter?',
87
- default: true
88
- }
89
- ]);
53
+ // Scaffold a project from the default repo
54
+ await scaffolder.scaffold({
55
+ outputDir: './my-project',
56
+ fromPath: 'starter', // Use the "starter" template variant
57
+ answers: { projectName: 'my-app' }, // Pre-populate answers
58
+ });
90
59
 
91
- console.log(answers);
92
- // { username: 'john_doe', newsletter: true }
60
+ // Or scaffold from a specific repo
61
+ await scaffolder.scaffold({
62
+ template: 'https://github.com/other/templates.git',
63
+ outputDir: './another-project',
64
+ branch: 'v2',
65
+ });
93
66
  ```
94
67
 
95
- ## Core Concepts
96
-
97
- ### TypeScript Support
98
-
99
- Import types for full type safety:
100
-
101
- ```typescript
102
- import {
103
- Prompter,
104
- Question,
105
- TextQuestion,
106
- NumberQuestion,
107
- ConfirmQuestion,
108
- ListQuestion,
109
- AutocompleteQuestion,
110
- CheckboxQuestion,
111
- PrompterOptions,
112
- DefaultResolverRegistry,
113
- registerDefaultResolver,
114
- resolveDefault
115
- } from 'genomic';
68
+ ### Template Repository Conventions
116
69
 
117
- interface UserConfig {
118
- name: string;
119
- age: number;
120
- newsletter: boolean;
121
- }
70
+ `TemplateScaffolder` supports the `.boilerplates.json` convention for organizing multiple templates in a single repository:
122
71
 
123
- const answers = await prompter.prompt<UserConfig>({}, questions);
124
- // answers is typed as UserConfig
125
72
  ```
126
-
127
- ### Question Types
128
-
129
- All questions support these base properties:
130
-
131
- ```typescript
132
- interface BaseQuestion {
133
- name: string; // Property name in result object
134
- type: string; // Question type
135
- _?: boolean; // Mark as positional argument (can be passed without --name flag)
136
- message?: string; // Prompt message to display
137
- description?: string; // Additional context
138
- default?: any; // Default value
139
- defaultFrom?: string; // Dynamic default from resolver (e.g., 'git.user.name')
140
- setFrom?: string; // Auto-set value from resolver, bypassing prompt entirely
141
- useDefault?: boolean; // Skip prompt and use default
142
- required?: boolean; // Validation requirement
143
- validate?: (input: any, answers: any) => boolean | Validation;
144
- sanitize?: (input: any, answers: any) => any;
145
- pattern?: string; // Regex pattern for validation
146
- dependsOn?: string[]; // Question dependencies
147
- when?: (answers: any) => boolean; // Conditional display
148
- }
73
+ my-templates/
74
+ ├── .boilerplates.json # { "dir": "templates" }
75
+ └── templates/
76
+ ├── starter/
77
+ │ ├── .boilerplate.json
78
+ │ └── ...template files...
79
+ └── advanced/
80
+ ├── .boilerplate.json
81
+ └── ...template files...
149
82
  ```
150
83
 
151
- ### Non-Interactive Mode
84
+ When you call `scaffold({ fromPath: 'starter' })`, the scaffolder will:
85
+ 1. Check if `starter/` exists directly in the repo root
86
+ 2. If not, read `.boilerplates.json` and look for `templates/starter/`
152
87
 
153
- When running in CI/CD or without a TTY, genomic automatically falls back to default values:
88
+ ### Core Components (Building Blocks)
154
89
 
155
- ```typescript
156
- const prompter = new Prompter({
157
- noTty: true, // Force non-interactive mode
158
- useDefaults: true // Use defaults without prompting
159
- });
160
- ```
90
+ For more control, you can use the individual components directly:
161
91
 
162
- ## API Reference
92
+ - **CacheManager**: Handles local caching of git repositories with TTL support
93
+ - **GitCloner**: Handles cloning git repositories
94
+ - **Templatizer**: Handles variable extraction, user prompting, and template generation
163
95
 
164
- ### Prompter Class
96
+ ### Example: Manual Orchestration
165
97
 
166
- #### Constructor Options
98
+ Here is how you can combine these components to create a full CLI pipeline (similar to `genomic-test`):
167
99
 
168
100
  ```typescript
169
- interface PrompterOptions {
170
- noTty?: boolean; // Disable interactive mode
171
- input?: Readable; // Input stream (default: process.stdin)
172
- output?: Writable; // Output stream (default: process.stdout)
173
- useDefaults?: boolean; // Skip prompts and use defaults
174
- globalMaxLines?: number; // Max lines for list displays (default: 10)
175
- mutateArgs?: boolean; // Mutate argv object (default: true)
176
- resolverRegistry?: DefaultResolverRegistry; // Custom resolver registry
177
- }
178
-
179
- const prompter = new Prompter(options);
180
- ```
181
-
182
- #### Methods
183
-
184
- ```typescript
185
- // Main prompt method
186
- prompt<T>(argv: T, questions: Question[], options?: PromptOptions): Promise<T>
187
-
188
- // Generate man page documentation
189
- generateManPage(info: ManPageInfo): string
190
-
191
- // Clean up resources
192
- close(): void
193
- exit(): void
194
- ```
195
-
196
- #### Managing Multiple Instances
101
+ import * as path from "path";
102
+ import { CacheManager, GitCloner, Templatizer } from "genomic";
197
103
 
198
- When working with multiple `Prompter` instances that share the same input stream (typically `process.stdin`), only one instance should be actively prompting at a time. Each instance attaches its own keyboard listener, so having multiple active instances will cause duplicate or unexpected keypress behavior.
104
+ async function main() {
105
+ const repoUrl = "https://github.com/user/template-repo";
106
+ const outputDir = "./my-new-project";
199
107
 
200
- **Best practices:**
108
+ // 1. Initialize components
109
+ const cacheManager = new CacheManager({
110
+ toolName: "my-cli", // ~/.my-cli/cache
111
+ // ttl is optional; omit to keep cache forever, or set (e.g., 1 week) to enable expiration
112
+ // ttl: 604800000,
113
+ });
114
+ const gitCloner = new GitCloner();
115
+ const templatizer = new Templatizer();
201
116
 
202
- 1. **Reuse a single instance** - Create one `Prompter` instance and reuse it for all prompts:
203
- ```typescript
204
- const prompter = new Prompter();
205
-
206
- // Use the same instance for multiple prompt sessions
207
- const answers1 = await prompter.prompt({}, questions1);
208
- const answers2 = await prompter.prompt({}, questions2);
209
-
210
- prompter.close(); // Clean up when done
211
- ```
117
+ // 2. Resolve template path (Cache or Clone)
118
+ const normalizedUrl = gitCloner.normalizeUrl(repoUrl);
119
+ const cacheKey = cacheManager.createKey(normalizedUrl);
120
+
121
+ // Check cache
122
+ let templateDir = cacheManager.get(cacheKey);
123
+ const isExpired = cacheManager.checkExpiration(cacheKey);
212
124
 
213
- 2. **Close before creating another** - If you need separate instances, close the first before using the second:
214
- ```typescript
215
- const prompter1 = new Prompter();
216
- const answers1 = await prompter1.prompt({}, questions1);
217
- prompter1.close(); // Important: close before creating another
218
-
219
- const prompter2 = new Prompter();
220
- const answers2 = await prompter2.prompt({}, questions2);
221
- prompter2.close();
222
- ```
223
-
224
- ### Question Types
225
-
226
- #### Text Question
227
-
228
- Collect string input from users.
229
-
230
- ```typescript
231
- {
232
- type: 'text',
233
- name: 'projectName',
234
- message: 'What is your project name?',
235
- default: 'my-app',
236
- required: true,
237
- pattern: '^[a-z0-9-]+$', // Regex validation
238
- validate: (input) => {
239
- if (input.length < 3) {
240
- return { success: false, reason: 'Name must be at least 3 characters' };
241
- }
242
- return true;
125
+ if (!templateDir || isExpired) {
126
+ console.log("Cloning template...");
127
+ if (isExpired) cacheManager.clear(cacheKey);
128
+
129
+ // Clone to a temporary location managed by CacheManager
130
+ const tempDest = path.join(cacheManager.getReposDir(), cacheKey);
131
+ await gitCloner.clone(normalizedUrl, tempDest, { depth: 1 });
132
+
133
+ // Register and update cache
134
+ cacheManager.set(cacheKey, tempDest);
135
+ templateDir = tempDest;
243
136
  }
244
- }
245
- ```
246
137
 
247
- #### Number Question
248
-
249
- Collect numeric input.
250
-
251
- ```typescript
252
- {
253
- type: 'number',
254
- name: 'port',
255
- message: 'Which port to use?',
256
- default: 3000,
257
- validate: (input) => {
258
- if (input < 1 || input > 65535) {
259
- return { success: false, reason: 'Port must be between 1 and 65535' };
138
+ // 3. Process Template
139
+ await templatizer.process(templateDir, outputDir, {
140
+ argv: {
141
+ PROJECT_NAME: "my-app",
142
+ LICENSE: "MIT"
260
143
  }
261
- return true;
262
- }
263
- }
264
- ```
265
-
266
- #### Confirm Question
267
-
268
- Yes/no questions.
269
-
270
- ```typescript
271
- {
272
- type: 'confirm',
273
- name: 'useTypeScript',
274
- message: 'Use TypeScript?',
275
- default: true // Default to 'yes'
276
- }
277
- ```
278
-
279
- #### List Question
280
-
281
- Select one option from a list (no search).
282
-
283
- ```typescript
284
- {
285
- type: 'list',
286
- name: 'license',
287
- message: 'Choose a license',
288
- options: ['MIT', 'Apache-2.0', 'GPL-3.0', 'BSD-3-Clause'],
289
- default: 'MIT',
290
- maxDisplayLines: 5
144
+ });
291
145
  }
292
146
  ```
293
147
 
294
- #### Autocomplete Question
148
+ ### Template Variables
295
149
 
296
- Select with fuzzy search capabilities.
150
+ Variables should be wrapped in four underscores on each side:
297
151
 
298
- ```typescript
299
- {
300
- type: 'autocomplete',
301
- name: 'framework',
302
- message: 'Choose your framework',
303
- options: [
304
- { name: 'React', value: 'react' },
305
- { name: 'Vue.js', value: 'vue' },
306
- { name: 'Angular', value: 'angular' },
307
- { name: 'Svelte', value: 'svelte' }
308
- ],
309
- allowCustomOptions: true, // Allow user to enter custom value
310
- maxDisplayLines: 8
311
- }
312
152
  ```
313
-
314
- #### Checkbox Question
315
-
316
- Multi-select with search.
317
-
318
- ```typescript
319
- {
320
- type: 'checkbox',
321
- name: 'features',
322
- message: 'Select features to include',
323
- options: [
324
- 'Authentication',
325
- 'Database',
326
- 'API Routes',
327
- 'Testing',
328
- 'Documentation'
329
- ],
330
- default: ['Authentication', 'API Routes'],
331
- returnFullResults: false, // Only return selected items
332
- required: true
333
- }
153
+ ____projectName____/
154
+ src/____moduleName____.ts
334
155
  ```
335
156
 
336
- With `returnFullResults: true`, returns all options with selection status:
337
-
338
157
  ```typescript
339
- [
340
- { name: 'Authentication', value: 'Authentication', selected: true },
341
- { name: 'Database', value: 'Database', selected: false },
342
- // ...
343
- ]
158
+ // ____moduleName____.ts
159
+ export const projectName = "____projectName____";
160
+ export const author = "____fullName____";
344
161
  ```
345
162
 
346
- ### Advanced Question Options
163
+ ### Custom Questions
347
164
 
348
- #### Custom Validation
165
+ Create a `.boilerplate.json`:
349
166
 
350
- ```typescript
351
- {
352
- type: 'text',
353
- name: 'email',
354
- message: 'Enter your email',
355
- pattern: '^[^@]+@[^@]+\\.[^@]+$',
356
- validate: (email, answers) => {
357
- // Custom async validation possible
358
- if (email.endsWith('@example.com')) {
359
- return {
360
- success: false,
361
- reason: 'Please use a real email address'
362
- };
363
- }
364
- return { success: true };
365
- }
366
- }
367
- ```
368
-
369
- #### Value Sanitization
370
-
371
- ```typescript
167
+ ```json
372
168
  {
373
- type: 'text',
374
- name: 'tags',
375
- message: 'Enter tags (comma-separated)',
376
- sanitize: (input) => {
377
- return input.split(',').map(tag => tag.trim());
378
- }
379
- }
380
- ```
381
-
382
- #### Conditional Questions
383
-
384
- ```typescript
385
- const questions: Question[] = [
386
- {
387
- type: 'confirm',
388
- name: 'useDatabase',
389
- message: 'Do you need a database?',
390
- default: false
391
- },
392
- {
393
- type: 'list',
394
- name: 'database',
395
- message: 'Which database?',
396
- options: ['PostgreSQL', 'MySQL', 'MongoDB', 'SQLite'],
397
- when: (answers) => answers.useDatabase === true // Only show if useDatabase is true
398
- }
399
- ];
400
- ```
401
-
402
- #### Question Dependencies
403
-
404
- Ensure questions appear in the correct order:
405
-
406
- ```typescript
407
- [
408
- {
409
- type: 'checkbox',
410
- name: 'services',
411
- message: 'Select services',
412
- options: ['Auth', 'Storage', 'Functions']
413
- },
414
- {
415
- type: 'text',
416
- name: 'authProvider',
417
- message: 'Which auth provider?',
418
- dependsOn: ['services'], // Wait for services question
419
- when: (answers) => {
420
- const selected = answers.services.find(s => s.name === 'Auth');
421
- return selected?.selected === true;
422
- }
423
- }
424
- ]
425
- ```
426
-
427
- ### Positional Arguments
428
-
429
- The `_` property allows you to name positional parameters, enabling users to pass values without flags. This is useful for CLI tools where the first few arguments have obvious meanings.
430
-
431
- #### Basic Usage
432
-
433
- ```typescript
434
- const questions: Question[] = [
435
- {
436
- _: true,
437
- name: 'database',
438
- type: 'text',
439
- message: 'Database name',
440
- required: true
441
- }
442
- ];
443
-
444
- const argv = minimist(process.argv.slice(2));
445
- const result = await prompter.prompt(argv, questions);
446
- ```
447
-
448
- Now users can run either:
449
- ```bash
450
- node myprogram.js mydb1
451
- # or equivalently:
452
- node myprogram.js --database mydb1
453
- ```
454
-
455
- #### Multiple Positional Arguments
456
-
457
- Positional arguments are assigned in declaration order:
458
-
459
- ```typescript
460
- const questions: Question[] = [
461
- { _: true, name: 'source', type: 'text', message: 'Source file' },
462
- { name: 'verbose', type: 'confirm', default: false },
463
- { _: true, name: 'destination', type: 'text', message: 'Destination file' }
464
- ];
465
-
466
- // Running: node copy.js input.txt output.txt --verbose
467
- // Results in: { source: 'input.txt', destination: 'output.txt', verbose: true }
468
- ```
469
-
470
- #### Named Arguments Take Precedence
471
-
472
- When both positional and named arguments are provided, named arguments win and the positional slot is preserved for the next positional question:
473
-
474
- ```typescript
475
- const questions: Question[] = [
476
- { _: true, name: 'foo', type: 'text' },
477
- { _: true, name: 'bar', type: 'text' },
478
- { _: true, name: 'baz', type: 'text' }
479
- ];
480
-
481
- // Running: node myprogram.js pos1 pos2 --bar named-bar
482
- // Results in: { foo: 'pos1', bar: 'named-bar', baz: 'pos2' }
483
- ```
484
-
485
- In this example, `bar` gets its value from the named flag, so the two positional values go to `foo` and `baz`.
486
-
487
- #### Positional with Options
488
-
489
- Positional arguments work with list, autocomplete, and checkbox questions. The value is mapped through the options:
490
-
491
- ```typescript
492
- const questions: Question[] = [
493
- {
494
- _: true,
495
- name: 'framework',
496
- type: 'list',
497
- options: [
498
- { name: 'React', value: 'react' },
499
- { name: 'Vue', value: 'vue' }
500
- ]
501
- }
502
- ];
503
-
504
- // Running: node setup.js React
505
- // Results in: { framework: 'react' }
506
- ```
507
-
508
- ## Real-World Examples
509
-
510
- ### Project Setup Wizard
511
-
512
- ```typescript
513
- import { Prompter, Question } from 'genomic';
514
- import minimist from 'minimist';
515
-
516
- const argv = minimist(process.argv.slice(2));
517
- const prompter = new Prompter();
518
-
519
- const questions: Question[] = [
520
- {
521
- type: 'text',
522
- name: 'projectName',
523
- message: 'Project name',
524
- required: true,
525
- pattern: '^[a-z0-9-]+$'
526
- },
527
- {
528
- type: 'text',
529
- name: 'description',
530
- message: 'Project description',
531
- default: 'My awesome project'
532
- },
533
- {
534
- type: 'confirm',
535
- name: 'typescript',
536
- message: 'Use TypeScript?',
537
- default: true
538
- },
539
- {
540
- type: 'autocomplete',
541
- name: 'framework',
542
- message: 'Choose a framework',
543
- options: ['React', 'Vue', 'Svelte', 'None'],
544
- default: 'React'
545
- },
546
- {
547
- type: 'checkbox',
548
- name: 'tools',
549
- message: 'Additional tools',
550
- options: ['ESLint', 'Prettier', 'Jest', 'Husky'],
551
- default: ['ESLint', 'Prettier']
552
- }
553
- ];
554
-
555
- const config = await prompter.prompt(argv, questions);
556
- console.log('Creating project with:', config);
557
- ```
558
-
559
- Run interactively:
560
- ```bash
561
- node setup.js
562
- ```
563
-
564
- Or with CLI args:
565
- ```bash
566
- node setup.js --projectName=my-app --typescript --framework=React
567
- ```
568
-
569
- ### Configuration Builder
570
-
571
- ```typescript
572
- interface AppConfig {
573
- port: number;
574
- host: string;
575
- ssl: boolean;
576
- sslCert?: string;
577
- sslKey?: string;
578
- database: string;
579
- logLevel: string;
580
- }
581
-
582
- const questions: Question[] = [
583
- {
584
- type: 'number',
585
- name: 'port',
586
- message: 'Server port',
587
- default: 3000,
588
- validate: (port) => port > 0 && port < 65536
589
- },
590
- {
591
- type: 'text',
592
- name: 'host',
593
- message: 'Server host',
594
- default: '0.0.0.0'
595
- },
596
- {
597
- type: 'confirm',
598
- name: 'ssl',
599
- message: 'Enable SSL?',
600
- default: false
601
- },
602
- {
603
- type: 'text',
604
- name: 'sslCert',
605
- message: 'SSL certificate path',
606
- when: (answers) => answers.ssl === true,
607
- required: true
608
- },
609
- {
610
- type: 'text',
611
- name: 'sslKey',
612
- message: 'SSL key path',
613
- when: (answers) => answers.ssl === true,
614
- required: true
615
- },
616
- {
617
- type: 'list',
618
- name: 'database',
619
- message: 'Database type',
620
- options: ['PostgreSQL', 'MySQL', 'SQLite'],
621
- default: 'PostgreSQL'
622
- },
623
- {
624
- type: 'list',
625
- name: 'logLevel',
626
- message: 'Log level',
627
- options: ['error', 'warn', 'info', 'debug'],
628
- default: 'info'
629
- }
630
- ];
631
-
632
- const config = await prompter.prompt<AppConfig>(argv, questions);
633
-
634
- // Write config to file
635
- fs.writeFileSync('config.json', JSON.stringify(config, null, 2));
636
- ```
637
-
638
- ### CLI with Commander Integration
639
-
640
- ```typescript
641
- import { CLI, CommandHandler } from 'genomic';
642
- import { Question } from 'genomic';
643
-
644
- const handler: CommandHandler = async (argv, prompter, options) => {
645
- const questions: Question[] = [
169
+ "type": "module",
170
+ "questions": [
646
171
  {
647
- type: 'text',
648
- name: 'name',
649
- message: 'What is your name?',
650
- required: true
172
+ "name": "____fullName____",
173
+ "type": "text",
174
+ "message": "Enter author full name",
175
+ "required": true
651
176
  },
652
177
  {
653
- type: 'number',
654
- name: 'age',
655
- message: 'What is your age?',
656
- validate: (age) => age >= 0 && age <= 120
178
+ "name": "____license____",
179
+ "type": "list",
180
+ "message": "Choose a license",
181
+ "options": ["MIT", "Apache-2.0", "ISC", "GPL-3.0"]
657
182
  }
658
- ];
659
-
660
- const answers = await prompter.prompt(argv, questions);
661
- console.log('Hello,', answers.name);
662
- };
663
-
664
- const cli = new CLI(handler, {
665
- version: 'myapp@1.0.0',
666
- minimistOpts: {
667
- alias: {
668
- n: 'name',
669
- a: 'age',
670
- v: 'version'
671
- }
672
- }
673
- });
674
-
675
- await cli.run();
676
- ```
677
-
678
- ### Dynamic Dependencies
679
-
680
- ```typescript
681
- const questions: Question[] = [
682
- {
683
- type: 'checkbox',
684
- name: 'cloud',
685
- message: 'Select cloud services',
686
- options: ['AWS', 'Azure', 'GCP'],
687
- returnFullResults: true
688
- },
689
- {
690
- type: 'text',
691
- name: 'awsRegion',
692
- message: 'AWS Region',
693
- dependsOn: ['cloud'],
694
- when: (answers) => {
695
- const aws = answers.cloud?.find(c => c.name === 'AWS');
696
- return aws?.selected === true;
697
- },
698
- default: 'us-east-1'
699
- },
700
- {
701
- type: 'text',
702
- name: 'azureLocation',
703
- message: 'Azure Location',
704
- dependsOn: ['cloud'],
705
- when: (answers) => {
706
- const azure = answers.cloud?.find(c => c.name === 'Azure');
707
- return azure?.selected === true;
708
- },
709
- default: 'eastus'
710
- },
711
- {
712
- type: 'text',
713
- name: 'gcpZone',
714
- message: 'GCP Zone',
715
- dependsOn: ['cloud'],
716
- when: (answers) => {
717
- const gcp = answers.cloud?.find(c => c.name === 'GCP');
718
- return gcp?.selected === true;
719
- },
720
- default: 'us-central1-a'
721
- }
722
- ];
723
-
724
- const config = await prompter.prompt({}, questions);
725
- ```
726
-
727
- ### Custom Validation
728
-
729
- ```typescript
730
- const questions: Question[] = [
731
- {
732
- type: 'text',
733
- name: 'username',
734
- message: 'Choose a username',
735
- required: true,
736
- pattern: '^[a-zA-Z0-9_]{3,20}$',
737
- validate: async (username) => {
738
- // Simulate API call to check availability
739
- const available = await checkUsernameAvailability(username);
740
- if (!available) {
741
- return {
742
- success: false,
743
- reason: 'Username is already taken'
744
- };
745
- }
746
- return { success: true };
747
- }
748
- },
749
- {
750
- type: 'text',
751
- name: 'password',
752
- message: 'Choose a password',
753
- required: true,
754
- validate: (password) => {
755
- if (password.length < 8) {
756
- return {
757
- success: false,
758
- reason: 'Password must be at least 8 characters'
759
- };
760
- }
761
- if (!/[A-Z]/.test(password)) {
762
- return {
763
- success: false,
764
- reason: 'Password must contain an uppercase letter'
765
- };
766
- }
767
- if (!/[0-9]/.test(password)) {
768
- return {
769
- success: false,
770
- reason: 'Password must contain a number'
771
- };
772
- }
773
- return { success: true };
774
- }
775
- },
776
- {
777
- type: 'text',
778
- name: 'confirmPassword',
779
- message: 'Confirm password',
780
- required: true,
781
- dependsOn: ['password'],
782
- validate: (confirm, answers) => {
783
- if (confirm !== answers.password) {
784
- return {
785
- success: false,
786
- reason: 'Passwords do not match'
787
- };
788
- }
789
- return { success: true };
790
- }
791
- }
792
- ];
793
- ```
794
-
795
- ## Dynamic Defaults with Resolvers
796
-
797
- The `defaultFrom` feature allows you to automatically populate question defaults from dynamic sources like git configuration, environment variables, date/time values, or custom resolvers. This eliminates repetitive boilerplate code for common default values.
798
-
799
- ### Quick Example
800
-
801
- ```typescript
802
- import { Prompter } from 'genomic';
803
-
804
- const questions = [
805
- {
806
- type: 'text',
807
- name: 'authorName',
808
- message: 'Author name?',
809
- defaultFrom: 'git.user.name' // Auto-fills from git config
810
- },
811
- {
812
- type: 'text',
813
- name: 'authorEmail',
814
- message: 'Author email?',
815
- defaultFrom: 'git.user.email' // Auto-fills from git config
816
- },
817
- {
818
- type: 'text',
819
- name: 'npmUser',
820
- message: 'NPM username?',
821
- defaultFrom: 'npm.whoami' // Auto-fills from npm whoami
822
- },
823
- {
824
- type: 'text',
825
- name: 'copyrightYear',
826
- message: 'Copyright year?',
827
- defaultFrom: 'date.year' // Auto-fills current year
828
- }
829
- ];
830
-
831
- const prompter = new Prompter();
832
- const answers = await prompter.prompt({}, questions);
833
- ```
834
-
835
- ### Built-in Resolvers
836
-
837
- Prompter comes with several built-in resolvers ready to use:
838
-
839
- #### Git Configuration
840
-
841
- | Resolver | Description | Example Output |
842
- |----------|-------------|----------------|
843
- | `git.user.name` | Git global user name | `"John Doe"` |
844
- | `git.user.email` | Git global user email | `"john@example.com"` |
845
-
846
- #### NPM
847
-
848
- | Resolver | Description | Example Output |
849
- |----------|-------------|----------------|
850
- | `npm.whoami` | Currently logged in npm user | `"johndoe"` |
851
-
852
- #### Date & Time
853
-
854
- | Resolver | Description | Example Output |
855
- |----------|-------------|----------------|
856
- | `date.year` | Current year | `"2025"` |
857
- | `date.month` | Current month (zero-padded) | `"11"` |
858
- | `date.day` | Current day (zero-padded) | `"23"` |
859
- | `date.iso` | ISO date (YYYY-MM-DD) | `"2025-11-23"` |
860
- | `date.now` | ISO timestamp | `"2025-11-23T15:30:45.123Z"` |
861
- | `date.timestamp` | Unix timestamp (ms) | `"1732375845123"` |
862
-
863
- #### Workspace (nearest package.json)
864
-
865
- | Resolver | Description | Example Output |
866
- |----------|-------------|----------------|
867
- | `workspace.name` | Repo slug from `repository` URL (fallback: `package.json` `name`) | `"dev-utils"` |
868
- | `workspace.repo.name` | Repo name from `repository` URL | `"dev-utils"` |
869
- | `workspace.repo.organization` | Repo org/owner from `repository` URL | `"constructive-io"` |
870
- | `workspace.organization.name` | Alias for `workspace.repo.organization` | `"constructive-io"` |
871
- | `workspace.license` | License field from `package.json` | `"MIT"` |
872
- | `workspace.author` | Author name from `package.json` | `"Constructive"` |
873
- | `workspace.author.name` | Author name from `package.json` | `"Constructive"` |
874
- | `workspace.author.email` | Author email from `package.json` | `"email@example.org"` |
875
-
876
- ### Priority Order
877
-
878
- When resolving default values, genomic follows this priority:
879
-
880
- 1. **CLI Arguments** - Values passed via command line (highest priority)
881
- 2. **`setFrom`** - Auto-set values (bypasses prompt entirely)
882
- 3. **`defaultFrom`** - Dynamically resolved default values
883
- 4. **`default`** - Static default values
884
- 5. **`undefined`** - No default available
885
-
886
- ```typescript
887
- {
888
- type: 'text',
889
- name: 'author',
890
- defaultFrom: 'git.user.name', // Try git first
891
- default: 'Anonymous' // Fallback if git not configured
892
- }
893
- ```
894
-
895
- ### `setFrom` vs `defaultFrom`
896
-
897
- Both `setFrom` and `defaultFrom` use resolvers to get values, but they behave differently:
898
-
899
- | Feature | `defaultFrom` | `setFrom` |
900
- |---------|---------------|-----------|
901
- | Sets value as | Default (user can override) | Final value (no prompt) |
902
- | User prompted? | Yes, with pre-filled default | No, question is skipped |
903
- | Use case | Suggested values | Auto-computed values |
904
-
905
- **`defaultFrom`** - The resolved value becomes the default, but the user is still prompted and can change it:
906
-
907
- ```typescript
908
- {
909
- type: 'text',
910
- name: 'authorName',
911
- message: 'Author name?',
912
- defaultFrom: 'git.user.name' // User sees "Author name? [John Doe]" and can change it
913
- }
914
- ```
915
-
916
- **`setFrom`** - The resolved value is set directly and the question is skipped entirely:
917
-
918
- ```typescript
919
- {
920
- type: 'text',
921
- name: 'year',
922
- message: 'Copyright year?',
923
- setFrom: 'date.year' // Automatically set to "2025", no prompt shown
924
- }
925
- ```
926
-
927
- #### When to use each
928
-
929
- Use `defaultFrom` when:
930
- - The value is a suggestion the user might want to change
931
- - User confirmation is desired
932
-
933
- Use `setFrom` when:
934
- - The value should be computed automatically
935
- - No user input is needed (e.g., timestamps, computed fields)
936
- - You want to reduce the number of prompts
937
-
938
- #### Combined example
939
-
940
- ```typescript
941
- const questions = [
942
- {
943
- type: 'text',
944
- name: 'authorName',
945
- message: 'Author name?',
946
- defaultFrom: 'git.user.name' // User can override
947
- },
948
- {
949
- type: 'text',
950
- name: 'createdAt',
951
- setFrom: 'date.iso' // Auto-set, no prompt
952
- },
953
- {
954
- type: 'text',
955
- name: 'copyrightYear',
956
- setFrom: 'date.year' // Auto-set, no prompt
957
- }
958
- ];
959
-
960
- // User only sees prompt for authorName
961
- // createdAt and copyrightYear are set automatically
962
- ```
963
-
964
- ### Custom Resolvers
965
-
966
- Register your own custom resolvers for project-specific needs:
967
-
968
- ```typescript
969
- import { registerDefaultResolver } from 'genomic';
970
-
971
- // Register a resolver for current directory name
972
- registerDefaultResolver('cwd.name', () => {
973
- return process.cwd().split('/').pop();
974
- });
975
-
976
- // Register a resolver for environment variable
977
- registerDefaultResolver('env.user', () => {
978
- return process.env.USER;
979
- });
980
-
981
- // Use in questions
982
- const questions = [
983
- {
984
- type: 'text',
985
- name: 'projectName',
986
- message: 'Project name?',
987
- defaultFrom: 'cwd.name',
988
- default: 'my-project'
989
- },
990
- {
991
- type: 'text',
992
- name: 'author',
993
- message: 'Author?',
994
- defaultFrom: 'env.user'
995
- }
996
- ];
997
- ```
998
-
999
- ### Instance-Specific Resolvers
1000
-
1001
- For isolated resolver registries, use a custom resolver registry per Prompter instance:
1002
-
1003
- ```typescript
1004
- import { DefaultResolverRegistry, Prompter } from 'genomic';
1005
-
1006
- const customRegistry = new DefaultResolverRegistry();
1007
-
1008
- // Register resolvers specific to this instance
1009
- customRegistry.register('app.name', () => 'my-app');
1010
- customRegistry.register('app.port', () => 3000);
1011
-
1012
- const prompter = new Prompter({
1013
- resolverRegistry: customRegistry // Use custom registry
1014
- });
1015
-
1016
- const questions = [
1017
- {
1018
- type: 'text',
1019
- name: 'appName',
1020
- defaultFrom: 'app.name'
1021
- },
1022
- {
1023
- type: 'number',
1024
- name: 'port',
1025
- defaultFrom: 'app.port'
1026
- }
1027
- ];
1028
-
1029
- const answers = await prompter.prompt({}, questions);
1030
- ```
1031
-
1032
- ### Resolver Examples
1033
-
1034
- #### System Information
1035
-
1036
- ```typescript
1037
- import os from 'os';
1038
- import { registerDefaultResolver } from 'genomic';
1039
-
1040
- registerDefaultResolver('system.hostname', () => os.hostname());
1041
- registerDefaultResolver('system.username', () => os.userInfo().username);
1042
-
1043
- const questions = [
1044
- {
1045
- type: 'text',
1046
- name: 'hostname',
1047
- message: 'Hostname?',
1048
- defaultFrom: 'system.hostname'
1049
- }
1050
- ];
1051
- ```
1052
-
1053
- #### Conditional Defaults
1054
-
1055
- ```typescript
1056
- registerDefaultResolver('app.port', () => {
1057
- return process.env.NODE_ENV === 'production' ? 80 : 3000;
1058
- });
1059
-
1060
- const questions = [
1061
- {
1062
- type: 'number',
1063
- name: 'port',
1064
- message: 'Port?',
1065
- defaultFrom: 'app.port'
1066
- }
1067
- ];
1068
- ```
1069
-
1070
- ### Error Handling
1071
-
1072
- Resolvers fail silently by default. If a resolver throws an error or returns `undefined`, genomic falls back to the static `default` value (if provided):
1073
-
1074
- ```typescript
1075
- {
1076
- type: 'text',
1077
- name: 'author',
1078
- defaultFrom: 'git.user.name', // May fail if git not configured
1079
- default: 'Anonymous', // Used if resolver fails
1080
- required: true
183
+ ]
1081
184
  }
1082
185
  ```
1083
186
 
1084
- For debugging, set `DEBUG=genomic` to see resolver errors:
1085
-
1086
- ```bash
1087
- DEBUG=genomic node your-cli.js
1088
- ```
1089
-
1090
- ### Real-World Use Case
1091
-
1092
- ```typescript
1093
- import { Prompter, registerDefaultResolver } from 'genomic';
1094
-
1095
- // Register a resolver for current directory name
1096
- registerDefaultResolver('cwd.name', () => {
1097
- return process.cwd().split('/').pop();
1098
- });
1099
-
1100
- const questions = [
1101
- {
1102
- type: 'text',
1103
- name: 'projectName',
1104
- message: 'Project name?',
1105
- defaultFrom: 'cwd.name',
1106
- required: true
1107
- },
1108
- {
1109
- type: 'text',
1110
- name: 'author',
1111
- message: 'Author?',
1112
- defaultFrom: 'git.user.name',
1113
- required: true
1114
- },
1115
- {
1116
- type: 'text',
1117
- name: 'email',
1118
- message: 'Email?',
1119
- defaultFrom: 'git.user.email',
1120
- required: true
1121
- },
1122
- {
1123
- type: 'text',
1124
- name: 'year',
1125
- message: 'Copyright year?',
1126
- defaultFrom: 'date.year'
1127
- }
1128
- ];
1129
-
1130
- const prompter = new Prompter();
1131
- const config = await prompter.prompt({}, questions);
1132
- ```
1133
-
1134
- With git configured, the prompts will show:
1135
-
1136
- ```bash
1137
- Project name? (my-project-dir)
1138
- Author? (John Doe)
1139
- Email? (john@example.com)
1140
- Copyright year? (2025)
1141
- ```
1142
-
1143
- All defaults automatically populated from git config, directory name, and current date!
187
+ Or `.boilerplate.js` for dynamic logic. Question names can use `____var____` or plain `VAR`; they'll be normalized automatically.
1144
188
 
1145
- ## CLI Helper
189
+ Note: `.boilerplate.json`, `.boilerplate.js`, and `.boilerplates.json` files are automatically excluded from the generated output.
1146
190
 
1147
- The `CLI` class provides integration with command-line argument parsing:
191
+ ### License Templates
1148
192
 
1149
- ```typescript
1150
- import { CLI, CommandHandler, CLIOptions } from 'genomic';
1151
-
1152
- const options: Partial<CLIOptions> = {
1153
- version: 'myapp@1.0.0',
1154
- minimistOpts: {
1155
- alias: {
1156
- v: 'version',
1157
- h: 'help'
1158
- },
1159
- boolean: ['help', 'version'],
1160
- string: ['name', 'output']
1161
- }
1162
- };
1163
-
1164
- const handler: CommandHandler = async (argv, prompter) => {
1165
- if (argv.help) {
1166
- console.log('Usage: myapp [options]');
1167
- process.exit(0);
1168
- }
1169
-
1170
- const answers = await prompter.prompt(argv, questions);
1171
- // Handle answers
1172
- };
193
+ `genomic` ships text templates in `licenses-templates/`. To add another license, drop a `.txt` file matching the desired key (e.g., `BSD-2-CLAUSE.txt`) with placeholders:
1173
194
 
1174
- const cli = new CLI(handler, options);
1175
- await cli.run();
1176
- ```
195
+ - `{{YEAR}}`, `{{AUTHOR}}`, `{{EMAIL_LINE}}`
1177
196
 
1178
- ---
197
+ No code changes are needed; the generator discovers templates at runtime and will warn if a `.questions` option doesn’t have a matching template.
1179
198
 
1180
- ## Development
199
+ ## API Overview
1181
200
 
1182
- ### Setup
201
+ ### TemplateScaffolder (Recommended)
1183
202
 
1184
- 1. Clone the repository:
1185
-
1186
- ```bash
1187
- git clone https://github.com/constructive-io/dev-utils.git
1188
- ```
1189
-
1190
- 2. Install dependencies:
1191
-
1192
- ```bash
1193
- cd dev-utils
1194
- pnpm install
1195
- pnpm build
1196
- ```
1197
-
1198
- 3. Test the package of interest:
1199
-
1200
- ```bash
1201
- cd packages/<packagename>
1202
- pnpm test:watch
1203
- ```
203
+ The high-level orchestrator that combines caching, cloning, and template processing:
1204
204
 
1205
- ## Credits
205
+ - `new TemplateScaffolder(config)`: Initialize with configuration:
206
+ - `toolName` (required): Name for cache directory (e.g., `'my-cli'` → `~/.my-cli/cache`)
207
+ - `defaultRepo`: Default template repository URL or `org/repo` shorthand
208
+ - `defaultBranch`: Default branch to clone
209
+ - `ttlMs`: Cache time-to-live in milliseconds
210
+ - `cacheBaseDir`: Override cache location (useful for tests)
211
+ - `scaffold(options)`: Scaffold a project from a template:
212
+ - `template`: Repository URL, local path, or `org/repo` shorthand (uses `defaultRepo` if not provided)
213
+ - `outputDir` (required): Output directory for generated project
214
+ - `fromPath`: Subdirectory within template to use
215
+ - `branch`: Branch to clone
216
+ - `answers`: Pre-populated answers to skip prompting
217
+ - `noTty`: Disable interactive prompts
218
+ - `prompter`: Reuse an existing Genomic instance
219
+ - `readBoilerplatesConfig(dir)`: Read `.boilerplates.json` from a template repo
220
+ - `readBoilerplateConfig(dir)`: Read `.boilerplate.json` from a template directory
221
+ - `getCacheManager()`, `getGitCloner()`, `getTemplatizer()`: Access underlying components
1206
222
 
1207
- Built for developers, with developers.
1208
- 👉 https://launchql.com | https://hyperweb.io
223
+ ### CacheManager
224
+ - `new CacheManager(config)`: Initialize with `toolName` and optional `ttl`.
225
+ - `get(key)`: Get path to cached repo if exists.
226
+ - `set(key, path)`: Register a path in the cache.
227
+ - `checkExpiration(key)`: Check if a cache entry is expired.
228
+ - `clear(key)`: Remove a specific cache entry.
229
+ - `clearAll()`: Clear all cached repos.
230
+ - When `ttl` is `undefined`, cache entries never expire. Provide a TTL (ms) only when you want automatic invalidation.
231
+ - Advanced: if you already own an appstash instance, pass `dirs` to reuse it instead of letting CacheManager create its own.
1209
232
 
1210
- ## Disclaimer
233
+ ### GitCloner
234
+ - `clone(url, dest, options)`: Clone a repo to a destination.
235
+ - `normalizeUrl(url)`: Normalize a git URL for consistency.
1211
236
 
1212
- AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
237
+ ### Templatizer
238
+ - `process(templateDir, outputDir, options)`: Run the full template generation pipeline (extract -> prompt -> replace).
1213
239
 
1214
- No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
240
+ See `packages/genomic-test` for a complete reference implementation.