create-gen-app 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -49
- package/cache/cache-manager.d.ts +60 -0
- package/cache/cache-manager.js +228 -0
- package/cache/types.d.ts +22 -0
- package/cache/types.js +2 -0
- package/esm/cache/cache-manager.js +191 -0
- package/esm/cache/types.js +1 -0
- package/esm/git/git-cloner.js +92 -0
- package/esm/git/types.js +1 -0
- package/esm/index.js +26 -53
- package/esm/{replace.js → template/replace.js} +2 -2
- package/esm/template/templatizer.js +69 -0
- package/esm/template/types.js +1 -0
- package/esm/utils/npm-version-check.js +52 -0
- package/esm/utils/types.js +1 -0
- package/git/git-cloner.d.ts +32 -0
- package/git/git-cloner.js +129 -0
- package/git/types.d.ts +15 -0
- package/git/types.js +2 -0
- package/index.d.ts +19 -6
- package/index.js +27 -75
- package/package.json +5 -5
- package/{extract.d.ts → template/extract.d.ts} +1 -1
- package/{prompt.d.ts → template/prompt.d.ts} +1 -1
- package/{replace.d.ts → template/replace.d.ts} +1 -1
- package/{replace.js → template/replace.js} +2 -2
- package/template/templatizer.d.ts +29 -0
- package/template/templatizer.js +106 -0
- package/template/types.d.ts +11 -0
- package/template/types.js +2 -0
- package/utils/npm-version-check.d.ts +17 -0
- package/utils/npm-version-check.js +57 -0
- package/utils/types.d.ts +6 -0
- package/utils/types.js +2 -0
- package/cache.d.ts +0 -13
- package/cache.js +0 -76
- package/clone.d.ts +0 -15
- package/clone.js +0 -86
- package/esm/cache.js +0 -38
- package/esm/clone.js +0 -49
- package/esm/template-cache.js +0 -223
- package/template-cache.d.ts +0 -59
- package/template-cache.js +0 -260
- /package/esm/{extract.js → template/extract.js} +0 -0
- /package/esm/{prompt.js → template/prompt.js} +0 -0
- /package/{extract.js → template/extract.js} +0 -0
- /package/{prompt.js → template/prompt.js} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
export class GitCloner {
|
|
6
|
+
/**
|
|
7
|
+
* Clone a git repository to a destination
|
|
8
|
+
* @param url - Repository URL (will be normalized)
|
|
9
|
+
* @param destination - Target directory path
|
|
10
|
+
* @param options - Clone options (branch, depth)
|
|
11
|
+
* @returns Clone result with normalized URL and destination
|
|
12
|
+
*/
|
|
13
|
+
clone(url, destination, options) {
|
|
14
|
+
const normalizedUrl = this.normalizeUrl(url);
|
|
15
|
+
// Clean destination if exists
|
|
16
|
+
if (fs.existsSync(destination)) {
|
|
17
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
18
|
+
}
|
|
19
|
+
this.executeClone(normalizedUrl, destination, options);
|
|
20
|
+
this.removeGitDir(destination);
|
|
21
|
+
return {
|
|
22
|
+
destination,
|
|
23
|
+
normalizedUrl,
|
|
24
|
+
branch: options?.branch,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Clone to a temporary directory
|
|
29
|
+
* @param url - Repository URL
|
|
30
|
+
* @param options - Clone options
|
|
31
|
+
* @returns Clone result with temp directory path
|
|
32
|
+
*/
|
|
33
|
+
cloneToTemp(url, options) {
|
|
34
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'create-gen-'));
|
|
35
|
+
return this.clone(url, tempDir, options);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Normalize a URL to git-cloneable format
|
|
39
|
+
* Handles: org/repo -> https://github.com/org/repo.git
|
|
40
|
+
*/
|
|
41
|
+
normalizeUrl(url) {
|
|
42
|
+
// Already a full URL
|
|
43
|
+
if (url.startsWith('git@') ||
|
|
44
|
+
url.startsWith('https://') ||
|
|
45
|
+
url.startsWith('http://')) {
|
|
46
|
+
return url;
|
|
47
|
+
}
|
|
48
|
+
// org/repo shorthand
|
|
49
|
+
if (/^[\w-]+\/[\w-]+$/.test(url)) {
|
|
50
|
+
return `https://github.com/${url}.git`;
|
|
51
|
+
}
|
|
52
|
+
return url;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate git URL format
|
|
56
|
+
*/
|
|
57
|
+
validateUrl(url) {
|
|
58
|
+
const normalized = this.normalizeUrl(url);
|
|
59
|
+
return (normalized.startsWith('git@') ||
|
|
60
|
+
normalized.startsWith('https://') ||
|
|
61
|
+
normalized.startsWith('http://'));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Remove .git directory from cloned repo
|
|
65
|
+
*/
|
|
66
|
+
removeGitDir(directory) {
|
|
67
|
+
const gitDir = path.join(directory, '.git');
|
|
68
|
+
if (fs.existsSync(gitDir)) {
|
|
69
|
+
fs.rmSync(gitDir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
executeClone(url, destination, options) {
|
|
73
|
+
const branch = options?.branch;
|
|
74
|
+
const depth = options?.depth ?? 1;
|
|
75
|
+
const singleBranch = options?.singleBranch ?? true;
|
|
76
|
+
const branchArgs = branch ? ` --branch ${branch}` : '';
|
|
77
|
+
const singleBranchArgs = singleBranch ? ' --single-branch' : '';
|
|
78
|
+
const depthArgs = ` --depth ${depth}`;
|
|
79
|
+
const command = `git clone${branchArgs}${singleBranchArgs}${depthArgs} ${url} ${destination}`;
|
|
80
|
+
try {
|
|
81
|
+
execSync(command, { stdio: 'inherit' });
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
// Clean up on failure
|
|
85
|
+
if (fs.existsSync(destination)) {
|
|
86
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
89
|
+
throw new Error(`Failed to clone repository: ${errorMessage}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
package/esm/git/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/index.js
CHANGED
|
@@ -1,61 +1,34 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export * from "./
|
|
8
|
-
export * from "./
|
|
9
|
-
export * from "./
|
|
10
|
-
export * from "./
|
|
11
|
-
export * from "./
|
|
1
|
+
import { extractVariables } from "./template/extract";
|
|
2
|
+
import { promptUser } from "./template/prompt";
|
|
3
|
+
import { replaceVariables } from "./template/replace";
|
|
4
|
+
// Export new modular classes
|
|
5
|
+
export * from "./cache/cache-manager";
|
|
6
|
+
export * from "./cache/types";
|
|
7
|
+
export * from "./git/git-cloner";
|
|
8
|
+
export * from "./git/types";
|
|
9
|
+
export * from "./template/templatizer";
|
|
10
|
+
export * from "./template/types";
|
|
11
|
+
export * from "./utils/npm-version-check";
|
|
12
|
+
export * from "./utils/types";
|
|
13
|
+
// Export template processing functions
|
|
14
|
+
export * from "./template/extract";
|
|
15
|
+
export * from "./template/prompt";
|
|
16
|
+
export * from "./template/replace";
|
|
17
|
+
// Export shared types
|
|
12
18
|
export * from "./types";
|
|
13
|
-
|
|
19
|
+
// DEPRECATED: Legacy exports for backward compatibility (will be removed in future)
|
|
20
|
+
// Use CacheManager, GitCloner, and Templatizer classes instead
|
|
21
|
+
export { extractVariables, promptUser, replaceVariables };
|
|
14
22
|
/**
|
|
23
|
+
* @deprecated This function is deprecated and will be removed in the next major version.
|
|
24
|
+
* Use the modular approach with CacheManager, GitCloner, and Templatizer classes instead.
|
|
25
|
+
* See create-gen-app-test package for an example of the new orchestration pattern.
|
|
26
|
+
*
|
|
15
27
|
* Create a new project from a template repository
|
|
16
28
|
* @param options - Options for creating the project
|
|
17
29
|
* @returns Path to the generated project
|
|
18
30
|
*/
|
|
19
31
|
export async function createGen(options) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const templateSource = await prepareTemplateDirectory({
|
|
23
|
-
templateUrl,
|
|
24
|
-
branch: fromBranch,
|
|
25
|
-
cache,
|
|
26
|
-
});
|
|
27
|
-
const cacheEnabled = cache !== false && (cache?.enabled !== false);
|
|
28
|
-
if (cacheEnabled) {
|
|
29
|
-
console.log(templateSource.cacheUsed
|
|
30
|
-
? "Using cached repository"
|
|
31
|
-
: "Caching repository for future runs...");
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
console.log("Cloning repository without cache...");
|
|
35
|
-
}
|
|
36
|
-
const normalizedPath = fromPath ? path.normalize(fromPath) : ".";
|
|
37
|
-
const templateRoot = normalizedPath && normalizedPath !== "."
|
|
38
|
-
? path.join(templateSource.templateDir, normalizedPath)
|
|
39
|
-
: templateSource.templateDir;
|
|
40
|
-
try {
|
|
41
|
-
if (!fs.existsSync(templateRoot)) {
|
|
42
|
-
throw new Error(`Template path "${fromPath}" does not exist in repository ${templateUrl}.`);
|
|
43
|
-
}
|
|
44
|
-
console.log("Extracting template variables...");
|
|
45
|
-
const extractedVariables = await extractVariables(templateRoot);
|
|
46
|
-
console.log(`Found ${extractedVariables.fileReplacers.length} file replacers`);
|
|
47
|
-
console.log(`Found ${extractedVariables.contentReplacers.length} content replacers`);
|
|
48
|
-
if (extractedVariables.projectQuestions) {
|
|
49
|
-
console.log(`Found ${extractedVariables.projectQuestions.questions.length} project questions`);
|
|
50
|
-
}
|
|
51
|
-
console.log("Prompting for variable values...");
|
|
52
|
-
const answers = await promptUser(extractedVariables, argv, noTty);
|
|
53
|
-
console.log(`Generating project in ${outputDir}...`);
|
|
54
|
-
await replaceVariables(templateRoot, outputDir, extractedVariables, answers);
|
|
55
|
-
console.log("Project created successfully!");
|
|
56
|
-
return outputDir;
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
templateSource.cleanup();
|
|
60
|
-
}
|
|
32
|
+
throw new Error('createGen() has been deprecated. Please use CacheManager, GitCloner, and Templatizer classes for modular template processing. ' +
|
|
33
|
+
'See create-gen-app-test package for the new orchestration pattern.');
|
|
61
34
|
}
|
|
@@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { Transform } from 'stream';
|
|
4
4
|
import { pipeline } from 'stream/promises';
|
|
5
|
-
import { renderLicense, isSupportedLicense } from '
|
|
5
|
+
import { renderLicense, isSupportedLicense } from '../licenses';
|
|
6
6
|
/**
|
|
7
7
|
* Replace variables in all files in the template directory
|
|
8
8
|
* @param templateDir - Path to the template directory
|
|
@@ -107,7 +107,7 @@ async function replaceInFile(sourcePath, destPath, extractedVariables, answers)
|
|
|
107
107
|
fs.mkdirSync(destDir, { recursive: true });
|
|
108
108
|
}
|
|
109
109
|
const replaceTransform = new Transform({
|
|
110
|
-
transform(chunk,
|
|
110
|
+
transform(chunk, _encoding, callback) {
|
|
111
111
|
let content = chunk.toString();
|
|
112
112
|
for (const replacer of extractedVariables.contentReplacers) {
|
|
113
113
|
if (answers[replacer.variable] !== undefined) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { extractVariables } from './extract';
|
|
4
|
+
import { promptUser } from './prompt';
|
|
5
|
+
import { replaceVariables } from './replace';
|
|
6
|
+
export class Templatizer {
|
|
7
|
+
constructor() {
|
|
8
|
+
// Pure template processor - no configuration needed
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Process a local template directory (extract + prompt + replace)
|
|
12
|
+
* @param templateDir - Local directory path (MUST be local, NOT git URL)
|
|
13
|
+
* @param outputDir - Output directory for generated project
|
|
14
|
+
* @param options - Processing options (argv overrides, noTty)
|
|
15
|
+
* @returns Processing result
|
|
16
|
+
*/
|
|
17
|
+
async process(templateDir, outputDir, options) {
|
|
18
|
+
this.validateTemplateDir(templateDir);
|
|
19
|
+
// Handle subdirectory within template
|
|
20
|
+
const actualTemplateDir = options?.fromPath
|
|
21
|
+
? path.join(templateDir, options.fromPath)
|
|
22
|
+
: templateDir;
|
|
23
|
+
this.validateTemplateDir(actualTemplateDir);
|
|
24
|
+
// Extract variables
|
|
25
|
+
const variables = await this.extract(actualTemplateDir);
|
|
26
|
+
// Prompt for values
|
|
27
|
+
const answers = await this.prompt(variables, options?.argv, options?.noTty);
|
|
28
|
+
// Replace variables
|
|
29
|
+
await this.replace(actualTemplateDir, outputDir, variables, answers);
|
|
30
|
+
return {
|
|
31
|
+
outputDir,
|
|
32
|
+
variables,
|
|
33
|
+
answers,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extract variables from template directory
|
|
38
|
+
*/
|
|
39
|
+
async extract(templateDir) {
|
|
40
|
+
return extractVariables(templateDir);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Prompt user for variables
|
|
44
|
+
*/
|
|
45
|
+
async prompt(extracted, argv, noTty) {
|
|
46
|
+
return promptUser(extracted, argv ?? {}, noTty ?? false);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Replace variables in template
|
|
50
|
+
*/
|
|
51
|
+
async replace(templateDir, outputDir, extracted, answers) {
|
|
52
|
+
return replaceVariables(templateDir, outputDir, extracted, answers);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate template directory exists and has content
|
|
56
|
+
*/
|
|
57
|
+
validateTemplateDir(templateDir) {
|
|
58
|
+
if (!fs.existsSync(templateDir)) {
|
|
59
|
+
throw new Error(`Template directory does not exist: ${templateDir}`);
|
|
60
|
+
}
|
|
61
|
+
if (!fs.statSync(templateDir).isDirectory()) {
|
|
62
|
+
throw new Error(`Template path is not a directory: ${templateDir}`);
|
|
63
|
+
}
|
|
64
|
+
const entries = fs.readdirSync(templateDir);
|
|
65
|
+
if (entries.length === 0) {
|
|
66
|
+
throw new Error(`Template directory is empty: ${templateDir}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Check if current package version is outdated compared to npm registry
|
|
4
|
+
* @param packageName - Package name to check
|
|
5
|
+
* @param currentVersion - Current version string
|
|
6
|
+
* @returns Version comparison result
|
|
7
|
+
*/
|
|
8
|
+
export async function checkNpmVersion(packageName, currentVersion) {
|
|
9
|
+
try {
|
|
10
|
+
const latestVersion = execSync(`npm view ${packageName} version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
11
|
+
const isOutdated = compareVersions(currentVersion, latestVersion) < 0;
|
|
12
|
+
return {
|
|
13
|
+
currentVersion,
|
|
14
|
+
latestVersion,
|
|
15
|
+
isOutdated,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return {
|
|
20
|
+
currentVersion,
|
|
21
|
+
latestVersion: null,
|
|
22
|
+
isOutdated: false,
|
|
23
|
+
error: error instanceof Error ? error.message : String(error),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Compare two semver version strings
|
|
29
|
+
* Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
|
30
|
+
*/
|
|
31
|
+
export function compareVersions(v1, v2) {
|
|
32
|
+
const parts1 = v1.split('.').map(Number);
|
|
33
|
+
const parts2 = v2.split('.').map(Number);
|
|
34
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
35
|
+
const p1 = parts1[i] || 0;
|
|
36
|
+
const p2 = parts2[i] || 0;
|
|
37
|
+
if (p1 < p2)
|
|
38
|
+
return -1;
|
|
39
|
+
if (p1 > p2)
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Print version warning to console if outdated
|
|
46
|
+
*/
|
|
47
|
+
export function warnIfOutdated(packageName, result) {
|
|
48
|
+
if (result.isOutdated && result.latestVersion) {
|
|
49
|
+
console.warn(`\n⚠️ New version available: ${result.currentVersion} → ${result.latestVersion}`);
|
|
50
|
+
console.warn(` Run: npm install -g ${packageName}@latest\n`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { GitCloneOptions, GitCloneResult } from './types';
|
|
2
|
+
export declare class GitCloner {
|
|
3
|
+
/**
|
|
4
|
+
* Clone a git repository to a destination
|
|
5
|
+
* @param url - Repository URL (will be normalized)
|
|
6
|
+
* @param destination - Target directory path
|
|
7
|
+
* @param options - Clone options (branch, depth)
|
|
8
|
+
* @returns Clone result with normalized URL and destination
|
|
9
|
+
*/
|
|
10
|
+
clone(url: string, destination: string, options?: GitCloneOptions): GitCloneResult;
|
|
11
|
+
/**
|
|
12
|
+
* Clone to a temporary directory
|
|
13
|
+
* @param url - Repository URL
|
|
14
|
+
* @param options - Clone options
|
|
15
|
+
* @returns Clone result with temp directory path
|
|
16
|
+
*/
|
|
17
|
+
cloneToTemp(url: string, options?: GitCloneOptions): GitCloneResult;
|
|
18
|
+
/**
|
|
19
|
+
* Normalize a URL to git-cloneable format
|
|
20
|
+
* Handles: org/repo -> https://github.com/org/repo.git
|
|
21
|
+
*/
|
|
22
|
+
normalizeUrl(url: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Validate git URL format
|
|
25
|
+
*/
|
|
26
|
+
validateUrl(url: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Remove .git directory from cloned repo
|
|
29
|
+
*/
|
|
30
|
+
removeGitDir(directory: string): void;
|
|
31
|
+
private executeClone;
|
|
32
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
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.GitCloner = void 0;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
class GitCloner {
|
|
42
|
+
/**
|
|
43
|
+
* Clone a git repository to a destination
|
|
44
|
+
* @param url - Repository URL (will be normalized)
|
|
45
|
+
* @param destination - Target directory path
|
|
46
|
+
* @param options - Clone options (branch, depth)
|
|
47
|
+
* @returns Clone result with normalized URL and destination
|
|
48
|
+
*/
|
|
49
|
+
clone(url, destination, options) {
|
|
50
|
+
const normalizedUrl = this.normalizeUrl(url);
|
|
51
|
+
// Clean destination if exists
|
|
52
|
+
if (fs.existsSync(destination)) {
|
|
53
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
this.executeClone(normalizedUrl, destination, options);
|
|
56
|
+
this.removeGitDir(destination);
|
|
57
|
+
return {
|
|
58
|
+
destination,
|
|
59
|
+
normalizedUrl,
|
|
60
|
+
branch: options?.branch,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Clone to a temporary directory
|
|
65
|
+
* @param url - Repository URL
|
|
66
|
+
* @param options - Clone options
|
|
67
|
+
* @returns Clone result with temp directory path
|
|
68
|
+
*/
|
|
69
|
+
cloneToTemp(url, options) {
|
|
70
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'create-gen-'));
|
|
71
|
+
return this.clone(url, tempDir, options);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Normalize a URL to git-cloneable format
|
|
75
|
+
* Handles: org/repo -> https://github.com/org/repo.git
|
|
76
|
+
*/
|
|
77
|
+
normalizeUrl(url) {
|
|
78
|
+
// Already a full URL
|
|
79
|
+
if (url.startsWith('git@') ||
|
|
80
|
+
url.startsWith('https://') ||
|
|
81
|
+
url.startsWith('http://')) {
|
|
82
|
+
return url;
|
|
83
|
+
}
|
|
84
|
+
// org/repo shorthand
|
|
85
|
+
if (/^[\w-]+\/[\w-]+$/.test(url)) {
|
|
86
|
+
return `https://github.com/${url}.git`;
|
|
87
|
+
}
|
|
88
|
+
return url;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Validate git URL format
|
|
92
|
+
*/
|
|
93
|
+
validateUrl(url) {
|
|
94
|
+
const normalized = this.normalizeUrl(url);
|
|
95
|
+
return (normalized.startsWith('git@') ||
|
|
96
|
+
normalized.startsWith('https://') ||
|
|
97
|
+
normalized.startsWith('http://'));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Remove .git directory from cloned repo
|
|
101
|
+
*/
|
|
102
|
+
removeGitDir(directory) {
|
|
103
|
+
const gitDir = path.join(directory, '.git');
|
|
104
|
+
if (fs.existsSync(gitDir)) {
|
|
105
|
+
fs.rmSync(gitDir, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
executeClone(url, destination, options) {
|
|
109
|
+
const branch = options?.branch;
|
|
110
|
+
const depth = options?.depth ?? 1;
|
|
111
|
+
const singleBranch = options?.singleBranch ?? true;
|
|
112
|
+
const branchArgs = branch ? ` --branch ${branch}` : '';
|
|
113
|
+
const singleBranchArgs = singleBranch ? ' --single-branch' : '';
|
|
114
|
+
const depthArgs = ` --depth ${depth}`;
|
|
115
|
+
const command = `git clone${branchArgs}${singleBranchArgs}${depthArgs} ${url} ${destination}`;
|
|
116
|
+
try {
|
|
117
|
+
(0, child_process_1.execSync)(command, { stdio: 'inherit' });
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
// Clean up on failure
|
|
121
|
+
if (fs.existsSync(destination)) {
|
|
122
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
125
|
+
throw new Error(`Failed to clone repository: ${errorMessage}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.GitCloner = GitCloner;
|
package/git/types.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface GitCloneOptions {
|
|
2
|
+
branch?: string;
|
|
3
|
+
depth?: number;
|
|
4
|
+
singleBranch?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface GitCloneResult {
|
|
7
|
+
destination: string;
|
|
8
|
+
normalizedUrl: string;
|
|
9
|
+
branch?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface GitUrlValidation {
|
|
12
|
+
isValid: boolean;
|
|
13
|
+
normalized?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
package/git/types.js
ADDED
package/index.d.ts
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
|
+
import { extractVariables } from "./template/extract";
|
|
2
|
+
import { promptUser } from "./template/prompt";
|
|
3
|
+
import { replaceVariables } from "./template/replace";
|
|
1
4
|
import { CreateGenOptions } from "./types";
|
|
2
|
-
export * from "./cache";
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
6
|
-
export * from "./
|
|
5
|
+
export * from "./cache/cache-manager";
|
|
6
|
+
export * from "./cache/types";
|
|
7
|
+
export * from "./git/git-cloner";
|
|
8
|
+
export * from "./git/types";
|
|
9
|
+
export * from "./template/templatizer";
|
|
10
|
+
export * from "./template/types";
|
|
11
|
+
export * from "./utils/npm-version-check";
|
|
12
|
+
export * from "./utils/types";
|
|
13
|
+
export * from "./template/extract";
|
|
14
|
+
export * from "./template/prompt";
|
|
15
|
+
export * from "./template/replace";
|
|
7
16
|
export * from "./types";
|
|
8
|
-
export
|
|
17
|
+
export { extractVariables, promptUser, replaceVariables };
|
|
9
18
|
/**
|
|
19
|
+
* @deprecated This function is deprecated and will be removed in the next major version.
|
|
20
|
+
* Use the modular approach with CacheManager, GitCloner, and Templatizer classes instead.
|
|
21
|
+
* See create-gen-app-test package for an example of the new orchestration pattern.
|
|
22
|
+
*
|
|
10
23
|
* Create a new project from a template repository
|
|
11
24
|
* @param options - Options for creating the project
|
|
12
25
|
* @returns Path to the generated project
|
package/index.js
CHANGED
|
@@ -10,91 +10,43 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
|
10
10
|
if (k2 === undefined) k2 = k;
|
|
11
11
|
o[k2] = m[k];
|
|
12
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
13
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
36
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
15
|
};
|
|
38
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.replaceVariables = exports.promptUser = exports.extractVariables = void 0;
|
|
39
18
|
exports.createGen = createGen;
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
__exportStar(require("./
|
|
48
|
-
__exportStar(require("./
|
|
49
|
-
__exportStar(require("./
|
|
50
|
-
__exportStar(require("./
|
|
19
|
+
const extract_1 = require("./template/extract");
|
|
20
|
+
Object.defineProperty(exports, "extractVariables", { enumerable: true, get: function () { return extract_1.extractVariables; } });
|
|
21
|
+
const prompt_1 = require("./template/prompt");
|
|
22
|
+
Object.defineProperty(exports, "promptUser", { enumerable: true, get: function () { return prompt_1.promptUser; } });
|
|
23
|
+
const replace_1 = require("./template/replace");
|
|
24
|
+
Object.defineProperty(exports, "replaceVariables", { enumerable: true, get: function () { return replace_1.replaceVariables; } });
|
|
25
|
+
// Export new modular classes
|
|
26
|
+
__exportStar(require("./cache/cache-manager"), exports);
|
|
27
|
+
__exportStar(require("./cache/types"), exports);
|
|
28
|
+
__exportStar(require("./git/git-cloner"), exports);
|
|
29
|
+
__exportStar(require("./git/types"), exports);
|
|
30
|
+
__exportStar(require("./template/templatizer"), exports);
|
|
31
|
+
__exportStar(require("./template/types"), exports);
|
|
32
|
+
__exportStar(require("./utils/npm-version-check"), exports);
|
|
33
|
+
__exportStar(require("./utils/types"), exports);
|
|
34
|
+
// Export template processing functions
|
|
35
|
+
__exportStar(require("./template/extract"), exports);
|
|
36
|
+
__exportStar(require("./template/prompt"), exports);
|
|
37
|
+
__exportStar(require("./template/replace"), exports);
|
|
38
|
+
// Export shared types
|
|
51
39
|
__exportStar(require("./types"), exports);
|
|
52
|
-
__exportStar(require("./template-cache"), exports);
|
|
53
40
|
/**
|
|
41
|
+
* @deprecated This function is deprecated and will be removed in the next major version.
|
|
42
|
+
* Use the modular approach with CacheManager, GitCloner, and Templatizer classes instead.
|
|
43
|
+
* See create-gen-app-test package for an example of the new orchestration pattern.
|
|
44
|
+
*
|
|
54
45
|
* Create a new project from a template repository
|
|
55
46
|
* @param options - Options for creating the project
|
|
56
47
|
* @returns Path to the generated project
|
|
57
48
|
*/
|
|
58
49
|
async function createGen(options) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const templateSource = await (0, cache_1.prepareTemplateDirectory)({
|
|
62
|
-
templateUrl,
|
|
63
|
-
branch: fromBranch,
|
|
64
|
-
cache,
|
|
65
|
-
});
|
|
66
|
-
const cacheEnabled = cache !== false && (cache?.enabled !== false);
|
|
67
|
-
if (cacheEnabled) {
|
|
68
|
-
console.log(templateSource.cacheUsed
|
|
69
|
-
? "Using cached repository"
|
|
70
|
-
: "Caching repository for future runs...");
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
console.log("Cloning repository without cache...");
|
|
74
|
-
}
|
|
75
|
-
const normalizedPath = fromPath ? path.normalize(fromPath) : ".";
|
|
76
|
-
const templateRoot = normalizedPath && normalizedPath !== "."
|
|
77
|
-
? path.join(templateSource.templateDir, normalizedPath)
|
|
78
|
-
: templateSource.templateDir;
|
|
79
|
-
try {
|
|
80
|
-
if (!fs.existsSync(templateRoot)) {
|
|
81
|
-
throw new Error(`Template path "${fromPath}" does not exist in repository ${templateUrl}.`);
|
|
82
|
-
}
|
|
83
|
-
console.log("Extracting template variables...");
|
|
84
|
-
const extractedVariables = await (0, extract_1.extractVariables)(templateRoot);
|
|
85
|
-
console.log(`Found ${extractedVariables.fileReplacers.length} file replacers`);
|
|
86
|
-
console.log(`Found ${extractedVariables.contentReplacers.length} content replacers`);
|
|
87
|
-
if (extractedVariables.projectQuestions) {
|
|
88
|
-
console.log(`Found ${extractedVariables.projectQuestions.questions.length} project questions`);
|
|
89
|
-
}
|
|
90
|
-
console.log("Prompting for variable values...");
|
|
91
|
-
const answers = await (0, prompt_1.promptUser)(extractedVariables, argv, noTty);
|
|
92
|
-
console.log(`Generating project in ${outputDir}...`);
|
|
93
|
-
await (0, replace_1.replaceVariables)(templateRoot, outputDir, extractedVariables, answers);
|
|
94
|
-
console.log("Project created successfully!");
|
|
95
|
-
return outputDir;
|
|
96
|
-
}
|
|
97
|
-
finally {
|
|
98
|
-
templateSource.cleanup();
|
|
99
|
-
}
|
|
50
|
+
throw new Error('createGen() has been deprecated. Please use CacheManager, GitCloner, and Templatizer classes for modular template processing. ' +
|
|
51
|
+
'See create-gen-app-test package for the new orchestration pattern.');
|
|
100
52
|
}
|