@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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/dist/cli/args.js +34 -0
  4. package/dist/cli/output.js +20 -0
  5. package/dist/cli/run.js +326 -0
  6. package/dist/config/agents.js +224 -0
  7. package/dist/config/biome.js +30 -0
  8. package/dist/config/branding.js +2 -0
  9. package/dist/config/commitlint.js +8 -0
  10. package/dist/config/components.js +36 -0
  11. package/dist/config/editorconfig.js +14 -0
  12. package/dist/config/husky.js +14 -0
  13. package/dist/config/npmrc.js +3 -0
  14. package/dist/config/nvmrc.js +4 -0
  15. package/dist/config/pnpm-workspace.js +85 -0
  16. package/dist/config/query.js +16 -0
  17. package/dist/config/tailwind.js +31 -0
  18. package/dist/config/tech.js +83 -0
  19. package/dist/config/testing.js +26 -0
  20. package/dist/config/vscode.js +39 -0
  21. package/dist/frameworks/index.js +9 -0
  22. package/dist/frameworks/next.js +115 -0
  23. package/dist/frameworks/types.js +1 -0
  24. package/dist/frameworks/vite.js +135 -0
  25. package/dist/generators/configure-biome.js +22 -0
  26. package/dist/generators/configure-git-hooks.js +11 -0
  27. package/dist/generators/configure-node.js +45 -0
  28. package/dist/generators/configure-project.js +233 -0
  29. package/dist/generators/configure-registry.js +5 -0
  30. package/dist/generators/configure-shadcn.js +6 -0
  31. package/dist/generators/configure-testing.js +6 -0
  32. package/dist/generators/configure-vscode.js +6 -0
  33. package/dist/generators/create-project.js +3 -0
  34. package/dist/git/commit.js +12 -0
  35. package/dist/git/init.js +11 -0
  36. package/dist/git/push.js +59 -0
  37. package/dist/git/remote.js +30 -0
  38. package/dist/index.js +16 -0
  39. package/dist/prompts/cancel.js +8 -0
  40. package/dist/prompts/confirm.js +11 -0
  41. package/dist/prompts/framework.js +25 -0
  42. package/dist/prompts/github.js +25 -0
  43. package/dist/prompts/project.js +22 -0
  44. package/dist/prompts/tech.js +18 -0
  45. package/dist/utils/exec.js +107 -0
  46. package/dist/utils/filesystem.js +108 -0
  47. package/dist/utils/registry.js +18 -0
  48. package/dist/utils/validation.js +153 -0
  49. package/package.json +68 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 raulmoracode
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,231 @@
1
+ # @raulmoracode/create
2
+
3
+ Professional CLI to scaffold modern React projects. Built with Node.js, TypeScript, [Clack](https://github.com/natemoo-re/clack) and pnpm.
4
+
5
+ - **Package:** [`@raulmoracode/create`](https://www.npmjs.com/package/@raulmoracode/create) (published to npmjs)
6
+ - **Repository:** [`raulmoracode/raulmoracode-create`](https://github.com/raulmoracode/raulmoracode-create)
7
+ - **Command:** `raulmoracode-create`
8
+
9
+ ## Requirements
10
+
11
+ - [Node.js](https://nodejs.org) 24 LTS
12
+ - [pnpm](https://pnpm.io/installation) (declared per project via `packageManager`)
13
+ - npm
14
+ - [Git](https://git-scm.com/downloads) with a configured identity (`user.name` and `user.email`)
15
+ - A GitHub account and an existing empty repository for the new project
16
+ - `GH_TOKEN` (optional): only needed to install the private `@raulmoracode/icons` package. Without registry access, the project is generated without `.npmrc` and without icons — everything else is identical.
17
+ - [VS Code](https://code.visualstudio.com) (optional, used to open the project at the end)
18
+
19
+ ## Install globally
20
+
21
+ The package is published to [npmjs](https://www.npmjs.com/package/@raulmoracode/create). No authentication needed.
22
+
23
+ ```bash
24
+ npm install -g @raulmoracode/create
25
+ ```
26
+
27
+ Verify:
28
+
29
+ ```bash
30
+ raulmoracode-create
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ```bash
36
+ raulmoracode-create
37
+ ```
38
+
39
+ The CLI guides you through the whole process with Clack prompts:
40
+
41
+ 1. Select the technology: **React + Vite** or **Next.js**.
42
+ 2. Select the technologies to include (everything preselected by default).
43
+ 3. Enter the project name.
44
+ 4. Enter the GitHub repository URL (the repository must already exist).
45
+ 5. The project is created with the official generators (`create-vite` / `create-next-app`) and fully configured.
46
+ 6. Git is initialized, the remote is configured, the initial commit is created and pushed to GitHub.
47
+ 7. Optionally open the project with VS Code.
48
+
49
+ Available flags:
50
+
51
+ ```bash
52
+ raulmoracode-create --verbose # print the output of every external command
53
+ raulmoracode-create --help # show help
54
+ raulmoracode-create --version # show the installed version
55
+ ```
56
+
57
+ ## What the CLI does
58
+
59
+ - Scaffolds with the **official generators** (never a hand-written template):
60
+ - React + Vite: `pnpm create vite@9.2.1 <name> --template react-ts` (the `create-vite` release line that scaffolds the Vite 8.x line; `create-vite@8.3.1` does not exist), then pins `vite@8.3.1`, `react@19.3.0` and `typescript@7.0.2`.
61
+ - Next.js: `pnpm create next-app@16.3.6 <name> --ts --app --src-dir --import-alias "@/*" --biome --use-pnpm --disable-git --yes`.
62
+ - Installs dependencies with **pnpm** and pins exact versions (no `^` or `~`).
63
+ - Configures **Tailwind CSS 4.3.3** (CSS-first configuration, `@tailwindcss/vite` for Vite and `@tailwindcss/postcss` for Next.js).
64
+ - Prepares **shadcn**: writes a complete `components.json` (including the `@raulmoracode` registry) and the `cn()` helper (`src/lib/utils.ts`). No components are preinstalled; add them later with `pnpm dlx shadcn@latest add <component>`.
65
+ - Configures the private **`@raulmoracode` registry** in `.npmrc` using `${GH_TOKEN}` (never a real token) — but only when registry access is detected; otherwise `.npmrc` and `@raulmoracode/icons` are skipped.
66
+ - Installs **Zustand 5.0.15**, **React Hook Form 7.89.0**, **Zod 4.6.5**, **TanStack Query 5.104.0** and **@raulmoracode/icons 1.1.0**, and wires the TanStack Query provider.
67
+ - Configures **Biome 2.5.14** (formatter, linter and organize imports) and removes the ESLint/Oxlint leftovers from the official templates.
68
+ - Configures **Vitest 5.0.2**, **@testing-library/react 16.3.3** and **@testing-library/dom 10.4.2** (plus `jsdom`, required by Testing Library, and `clsx` + `tailwind-merge`, required by the shadcn `cn()` helper).
69
+ - Configures **VS Code** (`.vscode/settings.json` and `.vscode/extensions.json`).
70
+ - Pins **Node.js 24** via `.nvmrc` and writes `.editorconfig`.
71
+ - Applies **branding**: tab title `raulmoracode` and favicon `https://cdn.raulmoracode.com/icons/favicon.ico` (in `index.html` for Vite, in the root layout metadata for Next.js).
72
+ - Ships a minimal **starter** for React + Vite: `public/` and `src/assets/` are emptied, `App.tsx` renders a simple hello and `App.css` starts empty.
73
+ - Ships a minimal **starter** for Next.js: `public/` is emptied and `page.tsx` renders a simple hello (its orphaned `page.module.css` is removed). `AGENTS.md` is kept and `CLAUDE.md` is removed.
74
+ - Fills in **project metadata** in `package.json`: `author` (Raul Mora, https://raulmoracode.com), `homepage` and `repository` with the GitHub URL you enter at the start.
75
+ - Writes **`pnpm-workspace.yaml`** with `minimumReleaseAge: 10080` plus `minimumReleaseAgeExclude` entries for every locked package, so installs keep working (pnpm enforces the policy against the whole lockfile, not just direct dependencies).
76
+ - Initializes Git on `main`, adds the remote, creates the commit `chore: initial project setup` and pushes with `git push -u origin main` (never `--force`). If the remote already contains commits that do not exist locally, the process stops with a clear message instead of overwriting anything.
77
+
78
+ ## Generated projects
79
+
80
+ Every generated project includes:
81
+
82
+ - React + Vite or Next.js (official structure)
83
+ - Tailwind CSS 4
84
+ - shadcn ready (`components.json` with the `@raulmoracode` registry and the `cn()` helper)
85
+ - `@raulmoracode` registry in `.npmrc` (`${GH_TOKEN}`, only with registry access)
86
+ - Zustand
87
+ - React Hook Form + Zod
88
+ - TanStack Query (provider included)
89
+ - Biome (format, lint, organize imports)
90
+ - Vitest + Testing Library
91
+ - VS Code settings
92
+ - Branding (`raulmoracode` tab title and CDN favicon)
93
+ - `pnpm-workspace.yaml` with `minimumReleaseAge: 10080`
94
+ - Node.js 24 (`.nvmrc`)
95
+ - pnpm (`pnpm-lock.yaml`, no `package-lock.json` or `yarn.lock`)
96
+ - Git initialized and pushed to GitHub
97
+
98
+ ### Commands in a generated project
99
+
100
+ ```bash
101
+ pnpm install
102
+ pnpm dev
103
+ pnpm build
104
+ pnpm test
105
+ pnpm check
106
+ pnpm format
107
+ pnpm lint
108
+ ```
109
+
110
+ Add shadcn components (including the private registry):
111
+
112
+ ```bash
113
+ pnpm dlx shadcn@latest add card
114
+ pnpm dlx shadcn@latest add @raulmoracode/<component>
115
+ ```
116
+
117
+ After adding components, normalize their style with Biome (the shadcn CLI uses its own formatting):
118
+
119
+ ```bash
120
+ pnpm exec biome check --write .
121
+ ```
122
+
123
+ Vite projects resolve the `@/*` alias to `src/*` (`tsconfig.app.json`, root `tsconfig.json` and `vite.config.ts`).
124
+
125
+ Every generated project ships an `AGENTS.md` file with guidelines for AI coding agents (source of truth, conventions, validation, Git safety and security rules), plus a project tooling section (pnpm exclusively, script map and the shadcn workflow).
126
+
127
+ ## GitHub authentication
128
+
129
+ The CLI runs `git push -u origin main`, so Git must be authenticated against GitHub. Any of these work:
130
+
131
+ - [GitHub CLI](https://cli.github.com): `gh auth login`
132
+ - A credential manager / SSH key configured for `github.com`
133
+ - A `GH_TOKEN` / `GITHUB_TOKEN` environment variable together with a credential helper
134
+
135
+ ### Note on `GH_TOKEN` and pnpm >= 11.5.3
136
+
137
+ The generated `.npmrc` references `${GH_TOKEN}` as mandated. However, since **pnpm 11.5.3** environment variables are **not** expanded in project-level `.npmrc` credential values (a security fix for [GHSA-3qhv-2rgh-x77r](https://github.com/pnpm/security/advisories/GHSA-3qhv-2rgh-x77r)): pnpm prints a warning and ignores that line, while the registry mapping (`@raulmoracode:registry=...`) still applies. To authenticate installs from GitHub Packages, keep your token in a **user-level** location where expansion works:
138
+
139
+ ```bash
140
+ pnpm config set //npm.pkg.github.com/:_authToken "$GH_TOKEN"
141
+ ```
142
+
143
+ or in `~/.npmrc`:
144
+
145
+ ```ini
146
+ //npm.pkg.github.com/:_authToken=${GH_TOKEN}
147
+ ```
148
+
149
+ ## npm
150
+
151
+ ### Install
152
+
153
+ ```bash
154
+ npm install -g @raulmoracode/create
155
+ ```
156
+
157
+ ### Update the global installation
158
+
159
+ ```bash
160
+ npm install -g @raulmoracode/create
161
+ ```
162
+
163
+ ### Publish (maintainers)
164
+
165
+ ```bash
166
+ pnpm install
167
+ pnpm build
168
+ pnpm test
169
+ npm publish --access public
170
+ ```
171
+
172
+ Create the `NPM_TOKEN` (granular, read+write on `@raulmoracode/create`) at `npmjs.com > Access Tokens` and save it as `Settings > Secrets > Actions > NPM_TOKEN`.
173
+
174
+ The repository also ships a GitHub Actions workflow (`.github/workflows/publish.yml`) that builds, tests and publishes the package to npmjs on every `v*` tag using `NPM_TOKEN`.
175
+
176
+ ## Development
177
+
178
+ ```bash
179
+ pnpm install
180
+ pnpm build
181
+ pnpm test
182
+ ```
183
+
184
+ Other useful scripts:
185
+
186
+ ```bash
187
+ pnpm check # biome check .
188
+ pnpm format # biome format --write .
189
+ pnpm lint # biome lint .
190
+ ```
191
+
192
+ ### Architecture
193
+
194
+ ```text
195
+ src/
196
+ ├── index.ts # entry point (shebang)
197
+ ├── cli/ # orchestration and Clack output
198
+ ├── prompts/ # user interaction (Clack)
199
+ ├── generators/ # project configuration steps
200
+ ├── frameworks/ # vite / next specific logic
201
+ ├── git/ # git command builders and wrappers
202
+ ├── config/ # configuration file templates
203
+ └── utils/ # exec, filesystem, validation
204
+ tests/ # vitest suite (external commands are mocked)
205
+ ```
206
+
207
+ External commands are always executed with argument arrays through `utils/exec.ts` (never string interpolation), and every user input is validated before use.
208
+
209
+ ### Verified adjustments
210
+
211
+ The following deviations from the naive expected configuration were verified against the real tools and are intentional:
212
+
213
+ - **`create-vite@9.2.1`** is used for Vite projects because `create-vite@8.3.1` does not exist; the 9.x line is the one that scaffolds the Vite 8.x line. `vite` is then pinned to `8.3.1` exactly.
214
+ - **`components.json`** must contain the full shadcn configuration (`$schema`, `style`, `tailwind`, `rsc`, `tsx`, `aliases`) because shadcn 4.x rejects a config with only `registries` — and `pnpm dlx shadcn@latest add card` is a hard requirement. The `@raulmoracode` registry entry is exactly the mandated one.
215
+ - **`biome.json`** enables organize imports through `assist.actions.source.organizeImports` because Biome 2.5.14 removed the top-level `organizeImports` key; it ignores `dist`/`.next` build output; and disables `noSvgWithoutTitle`/`noAmbiguousAnchorText`, which fire on the official Vite/Next template demo assets.
216
+ - **`jsdom`** (Testing Library DOM environment) and **`clsx` + `tailwind-merge`** (shadcn `cn()` helper) are installed because they are strictly required by the mandated Testing Library and shadcn setups.
217
+
218
+ ## Packaging and local verification
219
+
220
+ ```bash
221
+ pnpm build
222
+ npm pack
223
+ npm install -g ./raulmoracode-create-1.0.0.tgz
224
+ raulmoracode-create
225
+ ```
226
+
227
+ The packaged tarball contains `dist/` (including the `raulmoracode-create` bin with its shebang), `README.md` and `LICENSE`.
228
+
229
+ ## License
230
+
231
+ [MIT](LICENSE)
@@ -0,0 +1,34 @@
1
+ export const VERSION = "1.0.0";
2
+ export function parseArgs(argv) {
3
+ return {
4
+ help: argv.includes("--help") || argv.includes("-h"),
5
+ version: argv.includes("--version") || argv.includes("-V"),
6
+ verbose: argv.includes("--verbose"),
7
+ };
8
+ }
9
+ export function helpText() {
10
+ return [
11
+ `raulmoracode-create v${VERSION} — scaffolds React + Vite / Next.js projects.`,
12
+ "",
13
+ "Usage:",
14
+ " raulmoracode-create [options]",
15
+ "",
16
+ "Options:",
17
+ " --verbose Print the output of every external command.",
18
+ " -h, --help Show this help message.",
19
+ " -V, --version Show the installed version.",
20
+ "",
21
+ "Environment:",
22
+ " GH_TOKEN (optional) Enables the private @raulmoracode/icons package.",
23
+ " Without registry access the project is generated",
24
+ " without .npmrc and without icons.",
25
+ "",
26
+ "Examples:",
27
+ " raulmoracode-create",
28
+ " raulmoracode-create --verbose",
29
+ "",
30
+ ].join("\n");
31
+ }
32
+ export function printHelp() {
33
+ console.log(helpText());
34
+ }
@@ -0,0 +1,20 @@
1
+ import { intro, log, outro } from "@clack/prompts";
2
+ export function showIntro() {
3
+ intro("Raulmoracode Create");
4
+ }
5
+ export function showError(message) {
6
+ log.error(message);
7
+ }
8
+ export function showWarning(message) {
9
+ log.warn(message);
10
+ }
11
+ export function showSummary(params) {
12
+ const { projectName, githubUrl, frameworkLabel } = params;
13
+ log.success("Project created successfully!");
14
+ log.message(`Framework: ${frameworkLabel}`);
15
+ log.message(`Local: ./${projectName}`);
16
+ log.message(`GitHub: ${githubUrl}`);
17
+ }
18
+ export function showFarewell() {
19
+ outro("Proyecto creado correctamente.\n¡Hasta pronto!");
20
+ }
@@ -0,0 +1,326 @@
1
+ import { intro, log, tasks } from "@clack/prompts";
2
+ import { applyPrivateAccess } from "../config/tech.js";
3
+ import { getFramework } from "../frameworks/index.js";
4
+ import { configureBiome, formatProject, } from "../generators/configure-biome.js";
5
+ import { configureGitHooks } from "../generators/configure-git-hooks.js";
6
+ import { augmentGitignore, configureEditorconfig, configureNode, } from "../generators/configure-node.js";
7
+ import { installDependencies, normalizePackageJson, patchPackageJson, refreshPnpmWorkspaceExcludes, } from "../generators/configure-project.js";
8
+ import { configureRegistry } from "../generators/configure-registry.js";
9
+ import { configureShadcn } from "../generators/configure-shadcn.js";
10
+ import { configureTesting } from "../generators/configure-testing.js";
11
+ import { configureVscode } from "../generators/configure-vscode.js";
12
+ import { createProject } from "../generators/create-project.js";
13
+ import { createCommit } from "../git/commit.js";
14
+ import { initRepository } from "../git/init.js";
15
+ import { push, remoteHasDivergentCommits } from "../git/push.js";
16
+ import { addRemote, lsRemote } from "../git/remote.js";
17
+ import { promptOpenInVscode } from "../prompts/confirm.js";
18
+ import { promptFramework } from "../prompts/framework.js";
19
+ import { promptGitHubUrl } from "../prompts/github.js";
20
+ import { promptProjectName } from "../prompts/project.js";
21
+ import { promptTechPreset } from "../prompts/tech.js";
22
+ import { ExecError, exec } from "../utils/exec.js";
23
+ import { ensureDir, isDirectoryEmpty, pathExists, removeEmptyDir, removeIfExists, resolvePath, } from "../utils/filesystem.js";
24
+ import { checkPrivateRegistryAccess } from "../utils/registry.js";
25
+ import { satisfiesNodeVersion } from "../utils/validation.js";
26
+ import { showFarewell, showSummary, showWarning } from "./output.js";
27
+ const MINIMUM_NODE_MAJOR = 24;
28
+ export const REMOTE_CONFLICT_MESSAGE = [
29
+ "El repositorio remoto ya contiene cambios que no existen localmente.",
30
+ "",
31
+ "No se realizará ningún push destructivo.",
32
+ "Por favor, revisa el repositorio y vuelve a ejecutar el proceso.",
33
+ ].join("\n");
34
+ export class PreflightError extends Error {
35
+ }
36
+ /**
37
+ * Decides whether the generated directory must be removed after a failure.
38
+ *
39
+ * Past `checkDestination`, everything under `./<name>` was created by this run
40
+ * (the destination either did not exist or was an empty dir we removed), so it
41
+ * is safe to delete. Two exceptions:
42
+ * - `null` (failure before any task ran): nothing was created, nothing to clean.
43
+ * - `"Pushing to GitHub"`: the project is complete locally, only the push
44
+ * failed — deleting it would destroy a perfectly good project.
45
+ */
46
+ export function shouldRemoveProjectDir(failedStep) {
47
+ if (failedStep === null) {
48
+ return false;
49
+ }
50
+ if (failedStep === "Pushing to GitHub") {
51
+ return false;
52
+ }
53
+ return true;
54
+ }
55
+ async function requireCommand(command, instructions, verbose) {
56
+ try {
57
+ await exec(command, ["--version"], { verbose });
58
+ }
59
+ catch (error) {
60
+ if (error instanceof ExecError && error.spawnError) {
61
+ throw new PreflightError(`${command} no está instalado o no disponible en el PATH.\n${instructions}`);
62
+ }
63
+ throw new PreflightError(`No se pudo ejecutar "${command} --version".`);
64
+ }
65
+ }
66
+ async function requireGitIdentity(verbose) {
67
+ const name = await exec("git", ["config", "--get", "user.name"], { verbose });
68
+ const email = await exec("git", ["config", "--get", "user.email"], {
69
+ verbose,
70
+ });
71
+ if (!name.stdout.trim() || !email.stdout.trim()) {
72
+ throw new PreflightError("Git no tiene configurada la identidad (user.name y user.email), necesaria para crear el commit inicial.\n" +
73
+ 'Ejecuta: git config --global user.name "Tu Nombre" && git config --global user.email "tu@email.com"');
74
+ }
75
+ }
76
+ async function preflightChecks(verbose) {
77
+ const nodeCheck = satisfiesNodeVersion(process.version, MINIMUM_NODE_MAJOR);
78
+ if (!nodeCheck.valid) {
79
+ throw new PreflightError(`${nodeCheck.error}\nInstala Node.js ${MINIMUM_NODE_MAJOR} LTS: https://nodejs.org`);
80
+ }
81
+ await requireCommand("pnpm", "Instala pnpm: https://pnpm.io/installation", verbose);
82
+ await requireCommand("git", "Instala Git: https://git-scm.com/downloads", verbose);
83
+ await requireGitIdentity(verbose);
84
+ }
85
+ async function checkDestination(projectName) {
86
+ const projectDir = resolvePath(process.cwd(), projectName);
87
+ if (await pathExists(projectDir)) {
88
+ if (await isDirectoryEmpty(projectDir)) {
89
+ await removeEmptyDir(projectDir);
90
+ return { projectDir, reusedEmptyDir: true };
91
+ }
92
+ throw new PreflightError(`El directorio "./${projectName}" ya existe y no está vacío.\n` +
93
+ "Muévalo, renómbralo o elimínalo antes de continuar. No se borrará ningún contenido existente.");
94
+ }
95
+ return { projectDir, reusedEmptyDir: false };
96
+ }
97
+ async function checkRemoteAccess(githubUrl, verbose) {
98
+ try {
99
+ await lsRemote(githubUrl, verbose);
100
+ }
101
+ catch {
102
+ throw new PreflightError(`No se pudo acceder al repositorio ${githubUrl}.\n` +
103
+ "Comprueba la URL, tu conexión a internet y tu autenticación de GitHub (https o gh auth).");
104
+ }
105
+ }
106
+ export async function run(options = {}) {
107
+ const verbose = options.verbose ?? false;
108
+ intro("Raulmoracode Create");
109
+ let projectName = "";
110
+ let cleanupDir = null;
111
+ let restoreEmptyDir = false;
112
+ let failedStep = null;
113
+ try {
114
+ await preflightChecks(verbose);
115
+ const frameworkId = await promptFramework();
116
+ const { selection, notes } = await promptTechPreset();
117
+ for (const note of notes) {
118
+ showWarning(note);
119
+ }
120
+ projectName = await promptProjectName();
121
+ const githubUrl = await promptGitHubUrl();
122
+ const { projectDir, reusedEmptyDir } = await checkDestination(projectName);
123
+ cleanupDir = projectDir;
124
+ restoreEmptyDir = reusedEmptyDir;
125
+ await checkRemoteAccess(githubUrl, verbose);
126
+ const hasPrivateAccess = await checkPrivateRegistryAccess();
127
+ const effectiveSelection = applyPrivateAccess(selection, hasPrivateAccess);
128
+ if (selection.registry && !hasPrivateAccess) {
129
+ showWarning("Sin acceso al registro privado @raulmoracode (GH_TOKEN ausente o sin permisos): " +
130
+ "se omite @raulmoracode/icons y el .npmrc.");
131
+ }
132
+ const framework = getFramework(frameworkId);
133
+ await tasks([
134
+ {
135
+ title: "Creating project",
136
+ task: async (message) => {
137
+ failedStep = "Creating project";
138
+ message(`Scaffolding with ${framework.label}`);
139
+ await createProject(framework, projectName, process.cwd(), verbose);
140
+ if (effectiveSelection.tailwind) {
141
+ message("Configuring Tailwind CSS");
142
+ await framework.configureTailwind(projectDir);
143
+ }
144
+ message("Configuring branding");
145
+ await framework.configureBranding(projectDir);
146
+ if (effectiveSelection.shadcn) {
147
+ message("Configuring shadcn");
148
+ await configureShadcn(projectDir, framework);
149
+ }
150
+ if (effectiveSelection.registry) {
151
+ message("Configuring @raulmoracode registry");
152
+ await configureRegistry(projectDir);
153
+ }
154
+ if (effectiveSelection["tanstack-query"]) {
155
+ message("Configuring TanStack Query");
156
+ await framework.configureTanStackQuery(projectDir);
157
+ }
158
+ if (framework.configureStarter) {
159
+ message("Configuring starter");
160
+ await framework.configureStarter(projectDir);
161
+ }
162
+ if (effectiveSelection.biome) {
163
+ message("Configuring Biome");
164
+ await configureBiome(projectDir);
165
+ }
166
+ if (effectiveSelection.testing) {
167
+ message("Configuring testing");
168
+ await configureTesting(projectDir);
169
+ }
170
+ if (effectiveSelection.vscode) {
171
+ message("Configuring VS Code");
172
+ await configureVscode(projectDir);
173
+ }
174
+ message("Configuring Node version");
175
+ await configureNode(projectDir);
176
+ await configureEditorconfig(projectDir);
177
+ if (effectiveSelection.husky) {
178
+ message("Configuring Git hooks");
179
+ await configureGitHooks(projectDir, effectiveSelection);
180
+ }
181
+ await patchPackageJson(projectDir, framework, projectName, githubUrl, effectiveSelection);
182
+ await augmentGitignore(projectDir);
183
+ return "Project created";
184
+ },
185
+ },
186
+ {
187
+ title: "Installing dependencies",
188
+ task: async (message) => {
189
+ failedStep = "Installing dependencies";
190
+ message("Installing base and additional dependencies");
191
+ await installDependencies(projectDir, framework, verbose, effectiveSelection);
192
+ await normalizePackageJson(projectDir);
193
+ if (effectiveSelection.biome) {
194
+ message("Formatting project with Biome");
195
+ await formatProject(projectDir, verbose);
196
+ }
197
+ message("Configuring pnpm workspace");
198
+ await refreshPnpmWorkspaceExcludes(projectDir, framework, verbose, effectiveSelection);
199
+ if (effectiveSelection.zustand) {
200
+ message("Zustand configured");
201
+ }
202
+ if (effectiveSelection.forms) {
203
+ message("React Hook Form + Zod configured");
204
+ }
205
+ if (effectiveSelection["tanstack-query"]) {
206
+ message("TanStack Query configured");
207
+ }
208
+ if (effectiveSelection.tailwind) {
209
+ message("Tailwind configured");
210
+ }
211
+ if (effectiveSelection.biome) {
212
+ message("Biome configured");
213
+ }
214
+ if (effectiveSelection.testing) {
215
+ message("Testing configured");
216
+ }
217
+ return "Dependencies installed";
218
+ },
219
+ },
220
+ {
221
+ title: "Initializing Git",
222
+ task: async () => {
223
+ failedStep = "Initializing Git";
224
+ await initRepository(projectDir, verbose);
225
+ if (effectiveSelection.husky) {
226
+ await exec("pnpm", ["exec", "husky"], {
227
+ cwd: projectDir,
228
+ verbose,
229
+ });
230
+ }
231
+ return "Git initialized";
232
+ },
233
+ },
234
+ {
235
+ title: "Configuring remote",
236
+ task: async () => {
237
+ failedStep = "Configuring remote";
238
+ await addRemote(projectDir, githubUrl, verbose);
239
+ return "Remote configured";
240
+ },
241
+ },
242
+ {
243
+ title: "Creating initial commit",
244
+ task: async () => {
245
+ failedStep = "Creating initial commit";
246
+ await createCommit(projectDir, verbose);
247
+ return "Initial commit created";
248
+ },
249
+ },
250
+ {
251
+ title: "Pushing to GitHub",
252
+ task: async () => {
253
+ failedStep = "Pushing to GitHub";
254
+ const hasConflicts = await remoteHasDivergentCommits(projectDir, verbose);
255
+ if (hasConflicts) {
256
+ throw new Error(REMOTE_CONFLICT_MESSAGE);
257
+ }
258
+ await push(projectDir, verbose);
259
+ return "Pushed to GitHub";
260
+ },
261
+ },
262
+ ]);
263
+ showSummary({ projectName, githubUrl, frameworkLabel: framework.label });
264
+ const openInVscode = await promptOpenInVscode();
265
+ if (openInVscode) {
266
+ try {
267
+ await exec("code", ["."], { cwd: projectDir, verbose });
268
+ }
269
+ catch (error) {
270
+ if (error instanceof ExecError && error.spawnError) {
271
+ showWarning('El comando "code" no está disponible. Abre el proyecto manualmente con VS Code.');
272
+ }
273
+ else {
274
+ showWarning("No se pudo abrir VS Code automáticamente. Abre el proyecto manualmente.");
275
+ }
276
+ }
277
+ }
278
+ showFarewell();
279
+ }
280
+ catch (error) {
281
+ const cancelled = error instanceof ExecError &&
282
+ (error.signal === "SIGINT" || error.signal === "SIGTERM");
283
+ let cleaned = false;
284
+ if (shouldRemoveProjectDir(failedStep) && cleanupDir !== null) {
285
+ try {
286
+ await removeIfExists(cleanupDir);
287
+ cleaned = true;
288
+ }
289
+ catch {
290
+ cleaned = false;
291
+ }
292
+ }
293
+ else if (failedStep === null && restoreEmptyDir && cleanupDir !== null) {
294
+ try {
295
+ await ensureDir(cleanupDir);
296
+ }
297
+ catch {
298
+ // Best effort: restoring an empty dir must never hide the real error.
299
+ }
300
+ }
301
+ if (cancelled) {
302
+ if (cleaned) {
303
+ log.error(`Operación cancelada. Se eliminó "./${projectName}" (todo su contenido lo había creado el CLI).`);
304
+ }
305
+ else {
306
+ log.error("Operación cancelada por el usuario.");
307
+ }
308
+ process.exit(130);
309
+ }
310
+ if (error instanceof Error) {
311
+ log.error(error.message);
312
+ }
313
+ else {
314
+ log.error(String(error));
315
+ }
316
+ if (cleaned) {
317
+ showWarning(`Se eliminó "./${projectName}" (todo su contenido lo había creado el CLI). ` +
318
+ "Vuelve a ejecutar raulmoracode-create para reintentar.");
319
+ }
320
+ else if (failedStep === "Pushing to GitHub") {
321
+ showWarning(`El proyecto está completo en "./${projectName}": solo falló el push. ` +
322
+ `Reintenta con: git -C "./${projectName}" push -u origin main`);
323
+ }
324
+ process.exit(1);
325
+ }
326
+ }