@vertz/create-vertz-app 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.
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { resolveOptions, scaffold } from '../src/index.js';
5
+ import type { Runtime } from '../src/types.js';
6
+
7
+ const program = new Command();
8
+
9
+ program
10
+ .name('create-vertz-app')
11
+ .description('Scaffold a new Vertz project')
12
+ .version('0.1.0')
13
+ .argument('[name]', 'Project name')
14
+ .option('-r, --runtime <runtime>', 'Runtime to use (bun, node, deno)', 'bun')
15
+ .option('-e, --example', 'Include example health module', undefined)
16
+ .option('--no-example', 'Exclude example health module')
17
+ .action(
18
+ async (
19
+ name: string | undefined,
20
+ options: {
21
+ runtime: string;
22
+ example?: boolean;
23
+ },
24
+ ) => {
25
+ try {
26
+ // Convert runtime string to Runtime type
27
+ const runtime = options.runtime.toLowerCase() as Runtime;
28
+
29
+ // Handle --example / --no-example
30
+ let includeExample: boolean | undefined;
31
+ if (options.example === true) {
32
+ includeExample = true;
33
+ } else if (options.example === false) {
34
+ includeExample = false;
35
+ }
36
+
37
+ const cliOptions = {
38
+ projectName: name,
39
+ runtime,
40
+ includeExample,
41
+ };
42
+
43
+ const resolved = await resolveOptions(cliOptions);
44
+
45
+ console.log(`Creating Vertz app: ${resolved.projectName}`);
46
+ console.log(`Runtime: ${resolved.runtime}`);
47
+ console.log(`Include example: ${resolved.includeExample ? 'Yes' : 'No'}`);
48
+
49
+ // Create project in current directory
50
+ const targetDir = process.cwd();
51
+ await scaffold(targetDir, resolved);
52
+
53
+ console.log(`\n✓ Created ${resolved.projectName}`);
54
+ console.log(`\nNext steps:`);
55
+ console.log(` cd ${resolved.projectName}`);
56
+ console.log(` bun install`);
57
+ console.log(` bun run dev`);
58
+ } catch (error) {
59
+ console.error('Error:', error instanceof Error ? error.message : error);
60
+ process.exit(1);
61
+ }
62
+ },
63
+ );
64
+
65
+ program.parse();
@@ -0,0 +1,5 @@
1
+ export * from './prompts.js';
2
+ export * from './scaffold.js';
3
+ export * from './templates/index.js';
4
+ export * from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './prompts.js';
2
+ export * from './scaffold.js';
3
+ export * from './templates/index.js';
4
+ export * from './types.js';
@@ -0,0 +1,31 @@
1
+ import type { CliOptions, Runtime, ScaffoldOptions } from './types.js';
2
+ /**
3
+ * Error thrown in CI mode when project name is required but not provided
4
+ */
5
+ export declare class ProjectNameRequiredError extends Error {
6
+ constructor();
7
+ }
8
+ /**
9
+ * Error thrown when an invalid runtime is provided
10
+ */
11
+ export declare class InvalidRuntimeError extends Error {
12
+ constructor(runtime: string);
13
+ }
14
+ /**
15
+ * Prompts the user for project name
16
+ */
17
+ export declare function promptForProjectName(): Promise<string>;
18
+ /**
19
+ * Prompts the user for runtime selection
20
+ */
21
+ export declare function promptForRuntime(): Promise<Runtime>;
22
+ /**
23
+ * Prompts the user for example module inclusion
24
+ */
25
+ export declare function promptForExample(): Promise<boolean>;
26
+ /**
27
+ * Resolves CLI options into complete scaffold options
28
+ * Handles both interactive and CI modes
29
+ */
30
+ export declare function resolveOptions(cliOptions: Partial<CliOptions>): Promise<ScaffoldOptions>;
31
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIvE;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;;CAKlD;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAY5D;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAiBzD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAkBzD;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CA0C9F"}
@@ -0,0 +1,122 @@
1
+ import { createInterface } from 'node:readline';
2
+ const VALID_RUNTIMES = ['bun', 'node', 'deno'];
3
+ /**
4
+ * Error thrown in CI mode when project name is required but not provided
5
+ */
6
+ export class ProjectNameRequiredError extends Error {
7
+ constructor() {
8
+ super('Project name is required in CI mode. Use --name or pass as argument.');
9
+ this.name = 'ProjectNameRequiredError';
10
+ }
11
+ }
12
+ /**
13
+ * Error thrown when an invalid runtime is provided
14
+ */
15
+ export class InvalidRuntimeError extends Error {
16
+ constructor(runtime) {
17
+ super(`Invalid runtime: ${runtime}. Valid options are: ${VALID_RUNTIMES.join(', ')}`);
18
+ this.name = 'InvalidRuntimeError';
19
+ }
20
+ }
21
+ /**
22
+ * Prompts the user for project name
23
+ */
24
+ export async function promptForProjectName() {
25
+ return new Promise((resolve) => {
26
+ const rl = createInterface({
27
+ input: process.stdin,
28
+ output: process.stdout,
29
+ });
30
+ rl.question('Project name: ', (answer) => {
31
+ rl.close();
32
+ resolve(answer.trim() || 'my-vertz-app');
33
+ });
34
+ });
35
+ }
36
+ /**
37
+ * Prompts the user for runtime selection
38
+ */
39
+ export async function promptForRuntime() {
40
+ return new Promise((resolve) => {
41
+ const rl = createInterface({
42
+ input: process.stdin,
43
+ output: process.stdout,
44
+ });
45
+ rl.question('Runtime (bun/node/deno) [bun]: ', (answer) => {
46
+ rl.close();
47
+ const trimmed = answer.trim().toLowerCase();
48
+ if (VALID_RUNTIMES.includes(trimmed)) {
49
+ resolve(trimmed);
50
+ }
51
+ else {
52
+ resolve('bun');
53
+ }
54
+ });
55
+ });
56
+ }
57
+ /**
58
+ * Prompts the user for example module inclusion
59
+ */
60
+ export async function promptForExample() {
61
+ return new Promise((resolve) => {
62
+ const rl = createInterface({
63
+ input: process.stdin,
64
+ output: process.stdout,
65
+ });
66
+ rl.question('Include example health module? (Y/n): ', (answer) => {
67
+ rl.close();
68
+ const trimmed = answer.trim().toLowerCase();
69
+ // Default to yes
70
+ if (trimmed === 'n' || trimmed === 'no') {
71
+ resolve(false);
72
+ }
73
+ else {
74
+ resolve(true);
75
+ }
76
+ });
77
+ });
78
+ }
79
+ /**
80
+ * Resolves CLI options into complete scaffold options
81
+ * Handles both interactive and CI modes
82
+ */
83
+ export async function resolveOptions(cliOptions) {
84
+ const isCI = process.env.CI === 'true';
85
+ // Validate runtime if provided
86
+ if (cliOptions.runtime !== undefined && !VALID_RUNTIMES.includes(cliOptions.runtime)) {
87
+ throw new InvalidRuntimeError(cliOptions.runtime);
88
+ }
89
+ // Handle project name
90
+ let projectName = cliOptions.projectName;
91
+ if (!projectName) {
92
+ if (isCI) {
93
+ throw new ProjectNameRequiredError();
94
+ }
95
+ projectName = await promptForProjectName();
96
+ }
97
+ // Handle runtime
98
+ let runtime = cliOptions.runtime;
99
+ if (!runtime) {
100
+ if (isCI) {
101
+ runtime = 'bun'; // Default in CI mode
102
+ }
103
+ else {
104
+ runtime = await promptForRuntime();
105
+ }
106
+ }
107
+ // Handle example inclusion
108
+ let includeExample = cliOptions.includeExample;
109
+ if (includeExample === undefined) {
110
+ if (isCI) {
111
+ includeExample = true; // Default in CI mode
112
+ }
113
+ else {
114
+ includeExample = await promptForExample();
115
+ }
116
+ }
117
+ return {
118
+ projectName,
119
+ runtime,
120
+ includeExample,
121
+ };
122
+ }
@@ -0,0 +1,14 @@
1
+ import type { ScaffoldOptions } from './types.js';
2
+ /**
3
+ * Error thrown when the project directory already exists
4
+ */
5
+ export declare class DirectoryExistsError extends Error {
6
+ constructor(projectName: string);
7
+ }
8
+ /**
9
+ * Scaffolds a new Vertz project
10
+ * @param parentDir - Parent directory where the project will be created
11
+ * @param options - Scaffold options
12
+ */
13
+ export declare function scaffold(parentDir: string, options: ScaffoldOptions): Promise<void>;
14
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,WAAW,EAAE,MAAM;CAIhC;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DzF"}
@@ -0,0 +1,74 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { appTemplate, denoConfigTemplate, envExampleTemplate, envSrcTemplate, envTemplate, gitignoreTemplate, healthCheckSchemaTemplate, healthModuleDefTemplate, healthModuleTemplate, healthRouterTemplate, healthServiceTemplate, mainTemplate, packageJsonTemplate, requestIdMiddlewareTemplate, tsconfigTemplate, vertzConfigTemplate, } from './templates/index.js';
4
+ /**
5
+ * Error thrown when the project directory already exists
6
+ */
7
+ export class DirectoryExistsError extends Error {
8
+ constructor(projectName) {
9
+ super(`Directory "${projectName}" already exists`);
10
+ this.name = 'DirectoryExistsError';
11
+ }
12
+ }
13
+ /**
14
+ * Scaffolds a new Vertz project
15
+ * @param parentDir - Parent directory where the project will be created
16
+ * @param options - Scaffold options
17
+ */
18
+ export async function scaffold(parentDir, options) {
19
+ const { projectName, runtime, includeExample } = options;
20
+ const projectDir = path.join(parentDir, projectName);
21
+ // Check if directory already exists
22
+ try {
23
+ await fs.access(projectDir);
24
+ throw new DirectoryExistsError(projectName);
25
+ }
26
+ catch (err) {
27
+ if (err instanceof DirectoryExistsError) {
28
+ throw err;
29
+ }
30
+ // Directory doesn't exist, which is what we want
31
+ }
32
+ // Create project directory
33
+ await fs.mkdir(projectDir, { recursive: true });
34
+ // Create subdirectories
35
+ const srcDir = path.join(projectDir, 'src');
36
+ const modulesDir = path.join(srcDir, 'modules');
37
+ const middlewaresDir = path.join(srcDir, 'middlewares');
38
+ await fs.mkdir(srcDir, { recursive: true });
39
+ await fs.mkdir(modulesDir, { recursive: true });
40
+ await fs.mkdir(middlewaresDir, { recursive: true });
41
+ // Generate and write core config files
42
+ await writeFile(projectDir, 'package.json', packageJsonTemplate({ projectName, runtime, includeExample }));
43
+ await writeFile(projectDir, 'tsconfig.json', tsconfigTemplate(runtime));
44
+ await writeFile(projectDir, 'vertz.config.ts', vertzConfigTemplate());
45
+ await writeFile(projectDir, '.env', envTemplate());
46
+ await writeFile(projectDir, '.env.example', envExampleTemplate());
47
+ await writeFile(projectDir, '.gitignore', gitignoreTemplate());
48
+ // Generate Deno-specific config if needed
49
+ if (runtime === 'deno') {
50
+ await writeFile(projectDir, 'deno.json', denoConfigTemplate());
51
+ }
52
+ // Generate source files
53
+ await writeFile(srcDir, 'env.ts', envSrcTemplate());
54
+ await writeFile(srcDir, 'app.ts', appTemplate());
55
+ await writeFile(srcDir, 'main.ts', mainTemplate());
56
+ await writeFile(middlewaresDir, 'request-id.middleware.ts', requestIdMiddlewareTemplate());
57
+ // Generate example health module if requested
58
+ if (includeExample) {
59
+ const schemasDir = path.join(modulesDir, 'schemas');
60
+ await fs.mkdir(schemasDir, { recursive: true });
61
+ await writeFile(modulesDir, 'health.module-def.ts', healthModuleDefTemplate());
62
+ await writeFile(modulesDir, 'health.module.ts', healthModuleTemplate());
63
+ await writeFile(modulesDir, 'health.service.ts', healthServiceTemplate());
64
+ await writeFile(modulesDir, 'health.router.ts', healthRouterTemplate());
65
+ await writeFile(schemasDir, 'health-check.schema.ts', healthCheckSchemaTemplate());
66
+ }
67
+ }
68
+ /**
69
+ * Helper to write a file with consistent formatting
70
+ */
71
+ async function writeFile(dir, filename, content) {
72
+ const filePath = path.join(dir, filename);
73
+ await fs.writeFile(filePath, content, 'utf-8');
74
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=templates.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.test.d.ts","sourceRoot":"","sources":["../../../src/templates/__tests__/templates.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,242 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { appTemplate, denoConfigTemplate, envExampleTemplate, envSrcTemplate, envTemplate, gitignoreTemplate, healthCheckSchemaTemplate, healthModuleDefTemplate, healthModuleTemplate, healthRouterTemplate, healthServiceTemplate, mainTemplate, packageJsonTemplate, requestIdMiddlewareTemplate, tsconfigTemplate, vertzConfigTemplate, } from '../index.js';
3
+ describe('templates', () => {
4
+ describe('packageJsonTemplate', () => {
5
+ it('returns valid JSON', () => {
6
+ const result = packageJsonTemplate({ projectName: 'test-app', runtime: 'bun' });
7
+ expect(() => JSON.parse(result)).not.toThrow();
8
+ });
9
+ it('includes project name', () => {
10
+ const result = packageJsonTemplate({ projectName: 'my-app', runtime: 'bun' });
11
+ const pkg = JSON.parse(result);
12
+ expect(pkg.name).toBe('my-app');
13
+ });
14
+ it('includes @vertz/server dependency', () => {
15
+ const result = packageJsonTemplate({ projectName: 'test-app', runtime: 'bun' });
16
+ const pkg = JSON.parse(result);
17
+ expect(pkg.dependencies['@vertz/server']).toBeDefined();
18
+ });
19
+ it('includes @vertz/cli as dev dependency', () => {
20
+ const result = packageJsonTemplate({ projectName: 'test-app', runtime: 'bun' });
21
+ const pkg = JSON.parse(result);
22
+ expect(pkg.devDependencies['@vertz/cli']).toBeDefined();
23
+ });
24
+ it('includes required scripts', () => {
25
+ const result = packageJsonTemplate({ projectName: 'test-app', runtime: 'bun' });
26
+ const pkg = JSON.parse(result);
27
+ expect(pkg.scripts.dev).toBeDefined();
28
+ expect(pkg.scripts.build).toBeDefined();
29
+ expect(pkg.scripts.check).toBeDefined();
30
+ });
31
+ it('uses bun scripts for bun runtime', () => {
32
+ const result = packageJsonTemplate({ projectName: 'test-app', runtime: 'bun' });
33
+ const pkg = JSON.parse(result);
34
+ expect(pkg.scripts.dev).toContain('bun');
35
+ });
36
+ it('uses tsx for node runtime', () => {
37
+ const result = packageJsonTemplate({ projectName: 'test-app', runtime: 'node' });
38
+ const pkg = JSON.parse(result);
39
+ expect(pkg.scripts.dev).toContain('tsx');
40
+ expect(pkg.dependencies.tsx).toBeDefined();
41
+ });
42
+ // Tests for runtime-specific type dependencies
43
+ describe('runtime-specific type dependencies', () => {
44
+ it('adds bun-types to devDependencies for bun runtime', () => {
45
+ const result = packageJsonTemplate({ projectName: 'test-app', runtime: 'bun' });
46
+ const pkg = JSON.parse(result);
47
+ expect(pkg.devDependencies['bun-types']).toBeDefined();
48
+ expect(pkg.devDependencies['bun-types']).toMatch(/^\^1\./);
49
+ });
50
+ it('adds @types/node to devDependencies for node runtime', () => {
51
+ const result = packageJsonTemplate({ projectName: 'test-app', runtime: 'node' });
52
+ const pkg = JSON.parse(result);
53
+ expect(pkg.devDependencies['@types/node']).toBeDefined();
54
+ expect(pkg.devDependencies['@types/node']).toMatch(/^\^20\./);
55
+ });
56
+ it('does not add type packages for deno runtime', () => {
57
+ const result = packageJsonTemplate({ projectName: 'test-app', runtime: 'deno' });
58
+ const pkg = JSON.parse(result);
59
+ expect(pkg.devDependencies['bun-types']).toBeUndefined();
60
+ expect(pkg.devDependencies['@types/node']).toBeUndefined();
61
+ });
62
+ });
63
+ });
64
+ describe('tsconfigTemplate', () => {
65
+ it('returns valid JSON', () => {
66
+ const result = tsconfigTemplate('bun');
67
+ expect(() => JSON.parse(result)).not.toThrow();
68
+ });
69
+ it('has strict mode enabled', () => {
70
+ const result = tsconfigTemplate('bun');
71
+ const tsconfig = JSON.parse(result);
72
+ expect(tsconfig.compilerOptions.strict).toBe(true);
73
+ });
74
+ // Tests for runtime-specific types in tsconfig
75
+ describe('runtime-specific types', () => {
76
+ it('includes bun-types in tsconfig for bun runtime', () => {
77
+ const result = tsconfigTemplate('bun');
78
+ const tsconfig = JSON.parse(result);
79
+ expect(tsconfig.compilerOptions.types).toContain('bun-types');
80
+ });
81
+ it('includes node types in tsconfig for node runtime', () => {
82
+ const result = tsconfigTemplate('node');
83
+ const tsconfig = JSON.parse(result);
84
+ expect(tsconfig.compilerOptions.types).toContain('node');
85
+ expect(tsconfig.compilerOptions.types).not.toContain('bun-types');
86
+ });
87
+ it('has empty types array for deno runtime', () => {
88
+ const result = tsconfigTemplate('deno');
89
+ const tsconfig = JSON.parse(result);
90
+ // Deno has built-in types, so types should be empty or undefined
91
+ expect(tsconfig.compilerOptions.types).toEqual([]);
92
+ });
93
+ });
94
+ });
95
+ describe('vertzConfigTemplate', () => {
96
+ it('returns non-empty string', () => {
97
+ const result = vertzConfigTemplate();
98
+ expect(result.length).toBeGreaterThan(0);
99
+ });
100
+ it('exports a default config', () => {
101
+ const result = vertzConfigTemplate();
102
+ expect(result).toContain('export default');
103
+ });
104
+ });
105
+ describe('envTemplate', () => {
106
+ it('returns non-empty string', () => {
107
+ const result = envTemplate();
108
+ expect(result.length).toBeGreaterThan(0);
109
+ });
110
+ it('includes DATABASE_URL placeholder', () => {
111
+ const result = envTemplate();
112
+ expect(result).toContain('DATABASE_URL=');
113
+ });
114
+ });
115
+ describe('envExampleTemplate', () => {
116
+ it('returns non-empty string', () => {
117
+ const result = envExampleTemplate();
118
+ expect(result.length).toBeGreaterThan(0);
119
+ });
120
+ it('matches env template structure', () => {
121
+ const result = envExampleTemplate();
122
+ expect(result).toContain('DATABASE_URL=');
123
+ });
124
+ });
125
+ describe('gitignoreTemplate', () => {
126
+ it('returns non-empty string', () => {
127
+ const result = gitignoreTemplate();
128
+ expect(result.length).toBeGreaterThan(0);
129
+ });
130
+ it('includes node_modules', () => {
131
+ const result = gitignoreTemplate();
132
+ expect(result).toContain('node_modules');
133
+ });
134
+ it('includes dist/', () => {
135
+ const result = gitignoreTemplate();
136
+ expect(result).toContain('dist/');
137
+ });
138
+ });
139
+ describe('envSrcTemplate', () => {
140
+ it('returns non-empty string', () => {
141
+ const result = envSrcTemplate();
142
+ expect(result.length).toBeGreaterThan(0);
143
+ });
144
+ it('uses envsafe for validation', () => {
145
+ const result = envSrcTemplate();
146
+ expect(result).toContain('envsafe');
147
+ });
148
+ });
149
+ describe('appTemplate', () => {
150
+ it('returns non-empty string', () => {
151
+ const result = appTemplate();
152
+ expect(result.length).toBeGreaterThan(0);
153
+ });
154
+ it('exports createServer', () => {
155
+ const result = appTemplate();
156
+ expect(result).toContain('createServer');
157
+ });
158
+ });
159
+ describe('mainTemplate', () => {
160
+ it('returns non-empty string', () => {
161
+ const result = mainTemplate();
162
+ expect(result.length).toBeGreaterThan(0);
163
+ });
164
+ it('starts the app', () => {
165
+ const result = mainTemplate();
166
+ expect(result).toContain('app.start');
167
+ });
168
+ });
169
+ describe('requestIdMiddlewareTemplate', () => {
170
+ it('returns non-empty string', () => {
171
+ const result = requestIdMiddlewareTemplate();
172
+ expect(result.length).toBeGreaterThan(0);
173
+ });
174
+ it('handles requestId', () => {
175
+ const result = requestIdMiddlewareTemplate();
176
+ expect(result).toContain('requestId');
177
+ });
178
+ });
179
+ describe('health module templates', () => {
180
+ it('healthModuleDefTemplate returns non-empty string', () => {
181
+ const result = healthModuleDefTemplate();
182
+ expect(result.length).toBeGreaterThan(0);
183
+ });
184
+ it('healthModuleTemplate returns non-empty string', () => {
185
+ const result = healthModuleTemplate();
186
+ expect(result.length).toBeGreaterThan(0);
187
+ });
188
+ it('healthServiceTemplate returns non-empty string', () => {
189
+ const result = healthServiceTemplate();
190
+ expect(result.length).toBeGreaterThan(0);
191
+ });
192
+ it('healthRouterTemplate returns non-empty string', () => {
193
+ const result = healthRouterTemplate();
194
+ expect(result.length).toBeGreaterThan(0);
195
+ });
196
+ it('healthCheckSchemaTemplate returns non-empty string', () => {
197
+ const result = healthCheckSchemaTemplate();
198
+ expect(result.length).toBeGreaterThan(0);
199
+ });
200
+ it('templates include helpful comments for new users', () => {
201
+ const serviceResult = healthServiceTemplate();
202
+ expect(serviceResult).toContain('In a real app');
203
+ });
204
+ });
205
+ describe('denoConfigTemplate', () => {
206
+ it('returns valid JSON', () => {
207
+ const result = denoConfigTemplate();
208
+ expect(() => JSON.parse(result)).not.toThrow();
209
+ });
210
+ it('includes imports', () => {
211
+ const result = denoConfigTemplate();
212
+ const config = JSON.parse(result);
213
+ expect(config.imports).toBeDefined();
214
+ });
215
+ });
216
+ describe('all templates return non-empty strings', () => {
217
+ it('every template function returns a non-empty string', () => {
218
+ const templates = [
219
+ () => packageJsonTemplate({ projectName: 'test', runtime: 'bun' }),
220
+ () => tsconfigTemplate('bun'),
221
+ vertzConfigTemplate,
222
+ envTemplate,
223
+ envExampleTemplate,
224
+ gitignoreTemplate,
225
+ envSrcTemplate,
226
+ appTemplate,
227
+ mainTemplate,
228
+ requestIdMiddlewareTemplate,
229
+ healthModuleDefTemplate,
230
+ healthModuleTemplate,
231
+ healthServiceTemplate,
232
+ healthRouterTemplate,
233
+ healthCheckSchemaTemplate,
234
+ denoConfigTemplate,
235
+ ];
236
+ for (const template of templates) {
237
+ const result = template();
238
+ expect(result.length).toBeGreaterThan(0);
239
+ }
240
+ });
241
+ });
242
+ });
@@ -0,0 +1,70 @@
1
+ import type { Runtime } from '../types.js';
2
+ /**
3
+ * Package.json template
4
+ */
5
+ export declare function packageJsonTemplate({ projectName, runtime, }: {
6
+ projectName: string;
7
+ runtime: Runtime;
8
+ includeExample?: boolean;
9
+ }): string;
10
+ /**
11
+ * Tsconfig.json template - runtime-specific types
12
+ */
13
+ export declare function tsconfigTemplate(runtime: Runtime): string;
14
+ /**
15
+ * vertz.config.ts template
16
+ */
17
+ export declare function vertzConfigTemplate(): string;
18
+ /**
19
+ * .env template
20
+ */
21
+ export declare function envTemplate(): string;
22
+ /**
23
+ * .env.example template
24
+ */
25
+ export declare function envExampleTemplate(): string;
26
+ /**
27
+ * .gitignore template
28
+ */
29
+ export declare function gitignoreTemplate(): string;
30
+ /**
31
+ * src/env.ts template
32
+ */
33
+ export declare function envSrcTemplate(): string;
34
+ /**
35
+ * src/app.ts template
36
+ */
37
+ export declare function appTemplate(): string;
38
+ /**
39
+ * src/main.ts template
40
+ */
41
+ export declare function mainTemplate(): string;
42
+ /**
43
+ * src/middlewares/request-id.middleware.ts template
44
+ */
45
+ export declare function requestIdMiddlewareTemplate(): string;
46
+ /**
47
+ * src/modules/health.module-def.ts template
48
+ */
49
+ export declare function healthModuleDefTemplate(): string;
50
+ /**
51
+ * src/modules/health.module.ts template
52
+ */
53
+ export declare function healthModuleTemplate(): string;
54
+ /**
55
+ * src/modules/health.service.ts template
56
+ */
57
+ export declare function healthServiceTemplate(): string;
58
+ /**
59
+ * src/modules/health.router.ts template
60
+ */
61
+ export declare function healthRouterTemplate(): string;
62
+ /**
63
+ * src/modules/schemas/health-check.schema.ts template
64
+ */
65
+ export declare function healthCheckSchemaTemplate(): string;
66
+ /**
67
+ * deno.json template for Deno runtime
68
+ */
69
+ export declare function denoConfigTemplate(): string;
70
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,OAAO,GACR,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,MAAM,CA+CT;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CA+BzD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAQ5C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAMpC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAM3C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAgC1C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAavC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAWpC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAarC;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAmBpD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAQhD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAY7C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAkB9C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAqB7C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAQlD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAgB3C"}
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Package.json template
3
+ */
4
+ export function packageJsonTemplate({ projectName, runtime, }) {
5
+ const deps = {
6
+ '@vertz/server': '^0.1.0',
7
+ };
8
+ const devDeps = {
9
+ '@vertz/cli': '^0.1.0',
10
+ typescript: '^5.9.3',
11
+ };
12
+ // Add runtime-specific type dependencies
13
+ if (runtime === 'bun') {
14
+ devDeps['bun-types'] = '^1.0.0';
15
+ }
16
+ else if (runtime === 'node') {
17
+ devDeps['@types/node'] = '^20.0.0';
18
+ }
19
+ // deno: no additional types needed (built-in)
20
+ const scripts = {};
21
+ if (runtime === 'bun') {
22
+ scripts.dev = 'bun run src/main.ts';
23
+ scripts.build = 'bun run vertz:build';
24
+ scripts.check = 'bun run vertz:check';
25
+ }
26
+ else if (runtime === 'node') {
27
+ deps.tsx = '^4.19.0';
28
+ scripts.dev = 'tsx watch src/main.ts';
29
+ scripts.build = 'tsc && vertz build';
30
+ scripts.check = 'tsc --noEmit';
31
+ }
32
+ else if (runtime === 'deno') {
33
+ scripts.dev = 'deno run src/main.ts';
34
+ scripts.check = 'deno check src/main.ts';
35
+ }
36
+ scripts.start = 'bun run src/main.ts';
37
+ const pkg = {
38
+ name: projectName,
39
+ version: '0.1.0',
40
+ type: 'module',
41
+ license: 'MIT',
42
+ scripts,
43
+ dependencies: deps,
44
+ devDependencies: devDeps,
45
+ };
46
+ return JSON.stringify(pkg, null, 2);
47
+ }
48
+ /**
49
+ * Tsconfig.json template - runtime-specific types
50
+ */
51
+ export function tsconfigTemplate(runtime) {
52
+ let types = [];
53
+ if (runtime === 'bun') {
54
+ types = ['bun-types'];
55
+ }
56
+ else if (runtime === 'node') {
57
+ types = ['node'];
58
+ }
59
+ // deno: types = [] (deno has built-in types)
60
+ const tsconfig = {
61
+ compilerOptions: {
62
+ target: 'ES2022',
63
+ module: 'ESNext',
64
+ lib: ['ES2022'],
65
+ moduleResolution: 'bundler',
66
+ strict: true,
67
+ esModuleInterop: true,
68
+ skipLibCheck: true,
69
+ forceConsistentCasingInFileNames: true,
70
+ resolveJsonModule: true,
71
+ allowJs: true,
72
+ outDir: './dist',
73
+ rootDir: './src',
74
+ types,
75
+ },
76
+ include: ['src/**/*'],
77
+ exclude: ['node_modules', 'dist'],
78
+ };
79
+ return JSON.stringify(tsconfig, null, 2);
80
+ }
81
+ /**
82
+ * vertz.config.ts template
83
+ */
84
+ export function vertzConfigTemplate() {
85
+ return `import { defineConfig } from '@vertz/server';
86
+
87
+ export default defineConfig({
88
+ modules: [],
89
+ middlewares: [],
90
+ });
91
+ `;
92
+ }
93
+ /**
94
+ * .env template
95
+ */
96
+ export function envTemplate() {
97
+ return `# Database connection string
98
+ DATABASE_URL=
99
+
100
+ # Add more environment variables below
101
+ `;
102
+ }
103
+ /**
104
+ * .env.example template
105
+ */
106
+ export function envExampleTemplate() {
107
+ return `# Database connection string (leave blank in development)
108
+ DATABASE_URL=
109
+
110
+ # Add more environment variables below
111
+ `;
112
+ }
113
+ /**
114
+ * .gitignore template
115
+ */
116
+ export function gitignoreTemplate() {
117
+ return `# Dependencies
118
+ node_modules/
119
+ .pnp/
120
+ .pnp.js
121
+
122
+ # Build outputs
123
+ dist/
124
+ .vertz/
125
+ .env
126
+ .env.local
127
+ .env.*.local
128
+
129
+ # IDE
130
+ .idea/
131
+ .vscode/
132
+ *.swp
133
+ *.swo
134
+
135
+ # OS
136
+ .DS_Store
137
+ Thumbs.db
138
+
139
+ # Logs
140
+ *.log
141
+ npm-debug.log*
142
+ yarn-debug.log*
143
+ yarn-error.log*
144
+
145
+ # Test coverage
146
+ coverage/
147
+ `;
148
+ }
149
+ /**
150
+ * src/env.ts template
151
+ */
152
+ export function envSrcTemplate() {
153
+ return `import { envsafe, str, port } from 'envsafe';
154
+
155
+ export const env = envsafe({
156
+ DATABASE_URL: str({
157
+ default: '',
158
+ allowEmpty: true,
159
+ }),
160
+ PORT: port({
161
+ default: 3000,
162
+ }),
163
+ });
164
+ `;
165
+ }
166
+ /**
167
+ * src/app.ts template
168
+ */
169
+ export function appTemplate() {
170
+ return `import { createServer } from '@vertz/server';
171
+
172
+ export const app = createServer({
173
+ name: 'vertz-app',
174
+ requestId: {
175
+ header: 'x-request-id',
176
+ attribute: 'requestId',
177
+ },
178
+ });
179
+ `;
180
+ }
181
+ /**
182
+ * src/main.ts template
183
+ */
184
+ export function mainTemplate() {
185
+ return `import { app } from './app.js';
186
+ import { env } from './env.js';
187
+
188
+ async function main() {
189
+ const { PORT } = env;
190
+
191
+ await app.start({ port: PORT });
192
+ console.log(\`Server running at http://localhost:\${PORT}\`);
193
+ }
194
+
195
+ main();
196
+ `;
197
+ }
198
+ /**
199
+ * src/middlewares/request-id.middleware.ts template
200
+ */
201
+ export function requestIdMiddlewareTemplate() {
202
+ return `import type { Middleware } from '@vertz/server';
203
+ import { randomUUID } from 'crypto';
204
+
205
+ export const requestIdMiddleware: Middleware = {
206
+ name: 'requestId',
207
+ handler: async (req, context, next) => {
208
+ const requestId = req.headers.get('x-request-id') || randomUUID();
209
+
210
+ context.set('requestId', requestId);
211
+
212
+ const response = await next();
213
+
214
+ response.headers.set('x-request-id', requestId);
215
+
216
+ return response;
217
+ },
218
+ };
219
+ `;
220
+ }
221
+ /**
222
+ * src/modules/health.module-def.ts template
223
+ */
224
+ export function healthModuleDefTemplate() {
225
+ return `import type { ModuleDefinition } from '@vertz/server';
226
+
227
+ export const healthModuleDef = {
228
+ name: 'health',
229
+ imports: [],
230
+ } satisfies ModuleDefinition;
231
+ `;
232
+ }
233
+ /**
234
+ * src/modules/health.module.ts template
235
+ */
236
+ export function healthModuleTemplate() {
237
+ return `import type { Module } from '@vertz/server';
238
+ import { healthModuleDef } from './health.module-def.js';
239
+ import { healthRouter } from './health.router.js';
240
+ import { HealthService } from './health.service.js';
241
+
242
+ export const healthModule = {
243
+ definition: healthModuleDef,
244
+ routers: [healthRouter],
245
+ services: [HealthService],
246
+ } satisfies Module;
247
+ `;
248
+ }
249
+ /**
250
+ * src/modules/health.service.ts template
251
+ */
252
+ export function healthServiceTemplate() {
253
+ return `import type { Service } from '@vertz/server';
254
+ import { HealthCheckSchema } from './schemas/health-check.schema.js';
255
+
256
+ export class HealthService implements Service {
257
+ readonly name = 'health';
258
+
259
+ async check() {
260
+ // In a real app, check database, external services, etc.
261
+ return {
262
+ status: 'ok',
263
+ timestamp: new Date().toISOString(),
264
+ };
265
+ }
266
+ }
267
+
268
+ export const healthService = new HealthService();
269
+ `;
270
+ }
271
+ /**
272
+ * src/modules/health.router.ts template
273
+ */
274
+ export function healthRouterTemplate() {
275
+ return `import type { Router } from '@vertz/server';
276
+ import { healthService } from './health.service.js';
277
+
278
+ export const healthRouter: Router = {
279
+ routes: {
280
+ 'GET /health': {
281
+ handler: async () => {
282
+ const health = await healthService.check();
283
+ return { data: health };
284
+ },
285
+ },
286
+ 'GET /health/ready': {
287
+ handler: async () => {
288
+ // Additional readiness check
289
+ return { data: { ready: true } };
290
+ },
291
+ },
292
+ },
293
+ };
294
+ `;
295
+ }
296
+ /**
297
+ * src/modules/schemas/health-check.schema.ts template
298
+ */
299
+ export function healthCheckSchemaTemplate() {
300
+ return `import { s } from '@vertz/schema';
301
+
302
+ export const HealthCheckSchema = s.object({
303
+ status: s.string(),
304
+ timestamp: s.string(),
305
+ });
306
+ `;
307
+ }
308
+ /**
309
+ * deno.json template for Deno runtime
310
+ */
311
+ export function denoConfigTemplate() {
312
+ const config = {
313
+ imports: {
314
+ '@vertz/server': 'jsr:@vertz/server@^0.1.0',
315
+ '@vertz/schema': 'jsr:@vertz/schema@^0.1.0',
316
+ },
317
+ tasks: {
318
+ dev: 'deno run --watch src/main.ts',
319
+ check: 'deno check src/main.ts',
320
+ },
321
+ compilerOptions: {
322
+ strict: true,
323
+ },
324
+ };
325
+ return JSON.stringify(config, null, 2);
326
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Runtime options for the scaffolded project
3
+ */
4
+ export type Runtime = 'bun' | 'node' | 'deno';
5
+ /**
6
+ * Options for the scaffold function
7
+ */
8
+ export interface ScaffoldOptions {
9
+ /** Name of the project to create */
10
+ projectName: string;
11
+ /** Target runtime (bun, node, or deno) */
12
+ runtime: Runtime;
13
+ /** Whether to include example health module */
14
+ includeExample: boolean;
15
+ }
16
+ /**
17
+ * CLI options parsed from command line flags
18
+ */
19
+ export interface CliOptions {
20
+ /** Project name (positional argument or --name) */
21
+ projectName?: string;
22
+ /** Target runtime */
23
+ runtime?: Runtime;
24
+ /** Whether to include example module */
25
+ includeExample?: boolean;
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,+CAA+C;IAC/C,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wCAAwC;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@vertz/create-vertz-app",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "Create a new Vertz application",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/vertz-dev/vertz.git",
10
+ "directory": "packages/create-vertz-app"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public",
14
+ "provenance": true
15
+ },
16
+ "bin": {
17
+ "create-vertz-app": "./bin/create-vertz-app.ts"
18
+ },
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "import": "./dist/index.js",
24
+ "types": "./dist/index.d.ts"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "bin"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsc"
33
+ },
34
+ "dependencies": {
35
+ "commander": "^14.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^25.2.3",
39
+ "typescript": "^5.9.3"
40
+ },
41
+ "engines": {
42
+ "node": ">=22"
43
+ }
44
+ }