genrex 0.0.0-stage → 0.1.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/README.md +147 -2
- package/dist/src/blueprints.js +29 -0
- package/dist/src/ci.js +31 -0
- package/dist/src/config.js +23 -0
- package/dist/src/create.js +108 -0
- package/dist/src/docker.js +57 -0
- package/dist/src/doctor.js +60 -0
- package/dist/src/generator.js +140 -0
- package/dist/src/generators/node-api.js +144 -0
- package/dist/src/generators/ts-lib.js +142 -0
- package/dist/src/git.js +23 -0
- package/dist/src/index.js +131 -0
- package/dist/src/pm.js +10 -0
- package/package.json +40 -4
package/README.md
CHANGED
|
@@ -1,3 +1,148 @@
|
|
|
1
|
-
#
|
|
1
|
+
# genrex
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
|
|
5
|
+
<img src="https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js" />
|
|
6
|
+
<img src="https://img.shields.io/badge/Biome-60A5FA?style=for-the-badge&logo=biome&logoColor=white" alt="Biome" />
|
|
7
|
+
<img src="https://img.shields.io/badge/Vitest-6E9F18?style=for-the-badge&logo=vitest&logoColor=white" alt="Vitest" />
|
|
8
|
+
<img src="https://img.shields.io/badge/Fastify-000000?style=for-the-badge&logo=fastify&logoColor=white" alt="Fastify" />
|
|
9
|
+
<img src="https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white" alt="Docker" />
|
|
10
|
+
<img src="https://img.shields.io/badge/GitHub_Actions-2088FF?style=for-the-badge&logo=github-actions&logoColor=white" alt="GitHub Actions" />
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<img src="https://img.shields.io/badge/version-0.1.0-blue?style=flat-square" alt="Version" />
|
|
15
|
+
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License" />
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
> **Forge production-ready projects from configurable blueprints.**
|
|
19
|
+
|
|
20
|
+
`genrex` is a modern, lightweight project scaffolding CLI engine. Rather than copying generic boilerplates, it generates strongly-typed, coherent software projects with Biome, Vitest, CI/CD, and Docker support tailored to your stack.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Tech Stack & Ecosystem
|
|
25
|
+
|
|
26
|
+
- **Language & Runtime:** TypeScript (Strict, Native ESM), Node.js (>=20)
|
|
27
|
+
- **Code Quality & Testing:** Biome (Format/Lint), Vitest
|
|
28
|
+
- **API Framework:** Fastify + `tsx` hot-reload
|
|
29
|
+
- **DevOps & Containers:** Multi-stage Docker, GitHub Actions CI
|
|
30
|
+
- **Package Managers:** `pnpm`, `npm`, `yarn`, `bun`
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
Run directly with `npx`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx genrex create my-api --blueprint node-api
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Preview what will be generated without writing to disk:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx genrex create my-cli --dry-run
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Blueprints
|
|
51
|
+
|
|
52
|
+
| Blueprint | ID | Description |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| **TypeScript CLI** | `ts-cli` | Standalone CLI binary, ESM, Biome formatting, Vitest |
|
|
55
|
+
| **Node.js API** | `node-api` | Fastify REST API, hot-reload (`tsx`), healthchecks |
|
|
56
|
+
| **TypeScript Library** | `ts-lib` | Publishable library with dual ESM exports and `.d.ts` declaration maps |
|
|
57
|
+
|
|
58
|
+
View all blueprints anytime:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
genrex list
|
|
62
|
+
genrex info ts-cli
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Commands & Usage
|
|
68
|
+
|
|
69
|
+
### 1. Create a Project
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
genrex create <project-name> [options]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Options:**
|
|
76
|
+
|
|
77
|
+
| Flag | Description | Default |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `--blueprint <id>` | Blueprint template (`ts-cli`, `node-api`, `ts-lib`) | `ts-cli` |
|
|
80
|
+
| `--package-manager <pm>` | `npm`, `pnpm`, `yarn`, or `bun` | `pnpm` |
|
|
81
|
+
| `--git` | Initialize empty Git repository | `false` |
|
|
82
|
+
| `--github-actions` | Generate GitHub Actions CI test/lint workflow | `false` |
|
|
83
|
+
| `--docker` | Generate optimized multi-stage Dockerfile | `false` |
|
|
84
|
+
| `--dry-run` | Preview file tree and dependencies without writing files | `false` |
|
|
85
|
+
| `--skip-install` | Scaffold files without running package installation | `false` |
|
|
86
|
+
|
|
87
|
+
**Full-fledged Example:**
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
genrex create billing-service \
|
|
91
|
+
--blueprint node-api \
|
|
92
|
+
--package-manager pnpm \
|
|
93
|
+
--git \
|
|
94
|
+
--github-actions \
|
|
95
|
+
--docker
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### 2. Environment Doctor
|
|
101
|
+
|
|
102
|
+
Diagnose your environment tools in one command:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
genrex doctor
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
genrex Doctor
|
|
110
|
+
|
|
111
|
+
Environment
|
|
112
|
+
────────────────────────────────────────
|
|
113
|
+
✓ Node.js v22.20.0
|
|
114
|
+
✓ npm 10.9.3
|
|
115
|
+
✓ Git git version 2.49.0
|
|
116
|
+
○ pnpm not found (optional)
|
|
117
|
+
○ yarn not found (optional)
|
|
118
|
+
○ bun not found (optional)
|
|
119
|
+
|
|
120
|
+
Environment is ready.
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Design Principles
|
|
126
|
+
|
|
127
|
+
- **Zero Unnecessary Bloat:** Fast execution with near-zero runtime dependencies.
|
|
128
|
+
- **Fail Safely:** Refuses to overwrite non-empty directories; pre-validates all input and tools before modifying the disk.
|
|
129
|
+
- **Modern Standards:** Native ESM, strict TypeScript, Biome linter/formatter, and Vitest by default.
|
|
130
|
+
- **Cross-Platform:** Works seamlessly on Windows, macOS, and Linux.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
git clone https://github.com/waeterr/genrex.git
|
|
138
|
+
cd genrex
|
|
139
|
+
npm install
|
|
140
|
+
npm test
|
|
141
|
+
npm run build
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT © [waeterr](https://github.com/waeterr)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const blueprints = [
|
|
2
|
+
{
|
|
3
|
+
id: "ts-cli",
|
|
4
|
+
name: "TypeScript CLI",
|
|
5
|
+
version: "1.0.0",
|
|
6
|
+
description: "Production-ready TypeScript CLI tool.",
|
|
7
|
+
language: "TypeScript",
|
|
8
|
+
capabilities: ["testing", "formatting", "git", "github-actions", "docker"],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: "node-api",
|
|
12
|
+
name: "Node.js API",
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
description: "Node.js REST API with routing and healthchecks.",
|
|
15
|
+
language: "TypeScript",
|
|
16
|
+
capabilities: ["testing", "formatting", "git", "github-actions", "docker"],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "ts-lib",
|
|
20
|
+
name: "TypeScript Library",
|
|
21
|
+
version: "1.0.0",
|
|
22
|
+
description: "Publishable TypeScript library blueprint.",
|
|
23
|
+
language: "TypeScript",
|
|
24
|
+
capabilities: ["testing", "formatting", "git", "github-actions"],
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
export function getBlueprint(id) {
|
|
28
|
+
return blueprints.find((b) => b.id === id);
|
|
29
|
+
}
|
package/dist/src/ci.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function generateCiWorkflow(pm) {
|
|
2
|
+
const steps = [
|
|
3
|
+
" - uses: actions/checkout@v4",
|
|
4
|
+
];
|
|
5
|
+
if (pm === "pnpm") {
|
|
6
|
+
steps.push(" - uses: pnpm/action-setup@v4\n with:\n version: 9", " - uses: actions/setup-node@v4\n with:\n node-version: 22\n cache: 'pnpm'", " - run: pnpm install", " - run: pnpm run lint", " - run: pnpm test");
|
|
7
|
+
}
|
|
8
|
+
else if (pm === "bun") {
|
|
9
|
+
steps.push(" - uses: oven-sh/setup-bun@v2", " - run: bun install", " - run: bun run lint", " - run: bun test");
|
|
10
|
+
}
|
|
11
|
+
else if (pm === "yarn") {
|
|
12
|
+
steps.push(" - uses: actions/setup-node@v4\n with:\n node-version: 22\n cache: 'yarn'", " - run: yarn install", " - run: yarn run lint", " - run: yarn test");
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
steps.push(" - uses: actions/setup-node@v4\n with:\n node-version: 22\n cache: 'npm'", " - run: npm install", " - run: npm run lint", " - run: npm test");
|
|
16
|
+
}
|
|
17
|
+
return `name: CI
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
push:
|
|
21
|
+
branches: [main, master]
|
|
22
|
+
pull_request:
|
|
23
|
+
branches: [main, master]
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
test:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
${steps.join("\n")}
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function validateProjectName(name) {
|
|
2
|
+
if (!name || name.trim() === "") {
|
|
3
|
+
return "Project name cannot be empty";
|
|
4
|
+
}
|
|
5
|
+
if (!/^[a-z0-9-_@/]+$/.test(name)) {
|
|
6
|
+
return "Project name must contain only lowercase letters, numbers, hyphens, underscores, or scopes";
|
|
7
|
+
}
|
|
8
|
+
if (name.startsWith("-") || name.startsWith("_")) {
|
|
9
|
+
return "Project name cannot start with - or _";
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
export function validateConfig(config) {
|
|
14
|
+
const errors = [];
|
|
15
|
+
const nameError = validateProjectName(config.name);
|
|
16
|
+
if (nameError) {
|
|
17
|
+
errors.push(nameError);
|
|
18
|
+
}
|
|
19
|
+
if (!config.blueprint) {
|
|
20
|
+
errors.push("Blueprint is required");
|
|
21
|
+
}
|
|
22
|
+
return errors;
|
|
23
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { basename, resolve } from "node:path";
|
|
2
|
+
import { getBlueprint } from "./blueprints.js";
|
|
3
|
+
import { validateConfig, } from "./config.js";
|
|
4
|
+
import { buildTsCliFiles, prepareDestination, writeProjectFiles, } from "./generator.js";
|
|
5
|
+
import { buildNodeApiFiles } from "./generators/node-api.js";
|
|
6
|
+
import { buildTsLibFiles } from "./generators/ts-lib.js";
|
|
7
|
+
import { checkGit, initGit } from "./git.js";
|
|
8
|
+
import { installDependencies } from "./pm.js";
|
|
9
|
+
export function createProject(name, options) {
|
|
10
|
+
if (!name) {
|
|
11
|
+
fail("Project name is required. Example: genrex create my-project");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const config = {
|
|
15
|
+
name: basename(name),
|
|
16
|
+
destination: resolve(name),
|
|
17
|
+
blueprint: options.blueprint ?? "ts-cli",
|
|
18
|
+
packageManager: options.packageManager ?? "pnpm",
|
|
19
|
+
testing: "vitest",
|
|
20
|
+
formatter: "biome",
|
|
21
|
+
git: options.git,
|
|
22
|
+
githubActions: options.githubActions,
|
|
23
|
+
docker: options.docker,
|
|
24
|
+
};
|
|
25
|
+
const errors = validateConfig(config);
|
|
26
|
+
const blueprint = getBlueprint(config.blueprint);
|
|
27
|
+
if (!blueprint) {
|
|
28
|
+
errors.push(`Blueprint '${config.blueprint}' not found`);
|
|
29
|
+
}
|
|
30
|
+
if (errors.length > 0 || !blueprint) {
|
|
31
|
+
fail(`Invalid configuration:\n${errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let files;
|
|
35
|
+
if (config.blueprint === "ts-cli") {
|
|
36
|
+
files = buildTsCliFiles(config);
|
|
37
|
+
}
|
|
38
|
+
else if (config.blueprint === "node-api") {
|
|
39
|
+
files = buildNodeApiFiles(config);
|
|
40
|
+
}
|
|
41
|
+
else if (config.blueprint === "ts-lib") {
|
|
42
|
+
files = buildTsLibFiles(config);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
fail(`Generation for blueprint '${config.blueprint}' is not implemented yet. Supported: ts-cli, node-api, ts-lib.`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (options.git) {
|
|
49
|
+
const gitStatus = checkGit();
|
|
50
|
+
if (!gitStatus.available) {
|
|
51
|
+
fail("Git was requested (--git), but 'git' is not installed or not in PATH.");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (options.dryRun) {
|
|
56
|
+
console.log("DRY RUN\n");
|
|
57
|
+
console.log(`Blueprint: ${blueprint.name}`);
|
|
58
|
+
console.log(`Destination: ${config.destination}`);
|
|
59
|
+
console.log(`Package manager: ${config.packageManager}`);
|
|
60
|
+
console.log(`Git: ${options.git ? "yes" : "no"}`);
|
|
61
|
+
console.log(`GitHub Actions: ${options.githubActions ? "yes" : "no"}`);
|
|
62
|
+
console.log(`Docker: ${options.docker ? "yes" : "no"}`);
|
|
63
|
+
console.log("\nWould create:");
|
|
64
|
+
for (const f of files) {
|
|
65
|
+
console.log(` - ${f.path}`);
|
|
66
|
+
}
|
|
67
|
+
console.log("\nWould install:");
|
|
68
|
+
console.log(" devDependencies: typescript, vitest, @biomejs/biome, @types/node");
|
|
69
|
+
if (options.git) {
|
|
70
|
+
console.log("\nWould execute:");
|
|
71
|
+
console.log(" git init");
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
prepareDestination(config.destination);
|
|
77
|
+
writeProjectFiles(config.destination, files);
|
|
78
|
+
console.log(`✓ Project scaffolded in ${config.destination}`);
|
|
79
|
+
if (options.skipInstall) {
|
|
80
|
+
console.log("○ Skipped dependency installation (--skip-install)");
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log(`Installing dependencies using ${config.packageManager}...`);
|
|
84
|
+
installDependencies(config.packageManager, config.destination);
|
|
85
|
+
console.log("✓ Dependencies installed");
|
|
86
|
+
}
|
|
87
|
+
if (options.git) {
|
|
88
|
+
console.log("Initializing Git repository...");
|
|
89
|
+
initGit(config.destination);
|
|
90
|
+
console.log("✓ Git repository initialized");
|
|
91
|
+
}
|
|
92
|
+
console.log("\nDone. Next steps:");
|
|
93
|
+
console.log(` cd ${name}`);
|
|
94
|
+
if (options.skipInstall) {
|
|
95
|
+
console.log(` ${config.packageManager} install`);
|
|
96
|
+
}
|
|
97
|
+
console.log(` ${config.packageManager} run build`);
|
|
98
|
+
console.log(` ${config.packageManager} test`);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
102
|
+
fail(`Generation failed: ${message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function fail(message) {
|
|
106
|
+
console.error(`Error: ${message}`);
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export function generateDockerfile(config) {
|
|
2
|
+
if (config.blueprint === "ts-cli") {
|
|
3
|
+
return `FROM node:22-alpine AS builder
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
COPY package*.json ./
|
|
6
|
+
RUN npm install
|
|
7
|
+
COPY . .
|
|
8
|
+
RUN npm run build
|
|
9
|
+
|
|
10
|
+
FROM node:22-alpine
|
|
11
|
+
WORKDIR /app
|
|
12
|
+
COPY --from=builder /app/dist ./dist
|
|
13
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
14
|
+
COPY package*.json ./
|
|
15
|
+
CMD ["node", "dist/src/index.js"]
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
18
|
+
if (config.blueprint === "node-api") {
|
|
19
|
+
return `FROM node:22-alpine AS builder
|
|
20
|
+
WORKDIR /app
|
|
21
|
+
COPY package*.json ./
|
|
22
|
+
RUN npm install
|
|
23
|
+
COPY . .
|
|
24
|
+
RUN npm run build
|
|
25
|
+
|
|
26
|
+
FROM node:22-alpine
|
|
27
|
+
WORKDIR /app
|
|
28
|
+
COPY --from=builder /app/dist ./dist
|
|
29
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
30
|
+
COPY package*.json ./
|
|
31
|
+
EXPOSE 3000
|
|
32
|
+
CMD ["node", "dist/src/server.js"]
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
return `FROM node:22-alpine
|
|
36
|
+
WORKDIR /app
|
|
37
|
+
COPY package*.json ./
|
|
38
|
+
RUN npm install
|
|
39
|
+
COPY . .
|
|
40
|
+
RUN npm run build
|
|
41
|
+
CMD ["npm", "test"]
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
export function generateDockerignore() {
|
|
45
|
+
return `node_modules
|
|
46
|
+
dist
|
|
47
|
+
coverage
|
|
48
|
+
*.log
|
|
49
|
+
.git
|
|
50
|
+
.github
|
|
51
|
+
tests
|
|
52
|
+
*.test.ts
|
|
53
|
+
README.md
|
|
54
|
+
tsconfig.json
|
|
55
|
+
biome.json
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { checkGit } from "./git.js";
|
|
3
|
+
export function runDoctor() {
|
|
4
|
+
console.log("genrex Doctor\n");
|
|
5
|
+
console.log("Environment\n" + "─".repeat(40) + "\n");
|
|
6
|
+
const checks = [
|
|
7
|
+
checkTool("Node.js", "node", ["--version"], false),
|
|
8
|
+
checkTool("npm", "npm", ["--version"], false),
|
|
9
|
+
checkTool("pnpm", "pnpm", ["--version"], true),
|
|
10
|
+
checkTool("yarn", "yarn", ["--version"], true),
|
|
11
|
+
checkTool("bun", "bun", ["--version"], true),
|
|
12
|
+
checkGitTool(),
|
|
13
|
+
];
|
|
14
|
+
for (const check of checks) {
|
|
15
|
+
const status = check.available ? "✓" : check.optional ? "○" : "✗";
|
|
16
|
+
const label = check.name.padEnd(14);
|
|
17
|
+
const version = check.version ? check.version : check.optional ? "not found (optional)" : "not found";
|
|
18
|
+
console.log(`${status} ${label} ${version}`);
|
|
19
|
+
}
|
|
20
|
+
const critical = checks.filter((c) => !c.available && !c.optional);
|
|
21
|
+
console.log();
|
|
22
|
+
if (critical.length === 0) {
|
|
23
|
+
console.log("Environment is ready.");
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
console.log("⚠ Missing required tools:");
|
|
27
|
+
for (const c of critical) {
|
|
28
|
+
console.log(` - ${c.name}`);
|
|
29
|
+
}
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function checkTool(name, command, args, optional) {
|
|
34
|
+
try {
|
|
35
|
+
const isWindows = process.platform === "win32";
|
|
36
|
+
// Node.js is always node.exe/node; npm/pnpm/yarn/bun are batch files (.cmd) on Windows
|
|
37
|
+
const binary = isWindows && command !== "node" ? `${command}.cmd` : command;
|
|
38
|
+
const result = spawnSync(binary, args, {
|
|
39
|
+
encoding: "utf8",
|
|
40
|
+
shell: isWindows,
|
|
41
|
+
timeout: 5000,
|
|
42
|
+
});
|
|
43
|
+
if (result.status === 0 && result.stdout) {
|
|
44
|
+
return { name, available: true, version: result.stdout.trim(), optional };
|
|
45
|
+
}
|
|
46
|
+
return { name, available: false, optional };
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { name, available: false, optional };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function checkGitTool() {
|
|
53
|
+
const git = checkGit();
|
|
54
|
+
return {
|
|
55
|
+
name: "Git",
|
|
56
|
+
available: git.available,
|
|
57
|
+
version: git.version,
|
|
58
|
+
optional: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { generateCiWorkflow } from "./ci.js";
|
|
4
|
+
import { generateDockerfile, generateDockerignore } from "./docker.js";
|
|
5
|
+
export function prepareDestination(destination) {
|
|
6
|
+
if (existsSync(destination)) {
|
|
7
|
+
const files = readdirSync(destination);
|
|
8
|
+
if (files.length > 0) {
|
|
9
|
+
throw new Error(`Destination directory '${destination}' already exists and is not empty.`);
|
|
10
|
+
}
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
mkdirSync(destination, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
export function buildTsCliFiles(config) {
|
|
16
|
+
const files = [
|
|
17
|
+
{
|
|
18
|
+
path: "package.json",
|
|
19
|
+
content: JSON.stringify({
|
|
20
|
+
name: config.name,
|
|
21
|
+
version: "0.1.0",
|
|
22
|
+
type: "module",
|
|
23
|
+
bin: {
|
|
24
|
+
[config.name]: "./dist/index.js",
|
|
25
|
+
},
|
|
26
|
+
scripts: {
|
|
27
|
+
build: "tsc",
|
|
28
|
+
dev: "tsc --watch",
|
|
29
|
+
start: "node dist/index.js",
|
|
30
|
+
test: "vitest run",
|
|
31
|
+
format: "biome format --write .",
|
|
32
|
+
lint: "biome check .",
|
|
33
|
+
},
|
|
34
|
+
devDependencies: {
|
|
35
|
+
"@biomejs/biome": "^1.9.0",
|
|
36
|
+
"@types/node": "^22.0.0",
|
|
37
|
+
typescript: "^5.8.0",
|
|
38
|
+
vitest: "^2.1.0",
|
|
39
|
+
},
|
|
40
|
+
}, null, 2) + "\n",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
path: "tsconfig.json",
|
|
44
|
+
content: JSON.stringify({
|
|
45
|
+
compilerOptions: {
|
|
46
|
+
target: "ES2022",
|
|
47
|
+
module: "NodeNext",
|
|
48
|
+
moduleResolution: "NodeNext",
|
|
49
|
+
outDir: "dist",
|
|
50
|
+
rootDir: "src",
|
|
51
|
+
strict: true,
|
|
52
|
+
skipLibCheck: true,
|
|
53
|
+
},
|
|
54
|
+
include: ["src/**/*"],
|
|
55
|
+
}, null, 2) + "\n",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
path: "biome.json",
|
|
59
|
+
content: JSON.stringify({
|
|
60
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
61
|
+
formatter: {
|
|
62
|
+
enabled: true,
|
|
63
|
+
indentStyle: "space",
|
|
64
|
+
indentWidth: 2,
|
|
65
|
+
},
|
|
66
|
+
linter: {
|
|
67
|
+
enabled: true,
|
|
68
|
+
rules: {
|
|
69
|
+
recommended: true,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
}, null, 2) + "\n",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
path: "README.md",
|
|
76
|
+
content: `# ${config.name}
|
|
77
|
+
|
|
78
|
+
Generated with [genrex](https://github.com/waeterr/genrex).
|
|
79
|
+
|
|
80
|
+
## Scripts
|
|
81
|
+
|
|
82
|
+
- \`${config.packageManager} run build\` — Build with TypeScript
|
|
83
|
+
- \`${config.packageManager} run start\` — Run compiled output
|
|
84
|
+
- \`${config.packageManager} test\` — Run tests with Vitest
|
|
85
|
+
- \`${config.packageManager} run lint\` — Lint with Biome
|
|
86
|
+
`,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
path: ".gitignore",
|
|
90
|
+
content: `node_modules/
|
|
91
|
+
dist/
|
|
92
|
+
coverage/
|
|
93
|
+
*.log
|
|
94
|
+
`,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
path: "src/index.ts",
|
|
98
|
+
content: `#!/usr/bin/env node
|
|
99
|
+
|
|
100
|
+
export function main(): void {
|
|
101
|
+
console.log("Hello from ${config.name}!");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
main();
|
|
105
|
+
`,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
path: "tests/index.test.ts",
|
|
109
|
+
content: `import { expect, test } from "vitest";
|
|
110
|
+
|
|
111
|
+
test("smoke test", () => {
|
|
112
|
+
expect(1 + 1).toBe(2);
|
|
113
|
+
});
|
|
114
|
+
`,
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
if (config.githubActions) {
|
|
118
|
+
files.push({
|
|
119
|
+
path: ".github/workflows/ci.yml",
|
|
120
|
+
content: generateCiWorkflow(config.packageManager),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (config.docker) {
|
|
124
|
+
files.push({
|
|
125
|
+
path: "Dockerfile",
|
|
126
|
+
content: generateDockerfile(config),
|
|
127
|
+
}, {
|
|
128
|
+
path: ".dockerignore",
|
|
129
|
+
content: generateDockerignore(),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return files;
|
|
133
|
+
}
|
|
134
|
+
export function writeProjectFiles(destination, files) {
|
|
135
|
+
for (const file of files) {
|
|
136
|
+
const fullPath = join(destination, file.path);
|
|
137
|
+
mkdirSync(dirname(fullPath), { recursive: true });
|
|
138
|
+
writeFileSync(fullPath, file.content, "utf8");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { generateCiWorkflow } from "../ci.js";
|
|
2
|
+
import { generateDockerfile, generateDockerignore } from "../docker.js";
|
|
3
|
+
export function buildNodeApiFiles(config) {
|
|
4
|
+
const files = [
|
|
5
|
+
{
|
|
6
|
+
path: "package.json",
|
|
7
|
+
content: JSON.stringify({
|
|
8
|
+
name: config.name,
|
|
9
|
+
version: "0.1.0",
|
|
10
|
+
type: "module",
|
|
11
|
+
scripts: {
|
|
12
|
+
build: "tsc",
|
|
13
|
+
dev: "tsx watch src/server.ts",
|
|
14
|
+
start: "node dist/server.js",
|
|
15
|
+
test: "vitest run",
|
|
16
|
+
format: "biome format --write .",
|
|
17
|
+
lint: "biome check .",
|
|
18
|
+
},
|
|
19
|
+
dependencies: {
|
|
20
|
+
fastify: "^5.1.0",
|
|
21
|
+
},
|
|
22
|
+
devDependencies: {
|
|
23
|
+
"@biomejs/biome": "^1.9.0",
|
|
24
|
+
"@types/node": "^22.0.0",
|
|
25
|
+
tsx: "^4.19.0",
|
|
26
|
+
typescript: "^5.8.0",
|
|
27
|
+
vitest: "^2.1.0",
|
|
28
|
+
},
|
|
29
|
+
}, null, 2) + "\n",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: "tsconfig.json",
|
|
33
|
+
content: JSON.stringify({
|
|
34
|
+
compilerOptions: {
|
|
35
|
+
target: "ES2022",
|
|
36
|
+
module: "NodeNext",
|
|
37
|
+
moduleResolution: "NodeNext",
|
|
38
|
+
outDir: "dist",
|
|
39
|
+
rootDir: "src",
|
|
40
|
+
strict: true,
|
|
41
|
+
skipLibCheck: true,
|
|
42
|
+
},
|
|
43
|
+
include: ["src/**/*"],
|
|
44
|
+
}, null, 2) + "\n",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
path: "biome.json",
|
|
48
|
+
content: JSON.stringify({
|
|
49
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
50
|
+
formatter: {
|
|
51
|
+
enabled: true,
|
|
52
|
+
indentStyle: "space",
|
|
53
|
+
indentWidth: 2,
|
|
54
|
+
},
|
|
55
|
+
linter: {
|
|
56
|
+
enabled: true,
|
|
57
|
+
rules: {
|
|
58
|
+
recommended: true,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}, null, 2) + "\n",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
path: "README.md",
|
|
65
|
+
content: `# ${config.name}
|
|
66
|
+
|
|
67
|
+
Node.js REST API built with Fastify.
|
|
68
|
+
|
|
69
|
+
Generated with [genrex](https://github.com/waeterr/genrex).
|
|
70
|
+
|
|
71
|
+
## Scripts
|
|
72
|
+
|
|
73
|
+
- \`${config.packageManager} run dev\` — Start development server with watch
|
|
74
|
+
- \`${config.packageManager} run build\` — Build with TypeScript
|
|
75
|
+
- \`${config.packageManager} start\` — Run production server
|
|
76
|
+
- \`${config.packageManager} test\` — Run tests with Vitest
|
|
77
|
+
- \`${config.packageManager} run lint\` — Lint with Biome
|
|
78
|
+
`,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
path: ".gitignore",
|
|
82
|
+
content: `node_modules/
|
|
83
|
+
dist/
|
|
84
|
+
coverage/
|
|
85
|
+
*.log
|
|
86
|
+
`,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
path: "src/server.ts",
|
|
90
|
+
content: `import Fastify from "fastify";
|
|
91
|
+
|
|
92
|
+
const server = Fastify({
|
|
93
|
+
logger: true,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
server.get("/health", async () => {
|
|
97
|
+
return { status: "ok", timestamp: new Date().toISOString() };
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
server.get("/", async () => {
|
|
101
|
+
return { message: "Welcome to ${config.name}" };
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const start = async () => {
|
|
105
|
+
try {
|
|
106
|
+
await server.listen({ port: 3000, host: "0.0.0.0" });
|
|
107
|
+
} catch (err) {
|
|
108
|
+
server.log.error(err);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
start();
|
|
114
|
+
`,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
path: "tests/server.test.ts",
|
|
118
|
+
content: `import { expect, test } from "vitest";
|
|
119
|
+
|
|
120
|
+
test("example test", () => {
|
|
121
|
+
expect(1 + 1).toBe(2);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// ponytail: add integration tests with Fastify .inject()
|
|
125
|
+
`,
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
if (config.githubActions) {
|
|
129
|
+
files.push({
|
|
130
|
+
path: ".github/workflows/ci.yml",
|
|
131
|
+
content: generateCiWorkflow(config.packageManager),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (config.docker) {
|
|
135
|
+
files.push({
|
|
136
|
+
path: "Dockerfile",
|
|
137
|
+
content: generateDockerfile(config),
|
|
138
|
+
}, {
|
|
139
|
+
path: ".dockerignore",
|
|
140
|
+
content: generateDockerignore(),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return files;
|
|
144
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { generateCiWorkflow } from "../ci.js";
|
|
2
|
+
export function buildTsLibFiles(config) {
|
|
3
|
+
const files = [
|
|
4
|
+
{
|
|
5
|
+
path: "package.json",
|
|
6
|
+
content: JSON.stringify({
|
|
7
|
+
name: config.name,
|
|
8
|
+
version: "0.1.0",
|
|
9
|
+
type: "module",
|
|
10
|
+
main: "./dist/index.js",
|
|
11
|
+
types: "./dist/index.d.ts",
|
|
12
|
+
exports: {
|
|
13
|
+
".": {
|
|
14
|
+
import: "./dist/index.js",
|
|
15
|
+
types: "./dist/index.d.ts",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
files: ["dist"],
|
|
19
|
+
scripts: {
|
|
20
|
+
build: "tsc",
|
|
21
|
+
test: "vitest run",
|
|
22
|
+
format: "biome format --write .",
|
|
23
|
+
lint: "biome check .",
|
|
24
|
+
prepublishOnly: "npm run build && npm test",
|
|
25
|
+
},
|
|
26
|
+
devDependencies: {
|
|
27
|
+
"@biomejs/biome": "^1.9.0",
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
typescript: "^5.8.0",
|
|
30
|
+
vitest: "^2.1.0",
|
|
31
|
+
},
|
|
32
|
+
}, null, 2) + "\n",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
path: "tsconfig.json",
|
|
36
|
+
content: JSON.stringify({
|
|
37
|
+
compilerOptions: {
|
|
38
|
+
target: "ES2022",
|
|
39
|
+
module: "NodeNext",
|
|
40
|
+
moduleResolution: "NodeNext",
|
|
41
|
+
outDir: "dist",
|
|
42
|
+
rootDir: "src",
|
|
43
|
+
declaration: true,
|
|
44
|
+
declarationMap: true,
|
|
45
|
+
sourceMap: true,
|
|
46
|
+
strict: true,
|
|
47
|
+
skipLibCheck: true,
|
|
48
|
+
},
|
|
49
|
+
include: ["src/**/*"],
|
|
50
|
+
}, null, 2) + "\n",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
path: "biome.json",
|
|
54
|
+
content: JSON.stringify({
|
|
55
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
56
|
+
formatter: {
|
|
57
|
+
enabled: true,
|
|
58
|
+
indentStyle: "space",
|
|
59
|
+
indentWidth: 2,
|
|
60
|
+
},
|
|
61
|
+
linter: {
|
|
62
|
+
enabled: true,
|
|
63
|
+
rules: {
|
|
64
|
+
recommended: true,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}, null, 2) + "\n",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
path: "README.md",
|
|
71
|
+
content: `# ${config.name}
|
|
72
|
+
|
|
73
|
+
TypeScript library.
|
|
74
|
+
|
|
75
|
+
Generated with [genrex](https://github.com/waeterr/genrex).
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
\`\`\`bash
|
|
80
|
+
${config.packageManager} add ${config.name}
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
\`\`\`typescript
|
|
86
|
+
import { greet } from "${config.name}";
|
|
87
|
+
|
|
88
|
+
console.log(greet("world"));
|
|
89
|
+
\`\`\`
|
|
90
|
+
|
|
91
|
+
## Scripts
|
|
92
|
+
|
|
93
|
+
- \`${config.packageManager} run build\` — Build TypeScript to dist/
|
|
94
|
+
- \`${config.packageManager} test\` — Run tests with Vitest
|
|
95
|
+
- \`${config.packageManager} run lint\` — Lint with Biome
|
|
96
|
+
`,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
path: ".gitignore",
|
|
100
|
+
content: `node_modules/
|
|
101
|
+
dist/
|
|
102
|
+
coverage/
|
|
103
|
+
*.log
|
|
104
|
+
`,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
path: ".npmignore",
|
|
108
|
+
content: `src/
|
|
109
|
+
tests/
|
|
110
|
+
coverage/
|
|
111
|
+
*.test.ts
|
|
112
|
+
*.config.*
|
|
113
|
+
biome.json
|
|
114
|
+
tsconfig.json
|
|
115
|
+
`,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
path: "src/index.ts",
|
|
119
|
+
content: `export function greet(name: string): string {
|
|
120
|
+
return \`Hello, \${name}!\`;
|
|
121
|
+
}
|
|
122
|
+
`,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
path: "tests/index.test.ts",
|
|
126
|
+
content: `import { expect, test } from "vitest";
|
|
127
|
+
import { greet } from "../src/index.js";
|
|
128
|
+
|
|
129
|
+
test("greet returns greeting", () => {
|
|
130
|
+
expect(greet("world")).toBe("Hello, world!");
|
|
131
|
+
});
|
|
132
|
+
`,
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
if (config.githubActions) {
|
|
136
|
+
files.push({
|
|
137
|
+
path: ".github/workflows/ci.yml",
|
|
138
|
+
content: generateCiWorkflow(config.packageManager),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return files;
|
|
142
|
+
}
|
package/dist/src/git.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
+
export function checkGit() {
|
|
3
|
+
try {
|
|
4
|
+
const result = spawnSync("git", ["--version"], {
|
|
5
|
+
encoding: "utf8",
|
|
6
|
+
shell: false,
|
|
7
|
+
});
|
|
8
|
+
if (result.status === 0 && result.stdout) {
|
|
9
|
+
return { available: true, version: result.stdout.trim() };
|
|
10
|
+
}
|
|
11
|
+
return { available: false };
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return { available: false };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function initGit(cwd) {
|
|
18
|
+
execFileSync("git", ["init"], {
|
|
19
|
+
cwd,
|
|
20
|
+
stdio: "inherit",
|
|
21
|
+
shell: false,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createProject } from "./create.js";
|
|
3
|
+
import { runDoctor } from "./doctor.js";
|
|
4
|
+
import { blueprints, getBlueprint } from "./blueprints.js";
|
|
5
|
+
const [command, ...args] = process.argv.slice(2);
|
|
6
|
+
switch (command) {
|
|
7
|
+
case "create":
|
|
8
|
+
runCreate(args);
|
|
9
|
+
break;
|
|
10
|
+
case "list":
|
|
11
|
+
listBlueprints();
|
|
12
|
+
break;
|
|
13
|
+
case "info":
|
|
14
|
+
showBlueprintInfo(args[0]);
|
|
15
|
+
break;
|
|
16
|
+
case "doctor":
|
|
17
|
+
runDoctor();
|
|
18
|
+
break;
|
|
19
|
+
case "--help":
|
|
20
|
+
case "-h":
|
|
21
|
+
case undefined:
|
|
22
|
+
printHelp();
|
|
23
|
+
break;
|
|
24
|
+
case "--version":
|
|
25
|
+
case "-v":
|
|
26
|
+
console.log("0.1.0");
|
|
27
|
+
break;
|
|
28
|
+
default:
|
|
29
|
+
console.error(`Unknown command: ${command}`);
|
|
30
|
+
console.error("Run 'genrex --help' for usage.");
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
}
|
|
33
|
+
function runCreate(args) {
|
|
34
|
+
const [name, ...flags] = args;
|
|
35
|
+
const options = {
|
|
36
|
+
dryRun: false,
|
|
37
|
+
skipInstall: false,
|
|
38
|
+
git: false,
|
|
39
|
+
githubActions: false,
|
|
40
|
+
docker: false,
|
|
41
|
+
};
|
|
42
|
+
for (let index = 0; index < flags.length; index += 1) {
|
|
43
|
+
const flag = flags[index];
|
|
44
|
+
if (flag === "--dry-run") {
|
|
45
|
+
options.dryRun = true;
|
|
46
|
+
}
|
|
47
|
+
else if (flag === "--skip-install") {
|
|
48
|
+
options.skipInstall = true;
|
|
49
|
+
}
|
|
50
|
+
else if (flag === "--git") {
|
|
51
|
+
options.git = true;
|
|
52
|
+
}
|
|
53
|
+
else if (flag === "--github-actions") {
|
|
54
|
+
options.githubActions = true;
|
|
55
|
+
}
|
|
56
|
+
else if (flag === "--docker") {
|
|
57
|
+
options.docker = true;
|
|
58
|
+
}
|
|
59
|
+
else if (flag === "--blueprint") {
|
|
60
|
+
options.blueprint = flags[++index];
|
|
61
|
+
}
|
|
62
|
+
else if (flag === "--package-manager") {
|
|
63
|
+
const packageManager = flags[++index];
|
|
64
|
+
if (!["npm", "pnpm", "yarn", "bun"].includes(packageManager)) {
|
|
65
|
+
console.error(`Error: Unsupported package manager '${packageManager}'.`);
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
options.packageManager = packageManager;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.error(`Error: Unknown create option '${flag}'.`);
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
createProject(name, options);
|
|
78
|
+
}
|
|
79
|
+
function listBlueprints() {
|
|
80
|
+
console.log("Available blueprints:\n");
|
|
81
|
+
for (const b of blueprints) {
|
|
82
|
+
console.log(` ${b.id.padEnd(12)} ${b.name} (${b.language})`);
|
|
83
|
+
console.log(` ${b.description}\n`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function showBlueprintInfo(id) {
|
|
87
|
+
if (!id) {
|
|
88
|
+
console.error("Error: Blueprint ID required. Example: genrex info ts-cli");
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const bp = getBlueprint(id);
|
|
93
|
+
if (!bp) {
|
|
94
|
+
console.error(`Error: Blueprint '${id}' not found.`);
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
console.log(`${bp.name} (${bp.id})`);
|
|
99
|
+
console.log(`Version: ${bp.version}`);
|
|
100
|
+
console.log(`Language: ${bp.language}`);
|
|
101
|
+
console.log(`Description: ${bp.description}`);
|
|
102
|
+
console.log("Capabilities:");
|
|
103
|
+
for (const c of bp.capabilities) {
|
|
104
|
+
console.log(` - ${c}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function printHelp() {
|
|
108
|
+
console.log(`genrex — Project Blueprint Engine
|
|
109
|
+
|
|
110
|
+
Usage:
|
|
111
|
+
genrex <command> [options]
|
|
112
|
+
|
|
113
|
+
Commands:
|
|
114
|
+
list List available blueprints
|
|
115
|
+
info <name> Show blueprint details
|
|
116
|
+
create <name> --dry-run Preview project generation
|
|
117
|
+
doctor Check local environment
|
|
118
|
+
|
|
119
|
+
Create options:
|
|
120
|
+
--blueprint <id> Blueprint ID (default: ts-cli)
|
|
121
|
+
--package-manager <manager> npm, pnpm, yarn, or bun (default: pnpm)
|
|
122
|
+
--dry-run Preview without filesystem changes
|
|
123
|
+
--skip-install Do not install dependencies
|
|
124
|
+
--git Initialize a Git repository
|
|
125
|
+
--github-actions Generate GitHub Actions CI workflow
|
|
126
|
+
--docker Generate Dockerfile & .dockerignore
|
|
127
|
+
|
|
128
|
+
Options:
|
|
129
|
+
-h, --help Show help
|
|
130
|
+
-v, --version Show version`);
|
|
131
|
+
}
|
package/dist/src/pm.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
export function installDependencies(pm, cwd) {
|
|
3
|
+
const isWindows = process.platform === "win32";
|
|
4
|
+
const cmd = isWindows ? `${pm}.cmd` : pm;
|
|
5
|
+
execFileSync(cmd, ["install"], {
|
|
6
|
+
cwd,
|
|
7
|
+
stdio: "inherit",
|
|
8
|
+
shell: false,
|
|
9
|
+
});
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genrex",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Forge production-ready TypeScript projects from blueprints",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "waeterr",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/waeterr/genrex.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cli",
|
|
14
|
+
"scaffolding",
|
|
15
|
+
"blueprint",
|
|
16
|
+
"generator",
|
|
17
|
+
"typescript",
|
|
18
|
+
"fastify",
|
|
19
|
+
"vitest",
|
|
20
|
+
"biome"
|
|
21
|
+
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"genrex": "./dist/src/index.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist/src"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.json",
|
|
30
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
31
|
+
"start": "node dist/src/index.js",
|
|
32
|
+
"test": "npm run build && node --test dist/tests/**/*.test.js",
|
|
33
|
+
"prepublishOnly": "npm run check && npm test"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^24.0.0",
|
|
37
|
+
"typescript": "^5.8.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20"
|
|
41
|
+
}
|
|
42
|
+
}
|