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 CHANGED
@@ -1,3 +1,148 @@
1
- # Temporary Holding Version
1
+ # genrex
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
+ }
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }