@raulmoracode/create 1.0.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/LICENSE +21 -0
- package/README.md +231 -0
- package/dist/cli/args.js +34 -0
- package/dist/cli/output.js +20 -0
- package/dist/cli/run.js +326 -0
- package/dist/config/agents.js +224 -0
- package/dist/config/biome.js +30 -0
- package/dist/config/branding.js +2 -0
- package/dist/config/commitlint.js +8 -0
- package/dist/config/components.js +36 -0
- package/dist/config/editorconfig.js +14 -0
- package/dist/config/husky.js +14 -0
- package/dist/config/npmrc.js +3 -0
- package/dist/config/nvmrc.js +4 -0
- package/dist/config/pnpm-workspace.js +85 -0
- package/dist/config/query.js +16 -0
- package/dist/config/tailwind.js +31 -0
- package/dist/config/tech.js +83 -0
- package/dist/config/testing.js +26 -0
- package/dist/config/vscode.js +39 -0
- package/dist/frameworks/index.js +9 -0
- package/dist/frameworks/next.js +115 -0
- package/dist/frameworks/types.js +1 -0
- package/dist/frameworks/vite.js +135 -0
- package/dist/generators/configure-biome.js +22 -0
- package/dist/generators/configure-git-hooks.js +11 -0
- package/dist/generators/configure-node.js +45 -0
- package/dist/generators/configure-project.js +233 -0
- package/dist/generators/configure-registry.js +5 -0
- package/dist/generators/configure-shadcn.js +6 -0
- package/dist/generators/configure-testing.js +6 -0
- package/dist/generators/configure-vscode.js +6 -0
- package/dist/generators/create-project.js +3 -0
- package/dist/git/commit.js +12 -0
- package/dist/git/init.js +11 -0
- package/dist/git/push.js +59 -0
- package/dist/git/remote.js +30 -0
- package/dist/index.js +16 -0
- package/dist/prompts/cancel.js +8 -0
- package/dist/prompts/confirm.js +11 -0
- package/dist/prompts/framework.js +25 -0
- package/dist/prompts/github.js +25 -0
- package/dist/prompts/project.js +22 -0
- package/dist/prompts/tech.js +18 -0
- package/dist/utils/exec.js +107 -0
- package/dist/utils/filesystem.js +108 -0
- package/dist/utils/registry.js +18 -0
- package/dist/utils/validation.js +153 -0
- package/package.json +68 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { agentsMd } from "../config/agents.js";
|
|
2
|
+
import { FAVICON_URL, SITE_TITLE } from "../config/branding.js";
|
|
3
|
+
import { queryClientConfig } from "../config/query.js";
|
|
4
|
+
import { nextPostcssConfig, tailwindCss } from "../config/tailwind.js";
|
|
5
|
+
import { exec } from "../utils/exec.js";
|
|
6
|
+
import { isDirectory, joinPath, listDirEntries, readTextFile, removeIfExists, writeTextFile, } from "../utils/filesystem.js";
|
|
7
|
+
const CREATE_NEXT_APP_VERSION = "16.3.6";
|
|
8
|
+
const NEXT_PAGE_TSX = [
|
|
9
|
+
"export default function Home() {",
|
|
10
|
+
" return <div>hello</div>;",
|
|
11
|
+
"}",
|
|
12
|
+
"",
|
|
13
|
+
].join("\n");
|
|
14
|
+
const PROVIDERS_TSX = [
|
|
15
|
+
'"use client";',
|
|
16
|
+
"",
|
|
17
|
+
'import { QueryClientProvider } from "@tanstack/react-query";',
|
|
18
|
+
'import type { ReactNode } from "react";',
|
|
19
|
+
'import { queryClient } from "@/lib/query-client";',
|
|
20
|
+
"",
|
|
21
|
+
"export function Providers({ children }: { children: ReactNode }) {",
|
|
22
|
+
" return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;",
|
|
23
|
+
"}",
|
|
24
|
+
"",
|
|
25
|
+
].join("\n");
|
|
26
|
+
export const nextFramework = {
|
|
27
|
+
id: "next",
|
|
28
|
+
label: "Next.js",
|
|
29
|
+
async createProject(name, cwd, verbose) {
|
|
30
|
+
await exec("pnpm", [
|
|
31
|
+
"create",
|
|
32
|
+
`next-app@${CREATE_NEXT_APP_VERSION}`,
|
|
33
|
+
name,
|
|
34
|
+
"--ts",
|
|
35
|
+
"--app",
|
|
36
|
+
"--src-dir",
|
|
37
|
+
"--import-alias",
|
|
38
|
+
"@/*",
|
|
39
|
+
"--biome",
|
|
40
|
+
"--use-pnpm",
|
|
41
|
+
"--disable-git",
|
|
42
|
+
"--yes",
|
|
43
|
+
], { cwd, verbose });
|
|
44
|
+
return joinPath(cwd, name);
|
|
45
|
+
},
|
|
46
|
+
scripts() {
|
|
47
|
+
return {
|
|
48
|
+
dev: "next dev",
|
|
49
|
+
build: "next build",
|
|
50
|
+
start: "next start",
|
|
51
|
+
check: "biome check .",
|
|
52
|
+
format: "biome format --write .",
|
|
53
|
+
lint: "biome lint .",
|
|
54
|
+
test: "vitest",
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
pinnedDependencies() {
|
|
58
|
+
return {
|
|
59
|
+
next: "16.3.6",
|
|
60
|
+
react: "19.3.0",
|
|
61
|
+
"react-dom": "19.3.0",
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
pinnedDevDependencies() {
|
|
65
|
+
return {
|
|
66
|
+
typescript: "7.0.2",
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
removedDependencyPatterns() {
|
|
70
|
+
return [/eslint/i, /oxlint/i];
|
|
71
|
+
},
|
|
72
|
+
componentsJsonOptions() {
|
|
73
|
+
return { rsc: true, tailwindCssPath: "src/app/globals.css" };
|
|
74
|
+
},
|
|
75
|
+
async configureStarter(projectDir) {
|
|
76
|
+
const publicDir = joinPath(projectDir, "public");
|
|
77
|
+
if (await isDirectory(publicDir)) {
|
|
78
|
+
for (const entry of await listDirEntries(publicDir)) {
|
|
79
|
+
await removeIfExists(joinPath(publicDir, entry));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
await writeTextFile(joinPath(projectDir, "src", "app", "page.tsx"), NEXT_PAGE_TSX);
|
|
83
|
+
await removeIfExists(joinPath(projectDir, "src", "app", "page.module.css"));
|
|
84
|
+
await removeIfExists(joinPath(projectDir, "CLAUDE.md"));
|
|
85
|
+
await writeTextFile(joinPath(projectDir, "AGENTS.md"), agentsMd());
|
|
86
|
+
},
|
|
87
|
+
async configureBranding(projectDir) {
|
|
88
|
+
const layoutPath = joinPath(projectDir, "src", "app", "layout.tsx");
|
|
89
|
+
const layout = await readTextFile(layoutPath);
|
|
90
|
+
const branded = layout.replace(/title:\s*"[^"]*",?/, `title: "${SITE_TITLE}",\n icons: {\n icon: "${FAVICON_URL}",\n },`);
|
|
91
|
+
if (!branded.includes(`title: "${SITE_TITLE}"`) ||
|
|
92
|
+
!branded.includes(FAVICON_URL)) {
|
|
93
|
+
throw new Error("No se pudo configurar el layout de Next.js (título o favicon).");
|
|
94
|
+
}
|
|
95
|
+
await writeTextFile(layoutPath, branded);
|
|
96
|
+
await removeIfExists(joinPath(projectDir, "src", "app", "favicon.ico"));
|
|
97
|
+
},
|
|
98
|
+
async configureTailwind(projectDir) {
|
|
99
|
+
await writeTextFile(joinPath(projectDir, "src", "app", "globals.css"), tailwindCss());
|
|
100
|
+
await writeTextFile(joinPath(projectDir, "postcss.config.mjs"), nextPostcssConfig());
|
|
101
|
+
},
|
|
102
|
+
async configureTanStackQuery(projectDir) {
|
|
103
|
+
await writeTextFile(joinPath(projectDir, "src", "lib", "query-client.ts"), queryClientConfig());
|
|
104
|
+
await writeTextFile(joinPath(projectDir, "src", "app", "providers.tsx"), PROVIDERS_TSX);
|
|
105
|
+
const layoutPath = joinPath(projectDir, "src", "app", "layout.tsx");
|
|
106
|
+
const layout = await readTextFile(layoutPath);
|
|
107
|
+
const wrapped = layout
|
|
108
|
+
.replace(/<body([^>]*)>\{children\}<\/body>/, "<body$1><Providers>{children}</Providers></body>")
|
|
109
|
+
.replace('import "./globals.css";', 'import "./globals.css";\nimport { Providers } from "./providers";');
|
|
110
|
+
if (!wrapped.includes("<Providers>{children}</Providers>")) {
|
|
111
|
+
throw new Error("No se pudo configurar el layout de Next.js para envolver la aplicación con los providers.");
|
|
112
|
+
}
|
|
113
|
+
await writeTextFile(layoutPath, wrapped);
|
|
114
|
+
},
|
|
115
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { agentsMd } from "../config/agents.js";
|
|
2
|
+
import { FAVICON_URL, SITE_TITLE } from "../config/branding.js";
|
|
3
|
+
import { queryClientConfig } from "../config/query.js";
|
|
4
|
+
import { tailwindCss, viteTailwindConfig } from "../config/tailwind.js";
|
|
5
|
+
import { exec } from "../utils/exec.js";
|
|
6
|
+
import { isDirectory, joinPath, listDirEntries, parseJsonc, readTextFile, removeIfExists, writeTextFile, } from "../utils/filesystem.js";
|
|
7
|
+
const CREATE_VITE_VERSION = "9.2.1";
|
|
8
|
+
const VITE_MAIN_TSX = [
|
|
9
|
+
'import { StrictMode } from "react";',
|
|
10
|
+
'import { createRoot } from "react-dom/client";',
|
|
11
|
+
'import { QueryClientProvider } from "@tanstack/react-query";',
|
|
12
|
+
'import "./index.css";',
|
|
13
|
+
'import App from "./App.tsx";',
|
|
14
|
+
'import { queryClient } from "./lib/query-client";',
|
|
15
|
+
"",
|
|
16
|
+
'const rootElement = document.getElementById("root");',
|
|
17
|
+
"",
|
|
18
|
+
"if (!rootElement) {",
|
|
19
|
+
' throw new Error("No se encontró el elemento raíz #root");',
|
|
20
|
+
"}",
|
|
21
|
+
"",
|
|
22
|
+
"createRoot(rootElement).render(",
|
|
23
|
+
" <StrictMode>",
|
|
24
|
+
" <QueryClientProvider client={queryClient}>",
|
|
25
|
+
" <App />",
|
|
26
|
+
" </QueryClientProvider>",
|
|
27
|
+
" </StrictMode>",
|
|
28
|
+
");",
|
|
29
|
+
"",
|
|
30
|
+
].join("\n");
|
|
31
|
+
async function ensurePathAlias(projectDir) {
|
|
32
|
+
for (const file of ["tsconfig.app.json", "tsconfig.json"]) {
|
|
33
|
+
const tsconfigPath = joinPath(projectDir, file);
|
|
34
|
+
let raw = null;
|
|
35
|
+
try {
|
|
36
|
+
raw = await readTextFile(tsconfigPath);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const tsconfig = parseJsonc(raw);
|
|
42
|
+
const compilerOptions = tsconfig.compilerOptions ?? {};
|
|
43
|
+
compilerOptions.baseUrl = ".";
|
|
44
|
+
compilerOptions.paths = { "@/*": ["./src/*"] };
|
|
45
|
+
tsconfig.compilerOptions = compilerOptions;
|
|
46
|
+
await writeTextFile(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const VITE_APP_TSX = [
|
|
50
|
+
'import "./App.css";',
|
|
51
|
+
"",
|
|
52
|
+
"function App() {",
|
|
53
|
+
" return <div>hello</div>;",
|
|
54
|
+
"}",
|
|
55
|
+
"",
|
|
56
|
+
"export default App;",
|
|
57
|
+
"",
|
|
58
|
+
].join("\n");
|
|
59
|
+
export const viteFramework = {
|
|
60
|
+
id: "vite",
|
|
61
|
+
label: "React + Vite",
|
|
62
|
+
async createProject(name, cwd, verbose) {
|
|
63
|
+
await exec("pnpm", ["create", `vite@${CREATE_VITE_VERSION}`, name, "--template", "react-ts"], {
|
|
64
|
+
cwd,
|
|
65
|
+
verbose,
|
|
66
|
+
});
|
|
67
|
+
return joinPath(cwd, name);
|
|
68
|
+
},
|
|
69
|
+
scripts() {
|
|
70
|
+
return {
|
|
71
|
+
dev: "vite",
|
|
72
|
+
build: "vite build",
|
|
73
|
+
check: "biome check .",
|
|
74
|
+
format: "biome format --write .",
|
|
75
|
+
lint: "biome lint .",
|
|
76
|
+
test: "vitest",
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
pinnedDependencies() {
|
|
80
|
+
return {
|
|
81
|
+
react: "19.3.0",
|
|
82
|
+
"react-dom": "19.3.0",
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
pinnedDevDependencies() {
|
|
86
|
+
return {
|
|
87
|
+
typescript: "7.0.2",
|
|
88
|
+
vite: "8.3.1",
|
|
89
|
+
"@vitejs/plugin-react": "6.1.1",
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
removedDependencyPatterns() {
|
|
93
|
+
return [/eslint/i, /oxlint/i, /^globals$/];
|
|
94
|
+
},
|
|
95
|
+
async configureTailwind(projectDir) {
|
|
96
|
+
await writeTextFile(joinPath(projectDir, "vite.config.ts"), viteTailwindConfig());
|
|
97
|
+
await writeTextFile(joinPath(projectDir, "src", "index.css"), tailwindCss());
|
|
98
|
+
},
|
|
99
|
+
async configureTanStackQuery(projectDir) {
|
|
100
|
+
await writeTextFile(joinPath(projectDir, "src", "lib", "query-client.ts"), queryClientConfig());
|
|
101
|
+
await writeTextFile(joinPath(projectDir, "src", "main.tsx"), VITE_MAIN_TSX);
|
|
102
|
+
},
|
|
103
|
+
componentsJsonOptions() {
|
|
104
|
+
return { rsc: false, tailwindCssPath: "src/index.css" };
|
|
105
|
+
},
|
|
106
|
+
async configureStarter(projectDir) {
|
|
107
|
+
for (const dir of [
|
|
108
|
+
joinPath(projectDir, "public"),
|
|
109
|
+
joinPath(projectDir, "src", "assets"),
|
|
110
|
+
]) {
|
|
111
|
+
if (!(await isDirectory(dir))) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
for (const entry of await listDirEntries(dir)) {
|
|
115
|
+
await removeIfExists(joinPath(dir, entry));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
await writeTextFile(joinPath(projectDir, "src", "App.tsx"), VITE_APP_TSX);
|
|
119
|
+
await writeTextFile(joinPath(projectDir, "src", "App.css"), "");
|
|
120
|
+
await writeTextFile(joinPath(projectDir, "AGENTS.md"), agentsMd());
|
|
121
|
+
await ensurePathAlias(projectDir);
|
|
122
|
+
},
|
|
123
|
+
async configureBranding(projectDir) {
|
|
124
|
+
const indexPath = joinPath(projectDir, "index.html");
|
|
125
|
+
const html = await readTextFile(indexPath);
|
|
126
|
+
const branded = html
|
|
127
|
+
.replace(/<title>.*?<\/title>/, `<title>${SITE_TITLE}</title>`)
|
|
128
|
+
.replace(/<link[^>]*rel="icon"[^>]*>/, `<link rel="icon" type="image/x-icon" href="${FAVICON_URL}" />`);
|
|
129
|
+
if (!branded.includes(`<title>${SITE_TITLE}</title>`) ||
|
|
130
|
+
!branded.includes(FAVICON_URL)) {
|
|
131
|
+
throw new Error("No se pudo configurar el index.html del proyecto Vite (título o favicon).");
|
|
132
|
+
}
|
|
133
|
+
await writeTextFile(indexPath, branded);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { biomeConfig } from "../config/biome.js";
|
|
2
|
+
import { exec } from "../utils/exec.js";
|
|
3
|
+
import { joinPath, removeIfExists, writeTextFile, } from "../utils/filesystem.js";
|
|
4
|
+
export const TOOLING_CONFIG_FILES = [
|
|
5
|
+
"eslint.config.mjs",
|
|
6
|
+
"eslint.config.js",
|
|
7
|
+
".oxlintrc.json",
|
|
8
|
+
"_oxlintrc.json",
|
|
9
|
+
"oxlint.json",
|
|
10
|
+
];
|
|
11
|
+
export async function configureBiome(projectDir) {
|
|
12
|
+
await writeTextFile(joinPath(projectDir, "biome.json"), biomeConfig());
|
|
13
|
+
for (const file of TOOLING_CONFIG_FILES) {
|
|
14
|
+
await removeIfExists(joinPath(projectDir, file));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function biomeCheckWriteArgs() {
|
|
18
|
+
return ["exec", "biome", "check", "--write", "."];
|
|
19
|
+
}
|
|
20
|
+
export async function formatProject(projectDir, verbose) {
|
|
21
|
+
await exec("pnpm", biomeCheckWriteArgs(), { cwd: projectDir, verbose });
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { commitlintConfig } from "../config/commitlint.js";
|
|
2
|
+
import { huskyCommitMsg, huskyPreCommit } from "../config/husky.js";
|
|
3
|
+
import { FULL_TECH_SELECTION } from "../config/tech.js";
|
|
4
|
+
import { joinPath, makeExecutable, writeTextFile, } from "../utils/filesystem.js";
|
|
5
|
+
export async function configureGitHooks(projectDir, selection = FULL_TECH_SELECTION) {
|
|
6
|
+
await writeTextFile(joinPath(projectDir, ".husky", "pre-commit"), huskyPreCommit(selection));
|
|
7
|
+
await writeTextFile(joinPath(projectDir, ".husky", "commit-msg"), huskyCommitMsg());
|
|
8
|
+
await makeExecutable(joinPath(projectDir, ".husky", "pre-commit"));
|
|
9
|
+
await makeExecutable(joinPath(projectDir, ".husky", "commit-msg"));
|
|
10
|
+
await writeTextFile(joinPath(projectDir, "commitlint.config.ts"), commitlintConfig());
|
|
11
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { editorconfigContent } from "../config/editorconfig.js";
|
|
2
|
+
import { NODE_VERSION, nvmrcContent } from "../config/nvmrc.js";
|
|
3
|
+
import { joinPath, readTextFile, writeTextFile } from "../utils/filesystem.js";
|
|
4
|
+
const REQUIRED_GITIGNORE_ENTRIES = [
|
|
5
|
+
"node_modules",
|
|
6
|
+
"dist",
|
|
7
|
+
".env",
|
|
8
|
+
".env.*",
|
|
9
|
+
".next",
|
|
10
|
+
"coverage",
|
|
11
|
+
];
|
|
12
|
+
export async function configureNode(projectDir) {
|
|
13
|
+
await writeTextFile(joinPath(projectDir, ".nvmrc"), nvmrcContent());
|
|
14
|
+
}
|
|
15
|
+
export function nodeVersion() {
|
|
16
|
+
return NODE_VERSION;
|
|
17
|
+
}
|
|
18
|
+
export async function configureEditorconfig(projectDir) {
|
|
19
|
+
await writeTextFile(joinPath(projectDir, ".editorconfig"), editorconfigContent());
|
|
20
|
+
}
|
|
21
|
+
export function requiredGitignoreEntries() {
|
|
22
|
+
return [...REQUIRED_GITIGNORE_ENTRIES];
|
|
23
|
+
}
|
|
24
|
+
export async function augmentGitignore(projectDir) {
|
|
25
|
+
const gitignorePath = joinPath(projectDir, ".gitignore");
|
|
26
|
+
let content = "";
|
|
27
|
+
try {
|
|
28
|
+
content = await readTextFile(gitignorePath);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
content = "";
|
|
32
|
+
}
|
|
33
|
+
const existing = new Set(content
|
|
34
|
+
.split("\n")
|
|
35
|
+
.map((line) => line.trim())
|
|
36
|
+
.filter(Boolean));
|
|
37
|
+
const missing = REQUIRED_GITIGNORE_ENTRIES.filter((entry) => !existing.has(entry));
|
|
38
|
+
if (missing.length === 0)
|
|
39
|
+
return;
|
|
40
|
+
const merged = content.replace(/\s+$/, "");
|
|
41
|
+
const next = merged
|
|
42
|
+
? `${merged}\n${missing.join("\n")}\n`
|
|
43
|
+
: `${missing.join("\n")}\n`;
|
|
44
|
+
await writeTextFile(gitignorePath, next);
|
|
45
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { collectLockedPackages, mergePnpmWorkspaceYaml, } from "../config/pnpm-workspace.js";
|
|
2
|
+
import { FULL_TECH_SELECTION } from "../config/tech.js";
|
|
3
|
+
import { exec } from "../utils/exec.js";
|
|
4
|
+
import { joinPath, readJsonFile, readTextFile, removeIfExists, writeTextFile, } from "../utils/filesystem.js";
|
|
5
|
+
export const PNPM_VERSION = "12.6.0";
|
|
6
|
+
export const CLASS_VARIANCE_AUTHORITY_VERSION = "0.7.1";
|
|
7
|
+
export const HUSKY_VERSION = "9.1.7";
|
|
8
|
+
export const COMMITLINT_CLI_VERSION = "21.2.3";
|
|
9
|
+
export const COMMITLINT_CONFIG_CONVENTIONAL_VERSION = "21.2.3";
|
|
10
|
+
export function runtimeDependencies(selection = FULL_TECH_SELECTION) {
|
|
11
|
+
const dependencies = {};
|
|
12
|
+
if (selection.zustand) {
|
|
13
|
+
dependencies.zustand = "5.0.15";
|
|
14
|
+
}
|
|
15
|
+
if (selection.forms) {
|
|
16
|
+
dependencies["react-hook-form"] = "7.89.0";
|
|
17
|
+
dependencies.zod = "4.6.5";
|
|
18
|
+
}
|
|
19
|
+
if (selection["tanstack-query"]) {
|
|
20
|
+
dependencies["@tanstack/react-query"] = "5.104.0";
|
|
21
|
+
}
|
|
22
|
+
if (selection.registry) {
|
|
23
|
+
dependencies["@raulmoracode/icons"] = "1.1.0";
|
|
24
|
+
}
|
|
25
|
+
return dependencies;
|
|
26
|
+
}
|
|
27
|
+
export function devDependencies(framework, selection = FULL_TECH_SELECTION) {
|
|
28
|
+
const common = {};
|
|
29
|
+
if (selection.biome) {
|
|
30
|
+
common["@biomejs/biome"] = "2.5.14";
|
|
31
|
+
}
|
|
32
|
+
if (selection.testing) {
|
|
33
|
+
common.vitest = "5.0.2";
|
|
34
|
+
common["@testing-library/react"] = "16.3.3";
|
|
35
|
+
common["@testing-library/dom"] = "10.4.2";
|
|
36
|
+
common.jsdom = "30.1.1";
|
|
37
|
+
}
|
|
38
|
+
if (selection.shadcn) {
|
|
39
|
+
common.clsx = "2.1.1";
|
|
40
|
+
common["tailwind-merge"] = "3.7.0";
|
|
41
|
+
common["class-variance-authority"] = CLASS_VARIANCE_AUTHORITY_VERSION;
|
|
42
|
+
}
|
|
43
|
+
if (selection.husky) {
|
|
44
|
+
common.husky = HUSKY_VERSION;
|
|
45
|
+
common["@commitlint/cli"] = COMMITLINT_CLI_VERSION;
|
|
46
|
+
common["@commitlint/config-conventional"] =
|
|
47
|
+
COMMITLINT_CONFIG_CONVENTIONAL_VERSION;
|
|
48
|
+
}
|
|
49
|
+
if (selection.tailwind) {
|
|
50
|
+
common.tailwindcss = "4.3.3";
|
|
51
|
+
}
|
|
52
|
+
if (framework.id === "vite") {
|
|
53
|
+
return selection.tailwind
|
|
54
|
+
? { ...common, "@tailwindcss/vite": "4.3.3" }
|
|
55
|
+
: { ...common };
|
|
56
|
+
}
|
|
57
|
+
return selection.tailwind
|
|
58
|
+
? { ...common, "@tailwindcss/postcss": "4.3.3", postcss: "8.5.6" }
|
|
59
|
+
: { ...common };
|
|
60
|
+
}
|
|
61
|
+
export function pnpmInstallArgs() {
|
|
62
|
+
return ["install"];
|
|
63
|
+
}
|
|
64
|
+
export function pnpmAddArgs(dependencies) {
|
|
65
|
+
return [
|
|
66
|
+
"add",
|
|
67
|
+
...Object.entries(dependencies).map(([name, version]) => `${name}@${version}`),
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
export function pnpmAddDevArgs(dependencies) {
|
|
71
|
+
return [
|
|
72
|
+
"add",
|
|
73
|
+
"-D",
|
|
74
|
+
...Object.entries(dependencies).map(([name, version]) => `${name}@${version}`),
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
function stripRangePrefix(version) {
|
|
78
|
+
return version.replace(/^[\^~]/, "");
|
|
79
|
+
}
|
|
80
|
+
export const PROJECT_AUTHOR = {
|
|
81
|
+
name: "Raul Mora",
|
|
82
|
+
url: "https://raulmoracode.com",
|
|
83
|
+
};
|
|
84
|
+
function projectScripts(framework, selection) {
|
|
85
|
+
const scripts = { ...framework.scripts() };
|
|
86
|
+
if (!selection.biome) {
|
|
87
|
+
delete scripts.check;
|
|
88
|
+
delete scripts.format;
|
|
89
|
+
delete scripts.lint;
|
|
90
|
+
}
|
|
91
|
+
if (!selection.testing) {
|
|
92
|
+
delete scripts.test;
|
|
93
|
+
}
|
|
94
|
+
if (selection.husky) {
|
|
95
|
+
scripts.prepare = "husky";
|
|
96
|
+
}
|
|
97
|
+
return scripts;
|
|
98
|
+
}
|
|
99
|
+
export async function patchPackageJson(projectDir, framework, projectName, githubUrl, selection = FULL_TECH_SELECTION) {
|
|
100
|
+
const packageJsonPath = joinPath(projectDir, "package.json");
|
|
101
|
+
const pkg = await readJsonFile(packageJsonPath);
|
|
102
|
+
pkg.name = projectName;
|
|
103
|
+
pkg.version = "0.1.0";
|
|
104
|
+
pkg.private = true;
|
|
105
|
+
pkg.type = "module";
|
|
106
|
+
pkg.author = { ...PROJECT_AUTHOR };
|
|
107
|
+
pkg.homepage = githubUrl;
|
|
108
|
+
pkg.repository = { type: "git", url: githubUrl };
|
|
109
|
+
pkg.scripts = projectScripts(framework, selection);
|
|
110
|
+
pkg.engines = { node: ">=24" };
|
|
111
|
+
pkg.packageManager = `pnpm@${PNPM_VERSION}`;
|
|
112
|
+
const dependencies = pkg.dependencies ?? {};
|
|
113
|
+
for (const [name, version] of Object.entries(framework.pinnedDependencies())) {
|
|
114
|
+
dependencies[name] = version;
|
|
115
|
+
}
|
|
116
|
+
const devDependenciesMap = pkg.devDependencies ?? {};
|
|
117
|
+
for (const [name, version] of Object.entries(framework.pinnedDevDependencies())) {
|
|
118
|
+
devDependenciesMap[name] = version;
|
|
119
|
+
}
|
|
120
|
+
if (selection.husky) {
|
|
121
|
+
// Ensure `prepare: husky` can run on the first `pnpm install`.
|
|
122
|
+
// Husky must already be listed, otherwise prepare fails with exit 127.
|
|
123
|
+
devDependenciesMap.husky = HUSKY_VERSION;
|
|
124
|
+
devDependenciesMap["@commitlint/cli"] = COMMITLINT_CLI_VERSION;
|
|
125
|
+
devDependenciesMap["@commitlint/config-conventional"] =
|
|
126
|
+
COMMITLINT_CONFIG_CONVENTIONAL_VERSION;
|
|
127
|
+
}
|
|
128
|
+
const removalPatterns = framework.removedDependencyPatterns();
|
|
129
|
+
for (const key of Object.keys(dependencies)) {
|
|
130
|
+
if (removalPatterns.some((pattern) => pattern.test(key))) {
|
|
131
|
+
delete dependencies[key];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const key of Object.keys(devDependenciesMap)) {
|
|
135
|
+
if (removalPatterns.some((pattern) => pattern.test(key))) {
|
|
136
|
+
delete devDependenciesMap[key];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const map of [dependencies, devDependenciesMap]) {
|
|
140
|
+
for (const [key, version] of Object.entries(map)) {
|
|
141
|
+
map[key] = stripRangePrefix(version);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const finalPkg = {
|
|
145
|
+
name: projectName,
|
|
146
|
+
version: "0.1.0",
|
|
147
|
+
private: true,
|
|
148
|
+
type: "module",
|
|
149
|
+
author: { ...PROJECT_AUTHOR },
|
|
150
|
+
homepage: githubUrl,
|
|
151
|
+
repository: { type: "git", url: githubUrl },
|
|
152
|
+
scripts: projectScripts(framework, selection),
|
|
153
|
+
engines: { node: ">=24" },
|
|
154
|
+
packageManager: `pnpm@${PNPM_VERSION}`,
|
|
155
|
+
};
|
|
156
|
+
for (const [key, value] of Object.entries(pkg)) {
|
|
157
|
+
if (!(key in finalPkg)) {
|
|
158
|
+
finalPkg[key] = value;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (Object.keys(dependencies).length > 0) {
|
|
162
|
+
finalPkg.dependencies = dependencies;
|
|
163
|
+
}
|
|
164
|
+
if (Object.keys(devDependenciesMap).length > 0) {
|
|
165
|
+
finalPkg.devDependencies = devDependenciesMap;
|
|
166
|
+
}
|
|
167
|
+
await writeTextFile(packageJsonPath, `${JSON.stringify(finalPkg, null, 2)}\n`);
|
|
168
|
+
}
|
|
169
|
+
export async function removeToolingConfig(projectDir, relativePaths) {
|
|
170
|
+
for (const relativePath of relativePaths) {
|
|
171
|
+
await removeIfExists(joinPath(projectDir, relativePath));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export async function installDependencies(projectDir, framework, verbose, selection = FULL_TECH_SELECTION) {
|
|
175
|
+
await exec("pnpm", pnpmInstallArgs(), { cwd: projectDir, verbose });
|
|
176
|
+
const runtime = runtimeDependencies(selection);
|
|
177
|
+
if (Object.keys(runtime).length > 0) {
|
|
178
|
+
await exec("pnpm", pnpmAddArgs(runtime), { cwd: projectDir, verbose });
|
|
179
|
+
}
|
|
180
|
+
const dev = devDependencies(framework, selection);
|
|
181
|
+
if (Object.keys(dev).length > 0) {
|
|
182
|
+
await exec("pnpm", pnpmAddDevArgs(dev), { cwd: projectDir, verbose });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export function pinnedPackages(framework, selection = FULL_TECH_SELECTION) {
|
|
186
|
+
const entries = [
|
|
187
|
+
...Object.entries(framework.pinnedDependencies()),
|
|
188
|
+
...Object.entries(framework.pinnedDevDependencies()),
|
|
189
|
+
...Object.entries(runtimeDependencies(selection)),
|
|
190
|
+
...Object.entries(devDependencies(framework, selection)),
|
|
191
|
+
];
|
|
192
|
+
return [...new Set(entries.map(([name, version]) => `${name}@${version}`))];
|
|
193
|
+
}
|
|
194
|
+
export function pnpmListJsonArgs() {
|
|
195
|
+
return ["list", "--depth", "Infinity", "--json"];
|
|
196
|
+
}
|
|
197
|
+
export async function refreshPnpmWorkspaceExcludes(projectDir, framework, verbose, selection = FULL_TECH_SELECTION) {
|
|
198
|
+
let locked = [];
|
|
199
|
+
try {
|
|
200
|
+
const result = await exec("pnpm", pnpmListJsonArgs(), {
|
|
201
|
+
cwd: projectDir,
|
|
202
|
+
verbose,
|
|
203
|
+
});
|
|
204
|
+
locked = collectLockedPackages(JSON.parse(result.stdout));
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
locked = [];
|
|
208
|
+
}
|
|
209
|
+
const workspacePath = joinPath(projectDir, "pnpm-workspace.yaml");
|
|
210
|
+
let existing = null;
|
|
211
|
+
try {
|
|
212
|
+
existing = await readTextFile(workspacePath);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
existing = null;
|
|
216
|
+
}
|
|
217
|
+
const excludes = [...pinnedPackages(framework, selection), ...locked];
|
|
218
|
+
await writeTextFile(workspacePath, mergePnpmWorkspaceYaml(existing, excludes));
|
|
219
|
+
}
|
|
220
|
+
export async function normalizePackageJson(projectDir) {
|
|
221
|
+
const packageJsonPath = joinPath(projectDir, "package.json");
|
|
222
|
+
const pkg = await readJsonFile(packageJsonPath);
|
|
223
|
+
const dependencies = pkg.dependencies ?? {};
|
|
224
|
+
const devDependenciesMap = pkg.devDependencies ?? {};
|
|
225
|
+
for (const map of [dependencies, devDependenciesMap]) {
|
|
226
|
+
for (const [key, version] of Object.entries(map)) {
|
|
227
|
+
map[key] = stripRangePrefix(version);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
pkg.dependencies = dependencies;
|
|
231
|
+
pkg.devDependencies = devDependenciesMap;
|
|
232
|
+
await writeTextFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
233
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { componentsJson, utilsTs } from "../config/components.js";
|
|
2
|
+
import { joinPath, writeTextFile } from "../utils/filesystem.js";
|
|
3
|
+
export async function configureShadcn(projectDir, framework) {
|
|
4
|
+
await writeTextFile(joinPath(projectDir, "components.json"), componentsJson(framework.componentsJsonOptions()));
|
|
5
|
+
await writeTextFile(joinPath(projectDir, "src", "lib", "utils.ts"), utilsTs());
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { smokeTest, vitestConfig } from "../config/testing.js";
|
|
2
|
+
import { joinPath, writeTextFile } from "../utils/filesystem.js";
|
|
3
|
+
export async function configureTesting(projectDir) {
|
|
4
|
+
await writeTextFile(joinPath(projectDir, "vitest.config.ts"), vitestConfig());
|
|
5
|
+
await writeTextFile(joinPath(projectDir, "src", "test", "smoke.test.tsx"), smokeTest());
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { vscodeExtensions, vscodeSettings } from "../config/vscode.js";
|
|
2
|
+
import { joinPath, writeTextFile } from "../utils/filesystem.js";
|
|
3
|
+
export async function configureVscode(projectDir) {
|
|
4
|
+
await writeTextFile(joinPath(projectDir, ".vscode", "settings.json"), vscodeSettings());
|
|
5
|
+
await writeTextFile(joinPath(projectDir, ".vscode", "extensions.json"), vscodeExtensions());
|
|
6
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { exec } from "../utils/exec.js";
|
|
2
|
+
export const INITIAL_COMMIT_MESSAGE = "chore: initial project setup";
|
|
3
|
+
export function addArgs() {
|
|
4
|
+
return ["add", "."];
|
|
5
|
+
}
|
|
6
|
+
export function commitArgs(message = INITIAL_COMMIT_MESSAGE) {
|
|
7
|
+
return ["commit", "-m", message];
|
|
8
|
+
}
|
|
9
|
+
export async function createCommit(projectDir, verbose) {
|
|
10
|
+
await exec("git", addArgs(), { cwd: projectDir, verbose });
|
|
11
|
+
await exec("git", commitArgs(), { cwd: projectDir, verbose });
|
|
12
|
+
}
|
package/dist/git/init.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { exec } from "../utils/exec.js";
|
|
2
|
+
export function initArgs() {
|
|
3
|
+
return ["init"];
|
|
4
|
+
}
|
|
5
|
+
export function branchArgs() {
|
|
6
|
+
return ["branch", "-M", "main"];
|
|
7
|
+
}
|
|
8
|
+
export async function initRepository(projectDir, verbose) {
|
|
9
|
+
await exec("git", initArgs(), { cwd: projectDir, verbose });
|
|
10
|
+
await exec("git", branchArgs(), { cwd: projectDir, verbose });
|
|
11
|
+
}
|