genomic 5.0.3 → 5.2.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/esm/git/git-cloner.js +16 -1
- package/esm/scaffolder/index.js +1 -0
- package/esm/scaffolder/scan-boilerplates.js +187 -0
- package/esm/scaffolder/template-scaffolder.js +42 -2
- package/git/git-cloner.js +16 -1
- package/git/types.d.ts +2 -0
- package/package.json +3 -3
- package/scaffolder/index.d.ts +1 -0
- package/scaffolder/index.js +1 -0
- package/scaffolder/scan-boilerplates.d.ts +124 -0
- package/scaffolder/scan-boilerplates.js +227 -0
- package/scaffolder/template-scaffolder.d.ts +29 -2
- package/scaffolder/template-scaffolder.js +42 -2
package/esm/git/git-cloner.js
CHANGED
|
@@ -2,6 +2,7 @@ import { execSync } from 'child_process';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
+
import { createSpinner } from 'inquirerer';
|
|
5
6
|
export class GitCloner {
|
|
6
7
|
/**
|
|
7
8
|
* Clone a git repository to a destination
|
|
@@ -73,14 +74,28 @@ export class GitCloner {
|
|
|
73
74
|
const branch = options?.branch;
|
|
74
75
|
const depth = options?.depth ?? 1;
|
|
75
76
|
const singleBranch = options?.singleBranch ?? true;
|
|
77
|
+
const silent = options?.silent ?? true;
|
|
76
78
|
const branchArgs = branch ? ` --branch ${branch}` : '';
|
|
77
79
|
const singleBranchArgs = singleBranch ? ' --single-branch' : '';
|
|
78
80
|
const depthArgs = ` --depth ${depth}`;
|
|
79
81
|
const command = `git clone${branchArgs}${singleBranchArgs}${depthArgs} ${url} ${destination}`;
|
|
82
|
+
const spinner = silent ? createSpinner(`Cloning ${url}...`) : null;
|
|
80
83
|
try {
|
|
81
|
-
|
|
84
|
+
if (spinner) {
|
|
85
|
+
spinner.start();
|
|
86
|
+
}
|
|
87
|
+
execSync(command, {
|
|
88
|
+
stdio: silent ? 'pipe' : 'inherit',
|
|
89
|
+
encoding: 'utf-8'
|
|
90
|
+
});
|
|
91
|
+
if (spinner) {
|
|
92
|
+
spinner.succeed('Repository cloned');
|
|
93
|
+
}
|
|
82
94
|
}
|
|
83
95
|
catch (error) {
|
|
96
|
+
if (spinner) {
|
|
97
|
+
spinner.fail('Failed to clone repository');
|
|
98
|
+
}
|
|
84
99
|
// Clean up on failure
|
|
85
100
|
if (fs.existsSync(destination)) {
|
|
86
101
|
fs.rmSync(destination, { recursive: true, force: true });
|
package/esm/scaffolder/index.js
CHANGED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Directories to skip during recursive scanning.
|
|
5
|
+
* These are common directories that should never contain boilerplates.
|
|
6
|
+
*/
|
|
7
|
+
const SKIP_DIRECTORIES = new Set([
|
|
8
|
+
'.git',
|
|
9
|
+
'node_modules',
|
|
10
|
+
'.pnpm',
|
|
11
|
+
'dist',
|
|
12
|
+
'build',
|
|
13
|
+
'coverage',
|
|
14
|
+
'.next',
|
|
15
|
+
'.nuxt',
|
|
16
|
+
'.cache',
|
|
17
|
+
'__pycache__',
|
|
18
|
+
'.venv',
|
|
19
|
+
'venv',
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* Read the .boilerplate.json configuration from a directory.
|
|
23
|
+
*
|
|
24
|
+
* @param dirPath - The directory path to check
|
|
25
|
+
* @returns The boilerplate config or null if not found
|
|
26
|
+
*/
|
|
27
|
+
export function readBoilerplateConfig(dirPath) {
|
|
28
|
+
const configPath = path.join(dirPath, '.boilerplate.json');
|
|
29
|
+
if (fs.existsSync(configPath)) {
|
|
30
|
+
try {
|
|
31
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
32
|
+
return JSON.parse(content);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Recursively scan a directory for boilerplate templates.
|
|
42
|
+
*
|
|
43
|
+
* A boilerplate is any directory containing a `.boilerplate.json` file.
|
|
44
|
+
* This function recursively searches the entire directory tree (with sensible
|
|
45
|
+
* pruning of common non-template directories like node_modules, .git, etc.)
|
|
46
|
+
* and returns all discovered boilerplates with their relative paths.
|
|
47
|
+
*
|
|
48
|
+
* This is useful when:
|
|
49
|
+
* - The user specifies `--dir .` to bypass `.boilerplates.json`
|
|
50
|
+
* - You want to discover all available boilerplates regardless of nesting
|
|
51
|
+
* - You need to match a `fromPath` against available boilerplates
|
|
52
|
+
*
|
|
53
|
+
* @param baseDir - The root directory to start scanning from
|
|
54
|
+
* @param options - Scanning options
|
|
55
|
+
* @returns Array of discovered boilerplates with relative paths
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // Given structure:
|
|
60
|
+
* // repo/
|
|
61
|
+
* // default/
|
|
62
|
+
* // module/.boilerplate.json
|
|
63
|
+
* // workspace/.boilerplate.json
|
|
64
|
+
* // scripts/ (no .boilerplate.json)
|
|
65
|
+
*
|
|
66
|
+
* const boilerplates = scanBoilerplatesRecursive('/path/to/repo');
|
|
67
|
+
* // Returns:
|
|
68
|
+
* // [
|
|
69
|
+
* // { relativePath: 'default/module', absolutePath: '...', config: {...} },
|
|
70
|
+
* // { relativePath: 'default/workspace', absolutePath: '...', config: {...} }
|
|
71
|
+
* // ]
|
|
72
|
+
* // Note: 'scripts' is not included because it has no .boilerplate.json
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function scanBoilerplatesRecursive(baseDir, options = {}) {
|
|
76
|
+
const { maxDepth = 10, skipDirectories = [] } = options;
|
|
77
|
+
const boilerplates = [];
|
|
78
|
+
const skipSet = new Set([...SKIP_DIRECTORIES, ...skipDirectories]);
|
|
79
|
+
function scan(currentDir, relativePath, depth) {
|
|
80
|
+
if (depth > maxDepth) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (!fs.existsSync(currentDir)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let entries;
|
|
87
|
+
try {
|
|
88
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
if (!entry.isDirectory()) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (skipSet.has(entry.name)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const entryPath = path.join(currentDir, entry.name);
|
|
101
|
+
const entryRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
102
|
+
const config = readBoilerplateConfig(entryPath);
|
|
103
|
+
if (config) {
|
|
104
|
+
boilerplates.push({
|
|
105
|
+
relativePath: entryRelativePath,
|
|
106
|
+
absolutePath: entryPath,
|
|
107
|
+
config,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// Continue scanning subdirectories even if this directory is a boilerplate
|
|
111
|
+
// (in case there are nested boilerplates, though uncommon)
|
|
112
|
+
scan(entryPath, entryRelativePath, depth + 1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
scan(baseDir, '', 0);
|
|
116
|
+
// Sort by relative path for consistent ordering
|
|
117
|
+
boilerplates.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
118
|
+
return boilerplates;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Find a boilerplate by matching against a fromPath.
|
|
122
|
+
*
|
|
123
|
+
* This function attempts to match a user-provided `fromPath` against
|
|
124
|
+
* discovered boilerplates. It supports:
|
|
125
|
+
* 1. Exact match: `fromPath` matches a relative path exactly
|
|
126
|
+
* 2. Basename match: `fromPath` matches the last segment of a relative path
|
|
127
|
+
* (only if unambiguous - i.e., exactly one match)
|
|
128
|
+
*
|
|
129
|
+
* @param boilerplates - Array of scanned boilerplates
|
|
130
|
+
* @param fromPath - The path to match against
|
|
131
|
+
* @returns The matching boilerplate, or null if no match or ambiguous
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* const boilerplates = scanBoilerplatesRecursive('/path/to/repo');
|
|
136
|
+
*
|
|
137
|
+
* // Exact match
|
|
138
|
+
* findBoilerplateByPath(boilerplates, 'default/module');
|
|
139
|
+
* // Returns the 'default/module' boilerplate
|
|
140
|
+
*
|
|
141
|
+
* // Basename match (unambiguous)
|
|
142
|
+
* findBoilerplateByPath(boilerplates, 'module');
|
|
143
|
+
* // Returns the 'default/module' boilerplate if it's the only one ending in 'module'
|
|
144
|
+
*
|
|
145
|
+
* // Ambiguous basename match
|
|
146
|
+
* // If both 'default/module' and 'supabase/module' exist:
|
|
147
|
+
* findBoilerplateByPath(boilerplates, 'module');
|
|
148
|
+
* // Returns null (ambiguous)
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export function findBoilerplateByPath(boilerplates, fromPath) {
|
|
152
|
+
// Normalize the fromPath (remove leading/trailing slashes)
|
|
153
|
+
const normalizedPath = fromPath.replace(/^\/+|\/+$/g, '');
|
|
154
|
+
// Try exact match first
|
|
155
|
+
const exactMatch = boilerplates.find((bp) => bp.relativePath === normalizedPath);
|
|
156
|
+
if (exactMatch) {
|
|
157
|
+
return exactMatch;
|
|
158
|
+
}
|
|
159
|
+
// Try basename match (last segment of path)
|
|
160
|
+
const basename = path.basename(normalizedPath);
|
|
161
|
+
const basenameMatches = boilerplates.filter((bp) => path.basename(bp.relativePath) === basename);
|
|
162
|
+
// Only return if unambiguous (exactly one match)
|
|
163
|
+
if (basenameMatches.length === 1) {
|
|
164
|
+
return basenameMatches[0];
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Find a boilerplate by type within a scanned list.
|
|
170
|
+
*
|
|
171
|
+
* @param boilerplates - Array of scanned boilerplates
|
|
172
|
+
* @param type - The type to find (e.g., 'workspace', 'module')
|
|
173
|
+
* @returns The matching boilerplate or undefined
|
|
174
|
+
*/
|
|
175
|
+
export function findBoilerplateByType(boilerplates, type) {
|
|
176
|
+
return boilerplates.find((bp) => bp.config.type === type);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get all boilerplates of a specific type.
|
|
180
|
+
*
|
|
181
|
+
* @param boilerplates - Array of scanned boilerplates
|
|
182
|
+
* @param type - The type to filter by
|
|
183
|
+
* @returns Array of matching boilerplates
|
|
184
|
+
*/
|
|
185
|
+
export function filterBoilerplatesByType(boilerplates, type) {
|
|
186
|
+
return boilerplates.filter((bp) => bp.config.type === type);
|
|
187
|
+
}
|
|
@@ -3,6 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
import { CacheManager } from '../cache/cache-manager';
|
|
4
4
|
import { GitCloner } from '../git/git-cloner';
|
|
5
5
|
import { Templatizer } from '../template/templatizer';
|
|
6
|
+
import { scanBoilerplatesRecursive, findBoilerplateByPath, } from './scan-boilerplates';
|
|
6
7
|
/**
|
|
7
8
|
* High-level orchestrator for template scaffolding operations.
|
|
8
9
|
* Combines CacheManager, GitCloner, and Templatizer into a single, easy-to-use API.
|
|
@@ -137,6 +138,33 @@ export class TemplateScaffolder {
|
|
|
137
138
|
getTemplatizer() {
|
|
138
139
|
return this.templatizer;
|
|
139
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Scan a template directory recursively for all boilerplates.
|
|
143
|
+
*
|
|
144
|
+
* A boilerplate is any directory containing a `.boilerplate.json` file.
|
|
145
|
+
* This method recursively searches the entire directory tree and returns
|
|
146
|
+
* all discovered boilerplates with their relative paths.
|
|
147
|
+
*
|
|
148
|
+
* This is useful when:
|
|
149
|
+
* - The user specifies `--dir .` to bypass `.boilerplates.json`
|
|
150
|
+
* - You want to discover all available boilerplates regardless of nesting
|
|
151
|
+
* - You need to present a list of available boilerplates to the user
|
|
152
|
+
*
|
|
153
|
+
* @param templateDir - The root directory to scan
|
|
154
|
+
* @param options - Scanning options (maxDepth, skipDirectories)
|
|
155
|
+
* @returns Array of discovered boilerplates with relative paths
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* const scaffolder = new TemplateScaffolder({ toolName: 'my-cli' });
|
|
160
|
+
* const inspection = scaffolder.inspect({ template: 'org/repo' });
|
|
161
|
+
* const boilerplates = scaffolder.scanBoilerplates(inspection.templateDir);
|
|
162
|
+
* // Returns: [{ relativePath: 'default/module', ... }, { relativePath: 'default/workspace', ... }]
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
scanBoilerplates(templateDir, options) {
|
|
166
|
+
return scanBoilerplatesRecursive(templateDir, options);
|
|
167
|
+
}
|
|
140
168
|
inspectLocal(templateDir, fromPath, useBoilerplatesConfig = true) {
|
|
141
169
|
const { fromPath: resolvedFromPath, resolvedTemplatePath } = this.resolveFromPath(templateDir, fromPath, useBoilerplatesConfig);
|
|
142
170
|
const config = this.readBoilerplateConfig(resolvedTemplatePath);
|
|
@@ -246,12 +274,13 @@ export class TemplateScaffolder {
|
|
|
246
274
|
};
|
|
247
275
|
}
|
|
248
276
|
/**
|
|
249
|
-
* Resolve the fromPath using .boilerplates.json convention.
|
|
277
|
+
* Resolve the fromPath using .boilerplates.json convention and recursive scanning.
|
|
250
278
|
*
|
|
251
279
|
* Resolution order:
|
|
252
280
|
* 1. If explicit fromPath is provided and exists, use it directly
|
|
253
281
|
* 2. If useBoilerplatesConfig is true and .boilerplates.json exists with a dir field, prepend it to fromPath
|
|
254
|
-
* 3.
|
|
282
|
+
* 3. Recursively scan for boilerplates and try to match fromPath (exact match, then basename match if unambiguous)
|
|
283
|
+
* 4. Return the fromPath as-is (will likely fail later if path doesn't exist)
|
|
255
284
|
*
|
|
256
285
|
* @param templateDir - The template repository root directory
|
|
257
286
|
* @param fromPath - The subdirectory path to resolve
|
|
@@ -286,6 +315,17 @@ export class TemplateScaffolder {
|
|
|
286
315
|
}
|
|
287
316
|
}
|
|
288
317
|
}
|
|
318
|
+
// Try recursive scan to find a matching boilerplate
|
|
319
|
+
// This handles cases like `--dir .` where the user wants to match against
|
|
320
|
+
// discovered boilerplates (e.g., "module" matching "default/module")
|
|
321
|
+
const boilerplates = scanBoilerplatesRecursive(templateDir);
|
|
322
|
+
const match = findBoilerplateByPath(boilerplates, fromPath);
|
|
323
|
+
if (match) {
|
|
324
|
+
return {
|
|
325
|
+
fromPath: match.relativePath,
|
|
326
|
+
resolvedTemplatePath: match.absolutePath,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
289
329
|
return {
|
|
290
330
|
fromPath,
|
|
291
331
|
resolvedTemplatePath: path.join(templateDir, fromPath),
|
package/git/git-cloner.js
CHANGED
|
@@ -38,6 +38,7 @@ const child_process_1 = require("child_process");
|
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const os = __importStar(require("os"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
|
+
const inquirerer_1 = require("inquirerer");
|
|
41
42
|
class GitCloner {
|
|
42
43
|
/**
|
|
43
44
|
* Clone a git repository to a destination
|
|
@@ -109,14 +110,28 @@ class GitCloner {
|
|
|
109
110
|
const branch = options?.branch;
|
|
110
111
|
const depth = options?.depth ?? 1;
|
|
111
112
|
const singleBranch = options?.singleBranch ?? true;
|
|
113
|
+
const silent = options?.silent ?? true;
|
|
112
114
|
const branchArgs = branch ? ` --branch ${branch}` : '';
|
|
113
115
|
const singleBranchArgs = singleBranch ? ' --single-branch' : '';
|
|
114
116
|
const depthArgs = ` --depth ${depth}`;
|
|
115
117
|
const command = `git clone${branchArgs}${singleBranchArgs}${depthArgs} ${url} ${destination}`;
|
|
118
|
+
const spinner = silent ? (0, inquirerer_1.createSpinner)(`Cloning ${url}...`) : null;
|
|
116
119
|
try {
|
|
117
|
-
(
|
|
120
|
+
if (spinner) {
|
|
121
|
+
spinner.start();
|
|
122
|
+
}
|
|
123
|
+
(0, child_process_1.execSync)(command, {
|
|
124
|
+
stdio: silent ? 'pipe' : 'inherit',
|
|
125
|
+
encoding: 'utf-8'
|
|
126
|
+
});
|
|
127
|
+
if (spinner) {
|
|
128
|
+
spinner.succeed('Repository cloned');
|
|
129
|
+
}
|
|
118
130
|
}
|
|
119
131
|
catch (error) {
|
|
132
|
+
if (spinner) {
|
|
133
|
+
spinner.fail('Failed to clone repository');
|
|
134
|
+
}
|
|
120
135
|
// Clean up on failure
|
|
121
136
|
if (fs.existsSync(destination)) {
|
|
122
137
|
fs.rmSync(destination, { recursive: true, force: true });
|
package/git/types.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export interface GitCloneOptions {
|
|
|
2
2
|
branch?: string;
|
|
3
3
|
depth?: number;
|
|
4
4
|
singleBranch?: boolean;
|
|
5
|
+
/** If true (default), show spinner and silence git output. If false, show raw git output. */
|
|
6
|
+
silent?: boolean;
|
|
5
7
|
}
|
|
6
8
|
export interface GitCloneResult {
|
|
7
9
|
destination: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genomic",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "Clone and customize template repositories with variable replacement",
|
|
6
6
|
"main": "index.js",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"appstash": "0.2.7",
|
|
32
|
-
"inquirerer": "4.
|
|
32
|
+
"inquirerer": "4.2.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"copyfiles": "^2.4.1",
|
|
36
36
|
"makage": "0.1.8"
|
|
37
37
|
},
|
|
38
38
|
"keywords": [],
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "89afff9da8425e38d1c8b75b2021833581a90307"
|
|
40
40
|
}
|
package/scaffolder/index.d.ts
CHANGED
package/scaffolder/index.js
CHANGED
|
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./template-scaffolder"), exports);
|
|
18
18
|
__exportStar(require("./types"), exports);
|
|
19
|
+
__exportStar(require("./scan-boilerplates"), exports);
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { BoilerplateConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Result of scanning for boilerplates.
|
|
4
|
+
*/
|
|
5
|
+
export interface ScannedBoilerplate {
|
|
6
|
+
/**
|
|
7
|
+
* The relative path from the scan root to the boilerplate directory.
|
|
8
|
+
* For example: "default/module", "default/workspace"
|
|
9
|
+
*/
|
|
10
|
+
relativePath: string;
|
|
11
|
+
/**
|
|
12
|
+
* The absolute path to the boilerplate directory.
|
|
13
|
+
*/
|
|
14
|
+
absolutePath: string;
|
|
15
|
+
/**
|
|
16
|
+
* The boilerplate configuration from .boilerplate.json
|
|
17
|
+
*/
|
|
18
|
+
config: BoilerplateConfig;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Options for scanning boilerplates.
|
|
22
|
+
*/
|
|
23
|
+
export interface ScanBoilerplatesOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Maximum depth to recurse into directories.
|
|
26
|
+
* Default: 10 (should be enough for any reasonable structure)
|
|
27
|
+
*/
|
|
28
|
+
maxDepth?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Additional directory names to skip during scanning.
|
|
31
|
+
*/
|
|
32
|
+
skipDirectories?: string[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read the .boilerplate.json configuration from a directory.
|
|
36
|
+
*
|
|
37
|
+
* @param dirPath - The directory path to check
|
|
38
|
+
* @returns The boilerplate config or null if not found
|
|
39
|
+
*/
|
|
40
|
+
export declare function readBoilerplateConfig(dirPath: string): BoilerplateConfig | null;
|
|
41
|
+
/**
|
|
42
|
+
* Recursively scan a directory for boilerplate templates.
|
|
43
|
+
*
|
|
44
|
+
* A boilerplate is any directory containing a `.boilerplate.json` file.
|
|
45
|
+
* This function recursively searches the entire directory tree (with sensible
|
|
46
|
+
* pruning of common non-template directories like node_modules, .git, etc.)
|
|
47
|
+
* and returns all discovered boilerplates with their relative paths.
|
|
48
|
+
*
|
|
49
|
+
* This is useful when:
|
|
50
|
+
* - The user specifies `--dir .` to bypass `.boilerplates.json`
|
|
51
|
+
* - You want to discover all available boilerplates regardless of nesting
|
|
52
|
+
* - You need to match a `fromPath` against available boilerplates
|
|
53
|
+
*
|
|
54
|
+
* @param baseDir - The root directory to start scanning from
|
|
55
|
+
* @param options - Scanning options
|
|
56
|
+
* @returns Array of discovered boilerplates with relative paths
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* // Given structure:
|
|
61
|
+
* // repo/
|
|
62
|
+
* // default/
|
|
63
|
+
* // module/.boilerplate.json
|
|
64
|
+
* // workspace/.boilerplate.json
|
|
65
|
+
* // scripts/ (no .boilerplate.json)
|
|
66
|
+
*
|
|
67
|
+
* const boilerplates = scanBoilerplatesRecursive('/path/to/repo');
|
|
68
|
+
* // Returns:
|
|
69
|
+
* // [
|
|
70
|
+
* // { relativePath: 'default/module', absolutePath: '...', config: {...} },
|
|
71
|
+
* // { relativePath: 'default/workspace', absolutePath: '...', config: {...} }
|
|
72
|
+
* // ]
|
|
73
|
+
* // Note: 'scripts' is not included because it has no .boilerplate.json
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function scanBoilerplatesRecursive(baseDir: string, options?: ScanBoilerplatesOptions): ScannedBoilerplate[];
|
|
77
|
+
/**
|
|
78
|
+
* Find a boilerplate by matching against a fromPath.
|
|
79
|
+
*
|
|
80
|
+
* This function attempts to match a user-provided `fromPath` against
|
|
81
|
+
* discovered boilerplates. It supports:
|
|
82
|
+
* 1. Exact match: `fromPath` matches a relative path exactly
|
|
83
|
+
* 2. Basename match: `fromPath` matches the last segment of a relative path
|
|
84
|
+
* (only if unambiguous - i.e., exactly one match)
|
|
85
|
+
*
|
|
86
|
+
* @param boilerplates - Array of scanned boilerplates
|
|
87
|
+
* @param fromPath - The path to match against
|
|
88
|
+
* @returns The matching boilerplate, or null if no match or ambiguous
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const boilerplates = scanBoilerplatesRecursive('/path/to/repo');
|
|
93
|
+
*
|
|
94
|
+
* // Exact match
|
|
95
|
+
* findBoilerplateByPath(boilerplates, 'default/module');
|
|
96
|
+
* // Returns the 'default/module' boilerplate
|
|
97
|
+
*
|
|
98
|
+
* // Basename match (unambiguous)
|
|
99
|
+
* findBoilerplateByPath(boilerplates, 'module');
|
|
100
|
+
* // Returns the 'default/module' boilerplate if it's the only one ending in 'module'
|
|
101
|
+
*
|
|
102
|
+
* // Ambiguous basename match
|
|
103
|
+
* // If both 'default/module' and 'supabase/module' exist:
|
|
104
|
+
* findBoilerplateByPath(boilerplates, 'module');
|
|
105
|
+
* // Returns null (ambiguous)
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export declare function findBoilerplateByPath(boilerplates: ScannedBoilerplate[], fromPath: string): ScannedBoilerplate | null;
|
|
109
|
+
/**
|
|
110
|
+
* Find a boilerplate by type within a scanned list.
|
|
111
|
+
*
|
|
112
|
+
* @param boilerplates - Array of scanned boilerplates
|
|
113
|
+
* @param type - The type to find (e.g., 'workspace', 'module')
|
|
114
|
+
* @returns The matching boilerplate or undefined
|
|
115
|
+
*/
|
|
116
|
+
export declare function findBoilerplateByType(boilerplates: ScannedBoilerplate[], type: string): ScannedBoilerplate | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Get all boilerplates of a specific type.
|
|
119
|
+
*
|
|
120
|
+
* @param boilerplates - Array of scanned boilerplates
|
|
121
|
+
* @param type - The type to filter by
|
|
122
|
+
* @returns Array of matching boilerplates
|
|
123
|
+
*/
|
|
124
|
+
export declare function filterBoilerplatesByType(boilerplates: ScannedBoilerplate[], type: string): ScannedBoilerplate[];
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readBoilerplateConfig = readBoilerplateConfig;
|
|
37
|
+
exports.scanBoilerplatesRecursive = scanBoilerplatesRecursive;
|
|
38
|
+
exports.findBoilerplateByPath = findBoilerplateByPath;
|
|
39
|
+
exports.findBoilerplateByType = findBoilerplateByType;
|
|
40
|
+
exports.filterBoilerplatesByType = filterBoilerplatesByType;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
/**
|
|
44
|
+
* Directories to skip during recursive scanning.
|
|
45
|
+
* These are common directories that should never contain boilerplates.
|
|
46
|
+
*/
|
|
47
|
+
const SKIP_DIRECTORIES = new Set([
|
|
48
|
+
'.git',
|
|
49
|
+
'node_modules',
|
|
50
|
+
'.pnpm',
|
|
51
|
+
'dist',
|
|
52
|
+
'build',
|
|
53
|
+
'coverage',
|
|
54
|
+
'.next',
|
|
55
|
+
'.nuxt',
|
|
56
|
+
'.cache',
|
|
57
|
+
'__pycache__',
|
|
58
|
+
'.venv',
|
|
59
|
+
'venv',
|
|
60
|
+
]);
|
|
61
|
+
/**
|
|
62
|
+
* Read the .boilerplate.json configuration from a directory.
|
|
63
|
+
*
|
|
64
|
+
* @param dirPath - The directory path to check
|
|
65
|
+
* @returns The boilerplate config or null if not found
|
|
66
|
+
*/
|
|
67
|
+
function readBoilerplateConfig(dirPath) {
|
|
68
|
+
const configPath = path.join(dirPath, '.boilerplate.json');
|
|
69
|
+
if (fs.existsSync(configPath)) {
|
|
70
|
+
try {
|
|
71
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
72
|
+
return JSON.parse(content);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Recursively scan a directory for boilerplate templates.
|
|
82
|
+
*
|
|
83
|
+
* A boilerplate is any directory containing a `.boilerplate.json` file.
|
|
84
|
+
* This function recursively searches the entire directory tree (with sensible
|
|
85
|
+
* pruning of common non-template directories like node_modules, .git, etc.)
|
|
86
|
+
* and returns all discovered boilerplates with their relative paths.
|
|
87
|
+
*
|
|
88
|
+
* This is useful when:
|
|
89
|
+
* - The user specifies `--dir .` to bypass `.boilerplates.json`
|
|
90
|
+
* - You want to discover all available boilerplates regardless of nesting
|
|
91
|
+
* - You need to match a `fromPath` against available boilerplates
|
|
92
|
+
*
|
|
93
|
+
* @param baseDir - The root directory to start scanning from
|
|
94
|
+
* @param options - Scanning options
|
|
95
|
+
* @returns Array of discovered boilerplates with relative paths
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* // Given structure:
|
|
100
|
+
* // repo/
|
|
101
|
+
* // default/
|
|
102
|
+
* // module/.boilerplate.json
|
|
103
|
+
* // workspace/.boilerplate.json
|
|
104
|
+
* // scripts/ (no .boilerplate.json)
|
|
105
|
+
*
|
|
106
|
+
* const boilerplates = scanBoilerplatesRecursive('/path/to/repo');
|
|
107
|
+
* // Returns:
|
|
108
|
+
* // [
|
|
109
|
+
* // { relativePath: 'default/module', absolutePath: '...', config: {...} },
|
|
110
|
+
* // { relativePath: 'default/workspace', absolutePath: '...', config: {...} }
|
|
111
|
+
* // ]
|
|
112
|
+
* // Note: 'scripts' is not included because it has no .boilerplate.json
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
function scanBoilerplatesRecursive(baseDir, options = {}) {
|
|
116
|
+
const { maxDepth = 10, skipDirectories = [] } = options;
|
|
117
|
+
const boilerplates = [];
|
|
118
|
+
const skipSet = new Set([...SKIP_DIRECTORIES, ...skipDirectories]);
|
|
119
|
+
function scan(currentDir, relativePath, depth) {
|
|
120
|
+
if (depth > maxDepth) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (!fs.existsSync(currentDir)) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
let entries;
|
|
127
|
+
try {
|
|
128
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (!entry.isDirectory()) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (skipSet.has(entry.name)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const entryPath = path.join(currentDir, entry.name);
|
|
141
|
+
const entryRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
142
|
+
const config = readBoilerplateConfig(entryPath);
|
|
143
|
+
if (config) {
|
|
144
|
+
boilerplates.push({
|
|
145
|
+
relativePath: entryRelativePath,
|
|
146
|
+
absolutePath: entryPath,
|
|
147
|
+
config,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// Continue scanning subdirectories even if this directory is a boilerplate
|
|
151
|
+
// (in case there are nested boilerplates, though uncommon)
|
|
152
|
+
scan(entryPath, entryRelativePath, depth + 1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
scan(baseDir, '', 0);
|
|
156
|
+
// Sort by relative path for consistent ordering
|
|
157
|
+
boilerplates.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
158
|
+
return boilerplates;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Find a boilerplate by matching against a fromPath.
|
|
162
|
+
*
|
|
163
|
+
* This function attempts to match a user-provided `fromPath` against
|
|
164
|
+
* discovered boilerplates. It supports:
|
|
165
|
+
* 1. Exact match: `fromPath` matches a relative path exactly
|
|
166
|
+
* 2. Basename match: `fromPath` matches the last segment of a relative path
|
|
167
|
+
* (only if unambiguous - i.e., exactly one match)
|
|
168
|
+
*
|
|
169
|
+
* @param boilerplates - Array of scanned boilerplates
|
|
170
|
+
* @param fromPath - The path to match against
|
|
171
|
+
* @returns The matching boilerplate, or null if no match or ambiguous
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const boilerplates = scanBoilerplatesRecursive('/path/to/repo');
|
|
176
|
+
*
|
|
177
|
+
* // Exact match
|
|
178
|
+
* findBoilerplateByPath(boilerplates, 'default/module');
|
|
179
|
+
* // Returns the 'default/module' boilerplate
|
|
180
|
+
*
|
|
181
|
+
* // Basename match (unambiguous)
|
|
182
|
+
* findBoilerplateByPath(boilerplates, 'module');
|
|
183
|
+
* // Returns the 'default/module' boilerplate if it's the only one ending in 'module'
|
|
184
|
+
*
|
|
185
|
+
* // Ambiguous basename match
|
|
186
|
+
* // If both 'default/module' and 'supabase/module' exist:
|
|
187
|
+
* findBoilerplateByPath(boilerplates, 'module');
|
|
188
|
+
* // Returns null (ambiguous)
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
function findBoilerplateByPath(boilerplates, fromPath) {
|
|
192
|
+
// Normalize the fromPath (remove leading/trailing slashes)
|
|
193
|
+
const normalizedPath = fromPath.replace(/^\/+|\/+$/g, '');
|
|
194
|
+
// Try exact match first
|
|
195
|
+
const exactMatch = boilerplates.find((bp) => bp.relativePath === normalizedPath);
|
|
196
|
+
if (exactMatch) {
|
|
197
|
+
return exactMatch;
|
|
198
|
+
}
|
|
199
|
+
// Try basename match (last segment of path)
|
|
200
|
+
const basename = path.basename(normalizedPath);
|
|
201
|
+
const basenameMatches = boilerplates.filter((bp) => path.basename(bp.relativePath) === basename);
|
|
202
|
+
// Only return if unambiguous (exactly one match)
|
|
203
|
+
if (basenameMatches.length === 1) {
|
|
204
|
+
return basenameMatches[0];
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Find a boilerplate by type within a scanned list.
|
|
210
|
+
*
|
|
211
|
+
* @param boilerplates - Array of scanned boilerplates
|
|
212
|
+
* @param type - The type to find (e.g., 'workspace', 'module')
|
|
213
|
+
* @returns The matching boilerplate or undefined
|
|
214
|
+
*/
|
|
215
|
+
function findBoilerplateByType(boilerplates, type) {
|
|
216
|
+
return boilerplates.find((bp) => bp.config.type === type);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get all boilerplates of a specific type.
|
|
220
|
+
*
|
|
221
|
+
* @param boilerplates - Array of scanned boilerplates
|
|
222
|
+
* @param type - The type to filter by
|
|
223
|
+
* @returns Array of matching boilerplates
|
|
224
|
+
*/
|
|
225
|
+
function filterBoilerplatesByType(boilerplates, type) {
|
|
226
|
+
return boilerplates.filter((bp) => bp.config.type === type);
|
|
227
|
+
}
|
|
@@ -2,6 +2,7 @@ import { CacheManager } from '../cache/cache-manager';
|
|
|
2
2
|
import { GitCloner } from '../git/git-cloner';
|
|
3
3
|
import { Templatizer } from '../template/templatizer';
|
|
4
4
|
import { TemplateScaffolderConfig, ScaffoldOptions, ScaffoldResult, BoilerplatesConfig, BoilerplateConfig, InspectOptions, InspectResult } from './types';
|
|
5
|
+
import { ScannedBoilerplate, ScanBoilerplatesOptions } from './scan-boilerplates';
|
|
5
6
|
/**
|
|
6
7
|
* High-level orchestrator for template scaffolding operations.
|
|
7
8
|
* Combines CacheManager, GitCloner, and Templatizer into a single, easy-to-use API.
|
|
@@ -69,17 +70,43 @@ export declare class TemplateScaffolder {
|
|
|
69
70
|
* Get the underlying Templatizer instance for advanced template operations.
|
|
70
71
|
*/
|
|
71
72
|
getTemplatizer(): Templatizer;
|
|
73
|
+
/**
|
|
74
|
+
* Scan a template directory recursively for all boilerplates.
|
|
75
|
+
*
|
|
76
|
+
* A boilerplate is any directory containing a `.boilerplate.json` file.
|
|
77
|
+
* This method recursively searches the entire directory tree and returns
|
|
78
|
+
* all discovered boilerplates with their relative paths.
|
|
79
|
+
*
|
|
80
|
+
* This is useful when:
|
|
81
|
+
* - The user specifies `--dir .` to bypass `.boilerplates.json`
|
|
82
|
+
* - You want to discover all available boilerplates regardless of nesting
|
|
83
|
+
* - You need to present a list of available boilerplates to the user
|
|
84
|
+
*
|
|
85
|
+
* @param templateDir - The root directory to scan
|
|
86
|
+
* @param options - Scanning options (maxDepth, skipDirectories)
|
|
87
|
+
* @returns Array of discovered boilerplates with relative paths
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const scaffolder = new TemplateScaffolder({ toolName: 'my-cli' });
|
|
92
|
+
* const inspection = scaffolder.inspect({ template: 'org/repo' });
|
|
93
|
+
* const boilerplates = scaffolder.scanBoilerplates(inspection.templateDir);
|
|
94
|
+
* // Returns: [{ relativePath: 'default/module', ... }, { relativePath: 'default/workspace', ... }]
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
scanBoilerplates(templateDir: string, options?: ScanBoilerplatesOptions): ScannedBoilerplate[];
|
|
72
98
|
private inspectLocal;
|
|
73
99
|
private inspectRemote;
|
|
74
100
|
private scaffoldFromLocal;
|
|
75
101
|
private scaffoldFromRemote;
|
|
76
102
|
/**
|
|
77
|
-
* Resolve the fromPath using .boilerplates.json convention.
|
|
103
|
+
* Resolve the fromPath using .boilerplates.json convention and recursive scanning.
|
|
78
104
|
*
|
|
79
105
|
* Resolution order:
|
|
80
106
|
* 1. If explicit fromPath is provided and exists, use it directly
|
|
81
107
|
* 2. If useBoilerplatesConfig is true and .boilerplates.json exists with a dir field, prepend it to fromPath
|
|
82
|
-
* 3.
|
|
108
|
+
* 3. Recursively scan for boilerplates and try to match fromPath (exact match, then basename match if unambiguous)
|
|
109
|
+
* 4. Return the fromPath as-is (will likely fail later if path doesn't exist)
|
|
83
110
|
*
|
|
84
111
|
* @param templateDir - The template repository root directory
|
|
85
112
|
* @param fromPath - The subdirectory path to resolve
|
|
@@ -39,6 +39,7 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const cache_manager_1 = require("../cache/cache-manager");
|
|
40
40
|
const git_cloner_1 = require("../git/git-cloner");
|
|
41
41
|
const templatizer_1 = require("../template/templatizer");
|
|
42
|
+
const scan_boilerplates_1 = require("./scan-boilerplates");
|
|
42
43
|
/**
|
|
43
44
|
* High-level orchestrator for template scaffolding operations.
|
|
44
45
|
* Combines CacheManager, GitCloner, and Templatizer into a single, easy-to-use API.
|
|
@@ -173,6 +174,33 @@ class TemplateScaffolder {
|
|
|
173
174
|
getTemplatizer() {
|
|
174
175
|
return this.templatizer;
|
|
175
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Scan a template directory recursively for all boilerplates.
|
|
179
|
+
*
|
|
180
|
+
* A boilerplate is any directory containing a `.boilerplate.json` file.
|
|
181
|
+
* This method recursively searches the entire directory tree and returns
|
|
182
|
+
* all discovered boilerplates with their relative paths.
|
|
183
|
+
*
|
|
184
|
+
* This is useful when:
|
|
185
|
+
* - The user specifies `--dir .` to bypass `.boilerplates.json`
|
|
186
|
+
* - You want to discover all available boilerplates regardless of nesting
|
|
187
|
+
* - You need to present a list of available boilerplates to the user
|
|
188
|
+
*
|
|
189
|
+
* @param templateDir - The root directory to scan
|
|
190
|
+
* @param options - Scanning options (maxDepth, skipDirectories)
|
|
191
|
+
* @returns Array of discovered boilerplates with relative paths
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```typescript
|
|
195
|
+
* const scaffolder = new TemplateScaffolder({ toolName: 'my-cli' });
|
|
196
|
+
* const inspection = scaffolder.inspect({ template: 'org/repo' });
|
|
197
|
+
* const boilerplates = scaffolder.scanBoilerplates(inspection.templateDir);
|
|
198
|
+
* // Returns: [{ relativePath: 'default/module', ... }, { relativePath: 'default/workspace', ... }]
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
scanBoilerplates(templateDir, options) {
|
|
202
|
+
return (0, scan_boilerplates_1.scanBoilerplatesRecursive)(templateDir, options);
|
|
203
|
+
}
|
|
176
204
|
inspectLocal(templateDir, fromPath, useBoilerplatesConfig = true) {
|
|
177
205
|
const { fromPath: resolvedFromPath, resolvedTemplatePath } = this.resolveFromPath(templateDir, fromPath, useBoilerplatesConfig);
|
|
178
206
|
const config = this.readBoilerplateConfig(resolvedTemplatePath);
|
|
@@ -282,12 +310,13 @@ class TemplateScaffolder {
|
|
|
282
310
|
};
|
|
283
311
|
}
|
|
284
312
|
/**
|
|
285
|
-
* Resolve the fromPath using .boilerplates.json convention.
|
|
313
|
+
* Resolve the fromPath using .boilerplates.json convention and recursive scanning.
|
|
286
314
|
*
|
|
287
315
|
* Resolution order:
|
|
288
316
|
* 1. If explicit fromPath is provided and exists, use it directly
|
|
289
317
|
* 2. If useBoilerplatesConfig is true and .boilerplates.json exists with a dir field, prepend it to fromPath
|
|
290
|
-
* 3.
|
|
318
|
+
* 3. Recursively scan for boilerplates and try to match fromPath (exact match, then basename match if unambiguous)
|
|
319
|
+
* 4. Return the fromPath as-is (will likely fail later if path doesn't exist)
|
|
291
320
|
*
|
|
292
321
|
* @param templateDir - The template repository root directory
|
|
293
322
|
* @param fromPath - The subdirectory path to resolve
|
|
@@ -322,6 +351,17 @@ class TemplateScaffolder {
|
|
|
322
351
|
}
|
|
323
352
|
}
|
|
324
353
|
}
|
|
354
|
+
// Try recursive scan to find a matching boilerplate
|
|
355
|
+
// This handles cases like `--dir .` where the user wants to match against
|
|
356
|
+
// discovered boilerplates (e.g., "module" matching "default/module")
|
|
357
|
+
const boilerplates = (0, scan_boilerplates_1.scanBoilerplatesRecursive)(templateDir);
|
|
358
|
+
const match = (0, scan_boilerplates_1.findBoilerplateByPath)(boilerplates, fromPath);
|
|
359
|
+
if (match) {
|
|
360
|
+
return {
|
|
361
|
+
fromPath: match.relativePath,
|
|
362
|
+
resolvedTemplatePath: match.absolutePath,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
325
365
|
return {
|
|
326
366
|
fromPath,
|
|
327
367
|
resolvedTemplatePath: path.join(templateDir, fromPath),
|