Skip to content

Commit 6853906

Browse files
author
aXenDeveloper
committed
feat(create-vitnode): Add option git initialization during project setup
1 parent 6118db4 commit 6853906

6 files changed

Lines changed: 158 additions & 0 deletions

File tree

‎packages/create-vitnode-app/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ plugins: [myPlugin()]
7575
| `--package-manager` | Choose `npm` or `pnpm` for the generated project. |
7676
| `--eslint` | Include ESLint and Prettier configuration. |
7777
| `--skip-install` | Skip dependency installation after scaffolding. |
78+
| `--skip-git` | Skip initializing a git repository (created on `main` after install). |
7879
| `--mode` | Choose `singleApp`, `apiMonorepo`, or `onlyApi`. |
7980
| `--monorepo` | Create a workspace layout for plugins and multiple applications. |
8081
| `--docker` | Include local Docker services. |

‎packages/create-vitnode-app/src/create/create-vitnode.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,30 @@ import { fileURLToPath } from "node:url";
1212
import ora from "ora";
1313
import color from "picocolors";
1414

15+
import type { InitGitResult } from "../helpers/init-git.js";
1516
import type { CreateCliReturn } from "../questions.js";
1617

18+
import { initGitRepository } from "../helpers/init-git.js";
1719
import { generateMigrationsVitnode } from "../helpers/init-vitnode.js";
1820
import { installDependencies } from "../helpers/install-dependencies.js";
1921
import { isFolderEmpty } from "../helpers/is-folder-empty.js";
2022
import { createPackageJSON } from "./create-package-json.js";
2123

