From ffce3130e7a6d7330c781e3df9624092ca7b5dd1 Mon Sep 17 00:00:00 2001 From: Eason Date: Mon, 24 Nov 2025 14:43:26 +0800 Subject: [PATCH 1/7] refactor(clone): improve clone function - Added a depth argument to the git clone command for a shallow clone to enhance performance. --- packages/create-gen-app/src/clone.ts | 42 +++++++++++++++++----------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/create-gen-app/src/clone.ts b/packages/create-gen-app/src/clone.ts index 3e3c9f3f..8c55f25c 100644 --- a/packages/create-gen-app/src/clone.ts +++ b/packages/create-gen-app/src/clone.ts @@ -1,7 +1,7 @@ -import { execSync } from 'child_process'; -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; +import { execSync } from "child_process"; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; export interface CloneOptions { branch?: string; @@ -12,23 +12,27 @@ export interface CloneOptions { * @param url - Repository URL (GitHub or any git URL) * @returns Path to the cloned repository */ -export async function cloneRepo(url: string, options: CloneOptions = {}): Promise { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'create-gen-')); +export async function cloneRepo( + url: string, + options: CloneOptions = {} +): Promise { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "create-gen-")); const { branch } = options; - + try { const gitUrl = normalizeGitUrl(url); - const branchArgs = branch ? ` --branch ${branch} --single-branch` : ''; - - execSync(`git clone${branchArgs} ${gitUrl} ${tempDir}`, { - stdio: 'inherit' + const branchArgs = branch ? ` --branch ${branch} --single-branch` : ""; + const depthArgs = " --depth 1"; // use shallow clone for speed; remove if future features need full history + + execSync(`git clone${branchArgs}${depthArgs} ${gitUrl} ${tempDir}`, { + stdio: "inherit", }); - - const gitDir = path.join(tempDir, '.git'); + + const gitDir = path.join(tempDir, ".git"); if (fs.existsSync(gitDir)) { fs.rmSync(gitDir, { recursive: true, force: true }); } - + return tempDir; } catch (error) { if (fs.existsSync(tempDir)) { @@ -45,13 +49,17 @@ export async function cloneRepo(url: string, options: CloneOptions = {}): Promis * @returns Normalized git URL */ function normalizeGitUrl(url: string): string { - if (url.startsWith('git@') || url.startsWith('https://') || url.startsWith('http://')) { + if ( + url.startsWith("git@") || + url.startsWith("https://") || + url.startsWith("http://") + ) { return url; } - + if (/^[\w-]+\/[\w-]+$/.test(url)) { return `https://github.com/${url}.git`; } - + return url; } From d3727b4a556b6c3dd396cca2a83b76ac0fd3a144 Mon Sep 17 00:00:00 2001 From: Eason Date: Mon, 24 Nov 2025 17:23:02 +0800 Subject: [PATCH 2/7] refactor(create-gen-app): move CLI to create-gen-app-test package Remove CLI entry point from create-gen-app package to make it a pure library API. The CLI implementation and tests are now housed in the create-gen-app-test package for integration testing purposes. Changes: - Remove src/cli.ts and __tests__/cli.test.ts from create-gen-app - Remove bin field and minimist dependencies from create-gen-app/package.json - Update README.md to clarify that the published package is API-only - Port CLI code to create-gen-app-test/src/cli.ts - Add CLI tests to create-gen-app-test/src/__tests__/cli.test.ts - Add minimist and inquirerer dependencies to create-gen-app-test - Update CLI imports to use create-gen-app library APIs This separation ensures create-gen-app remains focused on its core template generation functionality while maintaining CLI testing capabilities in the dedicated test harness package. --- packages/create-gen-app-test/package.json | 7 ++- .../src}/__tests__/cli.test.ts | 15 +++--- .../src/cli.ts | 46 ++++++++++++------- packages/create-gen-app/README.md | 35 ++------------ packages/create-gen-app/package.json | 10 +--- 5 files changed, 51 insertions(+), 62 deletions(-) rename packages/{create-gen-app => create-gen-app-test/src}/__tests__/cli.test.ts (82%) rename packages/{create-gen-app => create-gen-app-test}/src/cli.ts (84%) diff --git a/packages/create-gen-app-test/package.json b/packages/create-gen-app-test/package.json index 8a1f46e8..6255515b 100644 --- a/packages/create-gen-app-test/package.json +++ b/packages/create-gen-app-test/package.json @@ -30,10 +30,13 @@ }, "dependencies": { "appstash": "workspace:*", - "create-gen-app": "workspace:*" + "create-gen-app": "workspace:*", + "inquirerer": "workspace:*", + "minimist": "^1.2.8" }, "devDependencies": { + "@types/minimist": "^1.2.5", "makage": "0.1.5" }, "keywords": [] -} +} \ No newline at end of file diff --git a/packages/create-gen-app/__tests__/cli.test.ts b/packages/create-gen-app-test/src/__tests__/cli.test.ts similarity index 82% rename from packages/create-gen-app/__tests__/cli.test.ts rename to packages/create-gen-app-test/src/__tests__/cli.test.ts index 511358af..62ffbdea 100644 --- a/packages/create-gen-app/__tests__/cli.test.ts +++ b/packages/create-gen-app-test/src/__tests__/cli.test.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; -import { runCli } from "../src/cli"; +import { runCli } from "../cli"; import { TEST_BRANCH, TEST_REPO, @@ -9,11 +9,11 @@ import { buildAnswers, cleanupWorkspace, createTempWorkspace, -} from "../test-utils/integration-helpers"; +} from "../../../create-gen-app/test-utils/integration-helpers"; jest.setTimeout(180_000); -describe("CLI integration (GitHub templates)", () => { +describe("CLI integration via create-gen-app-test harness", () => { it("generates a project using the real repo", async () => { const workspace = createTempWorkspace("cli"); const answers = buildAnswers("cli"); @@ -65,12 +65,15 @@ describe("CLI integration (GitHub templates)", () => { }); it("prints version and exits when --version is provided", async () => { - const logSpy = jest.spyOn(console, "log").mockImplementation(() => undefined); + const logSpy = jest + .spyOn(console, "log") + .mockImplementation(() => undefined); await runCli(["--version"]); - expect(logSpy).toHaveBeenCalledWith(expect.stringMatching(/create-gen-app v/)); + expect(logSpy).toHaveBeenCalledWith( + expect.stringMatching(/create-gen-app v/) + ); logSpy.mockRestore(); }); }); - diff --git a/packages/create-gen-app/src/cli.ts b/packages/create-gen-app-test/src/cli.ts similarity index 84% rename from packages/create-gen-app/src/cli.ts rename to packages/create-gen-app-test/src/cli.ts index eadcc584..6b309d9f 100644 --- a/packages/create-gen-app/src/cli.ts +++ b/packages/create-gen-app-test/src/cli.ts @@ -6,15 +6,15 @@ import * as path from "path"; import { Inquirerer, ListQuestion } from "inquirerer"; import minimist, { ParsedArgs } from "minimist"; -import { cloneRepo } from "./clone"; -import { createGen } from "./index"; -import packageJson from "../package.json"; +import { cloneRepo, createGen } from "create-gen-app"; +import createGenPackageJson from "create-gen-app/package.json"; const DEFAULT_REPO = "https://github.com/launchql/pgpm-boilerplates.git"; const DEFAULT_PATH = "."; const DEFAULT_OUTPUT_FALLBACK = "create-gen-app-output"; -const PACKAGE_VERSION = packageJson.version ?? "0.0.0"; +const PACKAGE_VERSION = + (createGenPackageJson as { version?: string }).version ?? "0.0.0"; const RESERVED_ARG_KEYS = new Set([ "_", @@ -43,7 +43,9 @@ export interface CliResult { template: string; } -export async function runCli(rawArgv: string[] = process.argv.slice(2)): Promise { +export async function runCli( + rawArgv: string[] = process.argv.slice(2) +): Promise { const args = minimist(rawArgv, { alias: { r: "repo", @@ -87,8 +89,13 @@ export async function runCli(rawArgv: string[] = process.argv.slice(2)): Promise tempDir = await cloneRepo(args.repo, { branch: args.branch }); const selectionRoot = path.join(tempDir, args.path); - if (!fs.existsSync(selectionRoot) || !fs.statSync(selectionRoot).isDirectory()) { - throw new Error(`Template path "${args.path}" does not exist in ${args.repo}`); + if ( + !fs.existsSync(selectionRoot) || + !fs.statSync(selectionRoot).isDirectory() + ) { + throw new Error( + `Template path "${args.path}" does not exist in ${args.repo}` + ); } const templates = fs @@ -134,7 +141,9 @@ export async function runCli(rawArgv: string[] = process.argv.slice(2)): Promise ensureOutputDir(outputDir, Boolean(args.force)); const answerOverrides = extractAnswerOverrides(args); - const noTty = Boolean(args["no-tty"] ?? (args as Record).noTty); + const noTty = Boolean( + args["no-tty"] ?? (args as Record).noTty + ); await createGen({ templateUrl: args.repo, @@ -156,11 +165,10 @@ export async function runCli(rawArgv: string[] = process.argv.slice(2)): Promise function printHelp(): void { console.log(` -create-gen-app CLI +create-gen-app CLI (test harness) Usage: - create-gen-app [options] [outputDir] - cga [options] [outputDir] + node cli [options] [outputDir] Options: -r, --repo Git repository to clone (default: ${DEFAULT_REPO}) @@ -174,7 +182,7 @@ Options: -h, --help Show this help message You can also pass variable overrides, e.g.: - create-gen-app --template module --PROJECT_NAME my-app + node cli --template module --PROJECT_NAME my-app `); } @@ -193,7 +201,9 @@ async function promptForTemplate(templates: string[]): Promise { }; try { - const answers = (await prompter.prompt({}, [question])) as { template: string }; + const answers = (await prompter.prompt({}, [question])) as { + template: string; + }; return answers.template; } finally { if (typeof (prompter as any).close === "function") { @@ -202,8 +212,13 @@ async function promptForTemplate(templates: string[]): Promise { } } -function resolveOutputDir(outputArg: string | undefined, template?: string): string { - const base = outputArg ?? (template ? path.join(process.cwd(), template) : DEFAULT_OUTPUT_FALLBACK); +function resolveOutputDir( + outputArg: string | undefined, + template?: string +): string { + const base = + outputArg ?? + (template ? path.join(process.cwd(), template) : DEFAULT_OUTPUT_FALLBACK); return path.resolve(base); } @@ -238,4 +253,3 @@ if (require.main === module) { process.exitCode = 1; }); } - diff --git a/packages/create-gen-app/README.md b/packages/create-gen-app/README.md index 73843771..92b6ff69 100644 --- a/packages/create-gen-app/README.md +++ b/packages/create-gen-app/README.md @@ -16,48 +16,23 @@

-A TypeScript-first CLI/library for cloning template repositories, asking the user for variables, and generating a new project with sensible defaults. +A TypeScript-first library for cloning template repositories, asking the user for variables, and generating a new project with sensible defaults. ## Features - Clone any Git repo (or GitHub `org/repo` shorthand) and optionally select a branch + subdirectory - Extract template variables from filenames and file contents using the safer `____variable____` convention - Merge auto-discovered variables with `.questions.{json,js}` (questions win, including `ignore` patterns) -- Interactive prompts powered by `inquirerer`, with CLI flag overrides (`--VAR value`) and non-TTY mode for CI -- Built-in CLI (`create-gen-app` / `cga`) that discovers templates, prompts once, and writes output safely +- Interactive prompts powered by `inquirerer`, with flexible override mapping (`argv` support) and non-TTY mode for CI - 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` ## Installation ```bash npm install create-gen-app -# or for CLI only -npm install -g create-gen-app ``` -## CLI Usage - -```bash -# interactively pick a template from launchql/pgpm-boilerplates -create-gen-app --output ./workspace - -# short alias -cga --template module --branch main --output ./module \ - --USERFULLNAME "Jane Dev" --USEREMAIL jane@example.com - -# point to a different repo/branch/path -cga --repo github:my-org/my-templates --branch release \ - --path ./templates --template api --output ./api -``` - -Key flags: - -- `--repo`, `--branch`, `--path` – choose the Git repo, branch/tag, and subdirectory that contains templates -- `--template` – folder inside `--path` (auto-prompted if omitted) -- `--output` – destination directory (defaults to `./