@carlos-tzin/tzin 0.1.4 → 0.1.6
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/dist/cli.js +179 -22
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,30 +1,187 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
5
|
+
const [, , cmd, ...rest] = process.argv;
|
|
3
6
|
function usage() {
|
|
4
7
|
console.error(`tzin CLI
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
Commands:
|
|
10
|
+
tzin dev [entry] [--port N] start dev server with hot reload
|
|
11
|
+
tzin build build for production
|
|
12
|
+
tzin deploy [--target <target>] deploy (node, workers)
|
|
13
|
+
tzin generate route <name> generate a route stub
|
|
14
|
+
tzin generate middleware <name> generate a middleware stub
|
|
15
|
+
tzin generate test <name> generate a test stub`);
|
|
8
16
|
process.exit(1);
|
|
9
17
|
}
|
|
10
|
-
|
|
11
|
-
if (cmd !== 'dev')
|
|
18
|
+
if (!cmd || cmd === '--help' || cmd === '-h')
|
|
12
19
|
usage();
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const entryArg = rest.find((a) => !a.startsWith('-')
|
|
21
|
-
if (entryArg)
|
|
22
|
-
|
|
23
|
-
const devServer = fileURLToPath(new URL('./dev-server.ts', import.meta.url));
|
|
24
|
-
const args = ['tsx', 'watch', '--clear-screen=false', devServer];
|
|
25
|
-
if (entry)
|
|
26
|
-
|
|
27
|
-
args.push('--port', port);
|
|
28
|
-
const child = spawn('npx', args, { stdio: 'inherit' });
|
|
29
|
-
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
30
|
-
child.on('exit', (code) => process.exit(code ?? 0));
|
|
20
|
+
// ── dev ──────────────────────────────────────────────────────────────
|
|
21
|
+
if (cmd === 'dev') {
|
|
22
|
+
let entry = '';
|
|
23
|
+
let port = '3000';
|
|
24
|
+
const portFlag = rest.indexOf('--port');
|
|
25
|
+
if (portFlag !== -1 && rest[portFlag + 1])
|
|
26
|
+
port = rest[portFlag + 1];
|
|
27
|
+
const entryArg = rest.find((a) => !a.startsWith('-'));
|
|
28
|
+
if (entryArg)
|
|
29
|
+
entry = entryArg;
|
|
30
|
+
const devServer = fileURLToPath(new URL('./dev-server.ts', import.meta.url));
|
|
31
|
+
const args = ['tsx', 'watch', '--clear-screen=false', devServer];
|
|
32
|
+
if (entry)
|
|
33
|
+
args.push(entry);
|
|
34
|
+
args.push('--port', port);
|
|
35
|
+
const child = spawn('npx', args, { stdio: 'inherit' });
|
|
36
|
+
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
37
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
// ── build ─────────────────────────────────────────────────────────────
|
|
41
|
+
if (cmd === 'build') {
|
|
42
|
+
const cwd = process.cwd();
|
|
43
|
+
// Check for tsconfig
|
|
44
|
+
if (!existsSync(resolve(cwd, 'tsconfig.json'))) {
|
|
45
|
+
console.error('No tsconfig.json found');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
// Check for src/app.ts
|
|
49
|
+
if (!existsSync(resolve(cwd, 'src/app.ts'))) {
|
|
50
|
+
console.error('No src/app.ts found. Create an app first.');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
console.log('Building...');
|
|
54
|
+
const child = spawn('npx', ['tsc', '-p', 'tsconfig.json'], {
|
|
55
|
+
cwd,
|
|
56
|
+
stdio: 'inherit',
|
|
57
|
+
});
|
|
58
|
+
child.on('exit', (code) => {
|
|
59
|
+
if (code === 0) {
|
|
60
|
+
console.log('\n✓ Build complete → dist/');
|
|
61
|
+
}
|
|
62
|
+
process.exit(code ?? 1);
|
|
63
|
+
});
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
// ── deploy ────────────────────────────────────────────────────────────
|
|
67
|
+
if (cmd === 'deploy') {
|
|
68
|
+
const cwd = process.cwd();
|
|
69
|
+
const targetFlag = rest.indexOf('--target');
|
|
70
|
+
const target = targetFlag !== -1 ? rest[targetFlag + 1] : 'node';
|
|
71
|
+
if (target === 'workers') {
|
|
72
|
+
console.log('Deploying to Cloudflare Workers...');
|
|
73
|
+
// Check for wrangler.toml
|
|
74
|
+
if (!existsSync(resolve(cwd, 'wrangler.toml'))) {
|
|
75
|
+
console.error('No wrangler.toml found. Run: npx wrangler init');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const child = spawn('npx', ['wrangler', 'deploy'], {
|
|
79
|
+
cwd,
|
|
80
|
+
stdio: 'inherit',
|
|
81
|
+
});
|
|
82
|
+
child.on('exit', (code) => process.exit(code ?? 1));
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
// Default: Node
|
|
86
|
+
console.log('Building for production...');
|
|
87
|
+
const build = spawn('npx', ['tsc', '-p', 'tsconfig.json'], {
|
|
88
|
+
cwd,
|
|
89
|
+
stdio: 'inherit',
|
|
90
|
+
});
|
|
91
|
+
build.on('exit', (code) => {
|
|
92
|
+
if (code !== 0)
|
|
93
|
+
process.exit(code ?? 1);
|
|
94
|
+
console.log('\n✓ Build complete');
|
|
95
|
+
console.log('Run: node dist/index.js');
|
|
96
|
+
process.exit(0);
|
|
97
|
+
});
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
// ── generate ─────────────────────────────────────────────────────────
|
|
101
|
+
if (cmd === 'generate' || cmd === 'g') {
|
|
102
|
+
const [sub, ...args] = rest;
|
|
103
|
+
const name = args[0];
|
|
104
|
+
if (!sub || !name) {
|
|
105
|
+
console.error('Usage: tzin generate <route|middleware|test> <name>');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const cwd = process.cwd();
|
|
109
|
+
if (sub === 'route' || sub === 'r') {
|
|
110
|
+
const dir = resolve(cwd, 'src/routes');
|
|
111
|
+
mkdirSync(dir, { recursive: true });
|
|
112
|
+
const file = resolve(dir, `${name}.ts`);
|
|
113
|
+
if (existsSync(file)) {
|
|
114
|
+
console.error(`File already exists: ${file}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
const slug = name.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
118
|
+
writeFileSync(file, `import { t } from '@carlos-tzin/tzin'
|
|
119
|
+
import { contract, impl } from '@carlos-tzin/tzin'
|
|
120
|
+
|
|
121
|
+
const ${slug} = contract({
|
|
122
|
+
method: 'GET',
|
|
123
|
+
path: '/${slug}',
|
|
124
|
+
responses: {
|
|
125
|
+
200: t.Object({ ok: t.Boolean() }),
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
export const ${slug}Route = impl(${slug}, async () => ({
|
|
130
|
+
status: 200 as const,
|
|
131
|
+
body: { ok: true },
|
|
132
|
+
}))
|
|
133
|
+
`);
|
|
134
|
+
console.log(`Created ${file}`);
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
if (sub === 'middleware' || sub === 'm') {
|
|
138
|
+
const dir = resolve(cwd, 'src/middleware');
|
|
139
|
+
mkdirSync(dir, { recursive: true });
|
|
140
|
+
const file = resolve(dir, `${name}.ts`);
|
|
141
|
+
if (existsSync(file)) {
|
|
142
|
+
console.error(`File already exists: ${file}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
const slug = name.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
146
|
+
writeFileSync(file, `import { middleware } from '@carlos-tzin/tzin'
|
|
147
|
+
|
|
148
|
+
export const ${slug} = middleware(async (ctx, next) => {
|
|
149
|
+
// TODO: add logic here
|
|
150
|
+
return next()
|
|
151
|
+
})
|
|
152
|
+
`);
|
|
153
|
+
console.log(`Created ${file}`);
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
if (sub === 'test' || sub === 't') {
|
|
157
|
+
const dir = resolve(cwd, 'tests');
|
|
158
|
+
mkdirSync(dir, { recursive: true });
|
|
159
|
+
const file = resolve(dir, `${name}.test.ts`);
|
|
160
|
+
if (existsSync(file)) {
|
|
161
|
+
console.error(`File already exists: ${file}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
writeFileSync(file, `import { describe, it, expect } from 'vitest'
|
|
165
|
+
import { createTestClient } from '@carlos-tzin/tzin/test'
|
|
166
|
+
import { app } from '../src/app.js'
|
|
167
|
+
|
|
168
|
+
describe('${name}', () => {
|
|
169
|
+
it('returns 200', async () => {
|
|
170
|
+
const api = await createTestClient(app)
|
|
171
|
+
try {
|
|
172
|
+
const res = await api.get('/${name}')
|
|
173
|
+
expect(res.status).toBe(200)
|
|
174
|
+
} finally {
|
|
175
|
+
await api.close()
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
`);
|
|
180
|
+
console.log(`Created ${file}`);
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
console.error(`Unknown generate type: ${sub}`);
|
|
184
|
+
console.error('Available: route, middleware, test');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
usage();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carlos-tzin/tzin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Contract-first TypeScript framework. Types that scale, realtime channels with presence, and an MCP server for every API.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "The tzin authors",
|