diff --git a/packages/create/src/cli.ts b/packages/create/src/cli.ts index b0bb886..251d817 100644 --- a/packages/create/src/cli.ts +++ b/packages/create/src/cli.ts @@ -1,2 +1,77 @@ -#!/usr/bin/env node -console.log("create-antd-admin stub"); +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { Command, Option } from "commander"; +import pc from "picocolors"; +import { runCreate } from "./commands/create.js"; +import { EXIT_ERROR, EXIT_SIGINT } from "./constants.js"; +import { CliError } from "./errors.js"; + +const pkg = JSON.parse( + readFileSync( + path.join(path.dirname(fileURLToPath(import.meta.url)), "../package.json"), + "utf8" + ) +) as { version: string }; + +const program = new Command(); + +program + .name("create-antd-admin") + .description("Create an Antd Admin app from official examples") + .version(pkg.version, "-v, --version") + .argument("[project-directory]", "Directory for the new project") + .option("-e, --example ", "Example name or template URL") + .option("--example-path ", "Path inside repo when URL is ambiguous") + .addOption( + new Option("-m, --package-manager ", "Package manager").choices([ + "npm", + "yarn", + "pnpm", + "bun", + ]) + ) + .option("--skip-install", "Skip installing dependencies", false) + .option("--skip-transforms", "Skip code transforms", false) + .option("--no-git", "Skip git repository initialization") + .action( + async ( + projectDirectory: string | undefined, + cmdOpts: { + example?: string; + examplePath?: string; + packageManager?: "npm" | "yarn" | "pnpm" | "bun"; + skipInstall?: boolean; + skipTransforms?: boolean; + git?: boolean; + } + ) => { + try { + await runCreate({ + projectDirectory, + example: cmdOpts.example, + examplePath: cmdOpts.examplePath, + packageManager: cmdOpts.packageManager, + skipInstall: Boolean(cmdOpts.skipInstall), + skipTransforms: Boolean(cmdOpts.skipTransforms), + git: cmdOpts.git !== false, + }); + } catch (e) { + if (e instanceof CliError) { + console.error(pc.red(e.message)); + process.exit(e.exitCode); + } + console.error(e); + process.exit(EXIT_ERROR); + } + } + ); + +process.on("SIGINT", () => { + process.exit(EXIT_SIGINT); +}); + +program.parseAsync(process.argv).catch((e) => { + console.error(e); + process.exit(EXIT_ERROR); +}); diff --git a/packages/create/src/commands/create.ts b/packages/create/src/commands/create.ts new file mode 100644 index 0000000..cdc529d --- /dev/null +++ b/packages/create/src/commands/create.ts @@ -0,0 +1,99 @@ +import fsSync from "node:fs"; +import path from "node:path"; +import pc from "picocolors"; +import { assertValidExample, loadExamplesManifest } from "../examples.js"; +import { downloadExampleToTemp } from "../download.js"; +import { installDependencies } from "../install.js"; +import { + detectAvailableManagers, + type PackageManagerName, +} from "../package-manager.js"; +import { resolveTargetRoot } from "../paths.js"; +import { + promptExample, + promptPackageManager, + promptPackageName, + promptTargetDir, +} from "../prompts.js"; +import { resolveExampleSource } from "../resolve-example-source.js"; +import { tryGitInit } from "../git.js"; +import { runTransforms } from "../transforms/run-transforms.js"; + +export async function runCreate( + opts: Omit & { + projectDirectory?: string; + git?: boolean; + } +): Promise { + const cwd = process.cwd(); + const manifest = loadExamplesManifest(); + + const exampleTrim = opts.example?.trim(); + const example = exampleTrim + ? exampleTrim + : await promptExample(manifest); + + if (!/^https?:\/\//i.test(example)) { + assertValidExample(example, manifest); + } + + const projectDirRaw = opts.projectDirectory?.trim(); + const projectDir = projectDirRaw + ? projectDirRaw + : await promptTargetDir(); + + const targetRoot = resolveTargetRoot(cwd, projectDir); + fsSync.mkdirSync(targetRoot, { recursive: true }); + + const defaultPkgName = + projectDir === "." + ? (() => { + const normalized = path + .basename(cwd) + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); + return normalized || "antd-admin-app"; + })() + : path.basename(targetRoot); + + const packageName = await promptPackageName(defaultPkgName); + + const packageManager: PackageManagerName = + opts.packageManager ?? + (await promptPackageManager(detectAvailableManagers())); + + if (opts.skipTransforms && opts.packageManager) { + console.warn( + pc.yellow( + "--skip-transforms was used with an explicit package manager; package.json name may not be updated before install." + ) + ); + } + + const { gigetSource } = resolveExampleSource({ + example, + examplePath: opts.examplePath, + }); + + const tmp = await downloadExampleToTemp(gigetSource); + try { + fsSync.cpSync(tmp, targetRoot, { recursive: true, dereference: true }); + } finally { + fsSync.rmSync(tmp, { recursive: true, force: true }); + } + + await runTransforms({ + projectRoot: targetRoot, + packageName, + skip: opts.skipTransforms, + }); + + if (!opts.skipInstall) { + installDependencies(targetRoot, packageManager); + } + + tryGitInit(targetRoot, opts.git !== false); + + console.log(pc.green(`Success! Project created at ${targetRoot}`)); +}