24+
const GIT_WARNINGS: Record<Exclude<InitGitResult, "created">, string> = {
25+
"already-in-repository":
26+
"Skipped git setup - the project already lives inside a git repository.",
27+
failed:
28+
'Could not initialize a git repository. Run "git init" in the project yourself once git is configured.',
29+
"git-unavailable":
30+
'Git is not installed, so no repository was created. Run "git init" in the project after installing git.',
31+
};
32+
2233
export const createVitNode = async ({
2334
root,
2435
appName,
2536
packageManager,
2637
eslint,
38+
git,
2739
install,
2840
docker,
2941
mode,
@@ -323,7 +335,17 @@ export const createVitNode = async ({
323335
}
324336
}
325337

338+
let gitResult: InitGitResult | undefined;
339+
if (git) {
340+
spinner.text = "Initializing git repository...";
341+
gitResult = await initGitRepository({ root });
342+
}
343+
326344
spinner.succeed(
327345
`${color.green("Success!")} Created ${color.cyan(appName)} at ${color.cyan(root)}`,
328346
);
347+
348+
if (gitResult && gitResult !== "created") {
349+
console.log(color.yellow(GIT_WARNINGS[gitResult]));
350+
}
329351
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { execFileSync } from "node:child_process";
2+
import { existsSync } from "node:fs";
3+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
4+
import { tmpdir } from "node:os";
5+
import { join } from "node:path";
6+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
7+
8+
import { initGitRepository } from "./init-git.js";
9+
10+
const git = (args: string[], cwd: string): string =>
11+
execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
12+
13+
describe("initGitRepository", () => {
14+
let root = "";
15+
16+
beforeEach(async () => {
17+
root = await mkdtemp(join(tmpdir(), "create-vitnode-git-"));
18+
await writeFile(join(root, "package.json"), "{}\n");
19+
process.env.GIT_AUTHOR_NAME = "VitNode";
20+
process.env.GIT_AUTHOR_EMAIL = "test@vitnode.com";
21+
process.env.GIT_COMMITTER_NAME = "VitNode";
22+
process.env.GIT_COMMITTER_EMAIL = "test@vitnode.com";
23+
});
24+
25+
afterEach(async () => {
26+
await rm(root, { force: true, recursive: true });
27+
});
28+
29+
it("creates a repository on the main branch with an initial commit", async () => {
30+
await expect(initGitRepository({ root })).resolves.toBe("created");
31+
32+
expect(existsSync(join(root, ".git"))).toBe(true);
33+
expect(git(["rev-parse", "--abbrev-ref", "HEAD"], root)).toBe("main");
34+
expect(git(["rev-list", "--count", "HEAD"], root)).toBe("1");
35+
expect(git(["show", "--name-only", "--format=", "HEAD"], root)).toContain(
36+
"package.json",
37+
);
38+
});
39+
40+
it("honours a custom branch name", async () => {
41+
await expect(initGitRepository({ root, branch: "trunk" })).resolves.toBe(
42+
"created",
43+
);
44+
45+
expect(git(["rev-parse", "--abbrev-ref", "HEAD"], root)).toBe("trunk");
46+
});
47+
48+
it("leaves an existing repository alone", async () => {
49+
git(["init"], root);
50+
51+
await expect(initGitRepository({ root })).resolves.toBe(
52+
"already-in-repository",
53+
);
54+
expect(git(["rev-list", "--count", "--all"], root)).toBe("0");
55+
});
56+
});
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { rm } from "node:fs/promises";
2+
import { join } from "node:path";
3+
4+
import { spawnCommand } from "./spawn-command.js";
5+
6+
export type InitGitResult =
7+
"already-in-repository" | "created" | "failed" | "git-unavailable";
8+
9+
export const DEFAULT_GIT_BRANCH = "main";
10+
11+
const runGit = async (args: string[], cwd: string): Promise<boolean> =>
12+
new Promise<boolean>(resolve => {
13+
const child = spawnCommand("git", args, { cwd, stdio: "ignore" });
14+
15+
child.on("error", () => {
16+
resolve(false);
17+
});
18+
child.on("close", code => {
19+
resolve(code === 0);
20+
});
21+
});
22+
23+
export const initGitRepository = async ({
24+
root,
25+
branch = DEFAULT_GIT_BRANCH,
26+
commitMessage = "Initial commit from Create VitNode App",
27+
}: {
28+
branch?: string;
29+
commitMessage?: string;
30+
root: string;
31+
}): Promise<InitGitResult> => {
32+
if (!(await runGit(["--version"], root))) {
33+
return "git-unavailable";
34+
}
35+
36+
if (await runGit(["rev-parse", "--is-inside-work-tree"], root)) {
37+
return "already-in-repository";
38+
}
39+
40+
const removeGitDirectory = async () => {
41+
await rm(join(root, ".git"), { force: true, recursive: true });
42+
};
43+
44+
const initialized =
45+
(await runGit(["init", "--initial-branch", branch], root)) ||
46+
((await runGit(["init"], root)) &&
47+
(await runGit(["symbolic-ref", "HEAD", `refs/heads/${branch}`], root)));
48+
49+
if (!initialized) {
50+
await removeGitDirectory();
51+
52+
return "failed";
53+
}
54+
55+
const committed =
56+
(await runGit(["add", "-A"], root)) &&
57+
(await runGit(["commit", "-m", commitMessage], root));
58+
59+
if (!committed) {
60+
await removeGitDirectory();
61+
62+
return "failed";
63+
}
64+
65+
return "created";
66+
};

‎packages/create-vitnode-app/src/index.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ const init = async () => {
5454
"--skip-install",
5555
"Skip installing packages after initializing the project.",
5656
);
57+
program.option(
58+
"--skip-git",
59+
"Skip initializing a git repository in the project.",
60+
);
5761
program.addOption(
5862
new Option(
5963
"--mode <mode>",

‎packages/create-vitnode-app/src/questions.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getAvailablePackageManagers } from "./helpers/get-available-package-man
88
export interface CreateCliReturn {
99
docker?: boolean;
1010
eslint: boolean;
11+
git: boolean;
1112
install: boolean;
1213
mode: "apiMonorepo" | "onlyApi" | "singleApp";
1314
monorepo?: boolean;
@@ -21,6 +22,7 @@ export const createQuestionsCli = async (
2122
const options: CreateCliReturn = {
2223
packageManager: optionsFromProgram.packageManager,
2324
eslint: optionsFromProgram.eslint,
25+
git: !optionsFromProgram.skipGit,
2426
install: !optionsFromProgram.skipInstall,
2527
docker: optionsFromProgram.docker,
2628
mode: optionsFromProgram.mode,
@@ -99,6 +101,13 @@ export const createQuestionsCli = async (
99101
});
100102
}
101103

104+
if (optionsFromProgram.skipGit === undefined) {
105+
options.git = await confirm({
106+
message: `Would you like to initialize a ${color.blue("Git repository")}?`,
107+
default: true,
108+
});
109+
}
110+
102111
if (optionsFromProgram.skipInstall === undefined) {
103112
options.install = await confirm({
104113
message: `Would you like to ${color.blue("Install dependencies")}?`,

0 commit comments

Comments
 (0)