@donkeylabs/cli 2.0.14 → 2.0.16
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/package.json +1 -1
- package/src/commands/config.ts +610 -0
- package/src/commands/deploy-enhanced.ts +354 -0
- package/src/commands/deploy.ts +204 -0
- package/src/commands/generate.ts +11 -13
- package/src/commands/init-enhanced.ts +1994 -0
- package/src/deployment/manager.ts +356 -0
- package/src/index.ts +47 -19
- package/templates/starter/.env.example +0 -44
- package/templates/starter/.gitignore.template +0 -4
- package/templates/starter/donkeylabs.config.ts +0 -6
- package/templates/starter/package.json +0 -21
- package/templates/starter/src/index.ts +0 -54
- package/templates/starter/src/plugins/stats/index.ts +0 -105
- package/templates/starter/src/routes/health/handlers/ping.ts +0 -22
- package/templates/starter/src/routes/health/index.ts +0 -19
- package/templates/starter/tsconfig.json +0 -27
- package/templates/sveltekit-app/.env.example +0 -59
- package/templates/sveltekit-app/README.md +0 -103
- package/templates/sveltekit-app/bun.lock +0 -683
- package/templates/sveltekit-app/donkeylabs.config.ts +0 -12
- package/templates/sveltekit-app/package.json +0 -38
- package/templates/sveltekit-app/src/app.css +0 -40
- package/templates/sveltekit-app/src/app.html +0 -12
- package/templates/sveltekit-app/src/hooks.server.ts +0 -4
- package/templates/sveltekit-app/src/lib/components/ui/badge/badge.svelte +0 -30
- package/templates/sveltekit-app/src/lib/components/ui/badge/index.ts +0 -3
- package/templates/sveltekit-app/src/lib/components/ui/button/button.svelte +0 -48
- package/templates/sveltekit-app/src/lib/components/ui/button/index.ts +0 -9
- package/templates/sveltekit-app/src/lib/components/ui/card/card-content.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-description.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-footer.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-header.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-title.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card.svelte +0 -21
- package/templates/sveltekit-app/src/lib/components/ui/card/index.ts +0 -21
- package/templates/sveltekit-app/src/lib/components/ui/index.ts +0 -4
- package/templates/sveltekit-app/src/lib/components/ui/input/index.ts +0 -2
- package/templates/sveltekit-app/src/lib/components/ui/input/input.svelte +0 -20
- package/templates/sveltekit-app/src/lib/permissions.ts +0 -213
- package/templates/sveltekit-app/src/lib/utils/index.ts +0 -6
- package/templates/sveltekit-app/src/routes/+layout.svelte +0 -8
- package/templates/sveltekit-app/src/routes/+page.server.ts +0 -25
- package/templates/sveltekit-app/src/routes/+page.svelte +0 -680
- package/templates/sveltekit-app/src/routes/workflows/+page.server.ts +0 -23
- package/templates/sveltekit-app/src/routes/workflows/+page.svelte +0 -522
- package/templates/sveltekit-app/src/server/events.ts +0 -28
- package/templates/sveltekit-app/src/server/index.ts +0 -124
- package/templates/sveltekit-app/src/server/plugins/auth/auth.test.ts +0 -377
- package/templates/sveltekit-app/src/server/plugins/auth/index.ts +0 -815
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/001_create_users.ts +0 -25
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/002_create_sessions.ts +0 -32
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/003_create_refresh_tokens.ts +0 -33
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/004_create_passkeys.ts +0 -60
- package/templates/sveltekit-app/src/server/plugins/auth/schema.ts +0 -65
- package/templates/sveltekit-app/src/server/plugins/demo/index.ts +0 -262
- package/templates/sveltekit-app/src/server/plugins/email/email.test.ts +0 -369
- package/templates/sveltekit-app/src/server/plugins/email/index.ts +0 -411
- package/templates/sveltekit-app/src/server/plugins/email/migrations/001_create_email_tokens.ts +0 -33
- package/templates/sveltekit-app/src/server/plugins/email/schema.ts +0 -24
- package/templates/sveltekit-app/src/server/plugins/permissions/index.ts +0 -1048
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/001_create_tenants.ts +0 -63
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/002_create_roles.ts +0 -90
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/003_create_resource_grants.ts +0 -50
- package/templates/sveltekit-app/src/server/plugins/permissions/permissions.test.ts +0 -566
- package/templates/sveltekit-app/src/server/plugins/permissions/schema.ts +0 -67
- package/templates/sveltekit-app/src/server/plugins/workflow-demo/index.ts +0 -198
- package/templates/sveltekit-app/src/server/routes/auth/auth.schemas.ts +0 -66
- package/templates/sveltekit-app/src/server/routes/auth/handlers/login.handler.ts +0 -18
- package/templates/sveltekit-app/src/server/routes/auth/handlers/logout.handler.ts +0 -16
- package/templates/sveltekit-app/src/server/routes/auth/handlers/me.handler.ts +0 -20
- package/templates/sveltekit-app/src/server/routes/auth/handlers/refresh.handler.ts +0 -17
- package/templates/sveltekit-app/src/server/routes/auth/handlers/register.handler.ts +0 -19
- package/templates/sveltekit-app/src/server/routes/auth/handlers/update-profile.handler.ts +0 -21
- package/templates/sveltekit-app/src/server/routes/auth/index.ts +0 -73
- package/templates/sveltekit-app/src/server/routes/demo.ts +0 -464
- package/templates/sveltekit-app/src/server/routes/example/example.schemas.ts +0 -22
- package/templates/sveltekit-app/src/server/routes/example/handlers/greet.handler.ts +0 -21
- package/templates/sveltekit-app/src/server/routes/example/index.ts +0 -28
- package/templates/sveltekit-app/src/server/routes/permissions/index.ts +0 -248
- package/templates/sveltekit-app/src/server/routes/tenants/index.ts +0 -339
- package/templates/sveltekit-app/static/robots.txt +0 -3
- package/templates/sveltekit-app/svelte.config.ts +0 -17
- package/templates/sveltekit-app/tsconfig.json +0 -20
- package/templates/sveltekit-app/vite.config.ts +0 -12
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
// packages/cli/src/commands/deploy-enhanced.ts
|
|
2
|
+
/**
|
|
3
|
+
* Enhanced deploy command with history, versioning, and rollbacks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import pc from "picocolors";
|
|
10
|
+
import { createDeploymentManager } from "../deployment/manager";
|
|
11
|
+
|
|
12
|
+
interface DeployOptions {
|
|
13
|
+
platform: "vercel" | "cloudflare" | "aws" | "vps";
|
|
14
|
+
environment: "production" | "staging" | "development";
|
|
15
|
+
version?: string;
|
|
16
|
+
versionBump?: "major" | "minor" | "patch";
|
|
17
|
+
skipBuild?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function deployEnhancedCommand(args: string[]) {
|
|
21
|
+
const platform = args[0] as DeployOptions["platform"];
|
|
22
|
+
|
|
23
|
+
if (!platform) {
|
|
24
|
+
console.error(pc.red("❌ Platform required"));
|
|
25
|
+
console.log(pc.gray("Usage: donkeylabs deploy <platform>"));
|
|
26
|
+
console.log(pc.gray("Platforms: vercel, cloudflare, aws, vps"));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const options = await promptForOptions(platform);
|
|
31
|
+
const manager = createDeploymentManager(process.cwd(), {
|
|
32
|
+
projectName: getProjectName(),
|
|
33
|
+
platform: options.platform,
|
|
34
|
+
environment: options.environment,
|
|
35
|
+
versionStrategy: "semver",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Determine version
|
|
39
|
+
const version = options.version || manager.getNextVersion(options.versionBump);
|
|
40
|
+
|
|
41
|
+
console.log(pc.cyan(pc.bold(`\n🚀 Deploying v${version} to ${platform}\n`)));
|
|
42
|
+
|
|
43
|
+
// Pre-deployment checks
|
|
44
|
+
if (!(await runPreDeployChecks(options))) {
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Build
|
|
49
|
+
if (!options.skipBuild) {
|
|
50
|
+
console.log(pc.blue("📦 Building..."));
|
|
51
|
+
try {
|
|
52
|
+
execSync("bun run build", { stdio: "inherit" });
|
|
53
|
+
} catch {
|
|
54
|
+
await manager.recordDeployment(version, platform, "failed");
|
|
55
|
+
console.error(pc.red("❌ Build failed"));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Deploy
|
|
61
|
+
let url: string | undefined;
|
|
62
|
+
try {
|
|
63
|
+
url = await deployToPlatform(options, version);
|
|
64
|
+
console.log(pc.green(`\n✅ Deployed successfully!`));
|
|
65
|
+
console.log(pc.gray(`URL: ${url}`));
|
|
66
|
+
} catch (error) {
|
|
67
|
+
await manager.recordDeployment(version, platform, "failed");
|
|
68
|
+
console.error(pc.red("\n❌ Deployment failed"));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Record successful deployment
|
|
73
|
+
const deployment = await manager.recordDeployment(version, platform, "success", url, {
|
|
74
|
+
duration: Date.now(), // Would track actual duration
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Show deployment info
|
|
78
|
+
console.log(pc.cyan(`\n📊 Deployment recorded`));
|
|
79
|
+
console.log(pc.gray(`ID: ${deployment.id}`));
|
|
80
|
+
console.log(pc.gray(`Version: ${deployment.version}`));
|
|
81
|
+
console.log(pc.gray(`Git: ${deployment.gitSha} - ${deployment.gitMessage}`));
|
|
82
|
+
|
|
83
|
+
// Update package.json version
|
|
84
|
+
updatePackageVersion(version);
|
|
85
|
+
|
|
86
|
+
console.log(pc.green(`\n🎉 Done!`));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function promptForOptions(platform: string): Promise<DeployOptions> {
|
|
90
|
+
const prompts = await import("prompts");
|
|
91
|
+
|
|
92
|
+
const environment = await prompts.default({
|
|
93
|
+
type: "select",
|
|
94
|
+
name: "value",
|
|
95
|
+
message: "Environment:",
|
|
96
|
+
choices: [
|
|
97
|
+
{ title: "Production", value: "production" },
|
|
98
|
+
{ title: "Staging", value: "staging" },
|
|
99
|
+
{ title: "Preview", value: "preview" },
|
|
100
|
+
],
|
|
101
|
+
initial: 0,
|
|
102
|
+
}).then((r: any) => r.value);
|
|
103
|
+
|
|
104
|
+
const versionBump = await prompts.default({
|
|
105
|
+
type: "select",
|
|
106
|
+
name: "value",
|
|
107
|
+
message: "Version bump:",
|
|
108
|
+
choices: [
|
|
109
|
+
{ title: "patch (bug fixes)", value: "patch" },
|
|
110
|
+
{ title: "minor (new features)", value: "minor" },
|
|
111
|
+
{ title: "major (breaking changes)", value: "major" },
|
|
112
|
+
{ title: "custom version", value: "custom" },
|
|
113
|
+
],
|
|
114
|
+
initial: 0,
|
|
115
|
+
}).then((r: any) => r.value);
|
|
116
|
+
|
|
117
|
+
let version: string | undefined;
|
|
118
|
+
if (versionBump === "custom") {
|
|
119
|
+
version = await prompts.default({
|
|
120
|
+
type: "text",
|
|
121
|
+
name: "value",
|
|
122
|
+
message: "Version:",
|
|
123
|
+
initial: "1.0.0",
|
|
124
|
+
}).then((r: any) => r.value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
platform: platform as DeployOptions["platform"],
|
|
129
|
+
environment,
|
|
130
|
+
version,
|
|
131
|
+
versionBump: versionBump === "custom" ? undefined : versionBump,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function runPreDeployChecks(options: DeployOptions): Promise<boolean> {
|
|
136
|
+
const checks = [
|
|
137
|
+
{ name: "Git clean", fn: () => checkGitClean() },
|
|
138
|
+
{ name: "Tests pass", fn: () => checkTests() },
|
|
139
|
+
{ name: "TypeScript compiles", fn: () => checkTypescript() },
|
|
140
|
+
{ name: "Environment variables", fn: () => checkEnvVars(options.platform) },
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
console.log(pc.blue("\n🔍 Pre-deployment checks:\n"));
|
|
144
|
+
|
|
145
|
+
for (const check of checks) {
|
|
146
|
+
process.stdout.write(` ${check.name}... `);
|
|
147
|
+
try {
|
|
148
|
+
await check.fn();
|
|
149
|
+
console.log(pc.green("✓"));
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.log(pc.red("✗"));
|
|
152
|
+
console.error(pc.red(` ${error}`));
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log();
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function deployToPlatform(options: DeployOptions, version: string): Promise<string> {
|
|
162
|
+
switch (options.platform) {
|
|
163
|
+
case "vercel":
|
|
164
|
+
return deployToVercel(options, version);
|
|
165
|
+
case "cloudflare":
|
|
166
|
+
return deployToCloudflare(options, version);
|
|
167
|
+
case "aws":
|
|
168
|
+
return deployToAWS(options, version);
|
|
169
|
+
case "vps":
|
|
170
|
+
return deployToVPS(options, version);
|
|
171
|
+
default:
|
|
172
|
+
throw new Error(`Unknown platform: ${options.platform}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function deployToVercel(options: DeployOptions, version: string): Promise<string> {
|
|
177
|
+
// Ensure vercel.json exists
|
|
178
|
+
setupVercelConfig();
|
|
179
|
+
|
|
180
|
+
const args = options.environment === "production" ? ["--prod"] : [];
|
|
181
|
+
const output = execSync(`vercel ${args.join(" ")}`, {
|
|
182
|
+
encoding: "utf-8",
|
|
183
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Extract URL from output
|
|
187
|
+
const urlMatch = output.match(/https:\/\/[^\s]+/);
|
|
188
|
+
return urlMatch ? urlMatch[0] : "";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function deployToCloudflare(options: DeployOptions, version: string): Promise<string> {
|
|
192
|
+
// Ensure wrangler.toml exists
|
|
193
|
+
setupWranglerConfig(version);
|
|
194
|
+
|
|
195
|
+
execSync("wrangler deploy", { stdio: "inherit" });
|
|
196
|
+
|
|
197
|
+
// Get deployment URL
|
|
198
|
+
const output = execSync("wrangler deployment list --limit 1", {
|
|
199
|
+
encoding: "utf-8",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const urlMatch = output.match(/https:\/\/[^\s]+/);
|
|
203
|
+
return urlMatch ? urlMatch[0] : "";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function deployToAWS(options: DeployOptions, version: string): Promise<string> {
|
|
207
|
+
// SAM or Serverless Framework deployment
|
|
208
|
+
execSync("sam deploy", { stdio: "inherit" });
|
|
209
|
+
|
|
210
|
+
// Get API Gateway URL
|
|
211
|
+
const output = execSync("sam list stack-outputs", {
|
|
212
|
+
encoding: "utf-8",
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const urlMatch = output.match(/https:\/\/[^\s]+\.amazonaws\.com/);
|
|
216
|
+
return urlMatch ? urlMatch[0] : "";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function deployToVPS(options: DeployOptions, version: string): Promise<string> {
|
|
220
|
+
// Docker deployment to VPS
|
|
221
|
+
execSync("docker-compose build", { stdio: "inherit" });
|
|
222
|
+
execSync("docker-compose up -d", { stdio: "inherit" });
|
|
223
|
+
|
|
224
|
+
return "http://your-vps-ip:3000"; // Would get actual IP
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Helper functions
|
|
228
|
+
function getProjectName(): string {
|
|
229
|
+
const pkgPath = join(process.cwd(), "package.json");
|
|
230
|
+
if (!existsSync(pkgPath)) return "unknown";
|
|
231
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
232
|
+
return pkg.name;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function checkGitClean(): void {
|
|
236
|
+
const status = execSync("git status --porcelain", { encoding: "utf-8" });
|
|
237
|
+
if (status.trim()) {
|
|
238
|
+
throw new Error("Uncommitted changes. Commit or stash first.");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function checkTests(): void {
|
|
243
|
+
try {
|
|
244
|
+
execSync("bun test", { stdio: "pipe" });
|
|
245
|
+
} catch {
|
|
246
|
+
throw new Error("Tests failed");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function checkTypescript(): void {
|
|
251
|
+
try {
|
|
252
|
+
execSync("bun --bun tsc --noEmit", { stdio: "pipe" });
|
|
253
|
+
} catch {
|
|
254
|
+
throw new Error("TypeScript errors");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function checkEnvVars(platform: string): void {
|
|
259
|
+
// Platform-specific env checks
|
|
260
|
+
const envPath = join(process.cwd(), ".env.local");
|
|
261
|
+
if (!existsSync(envPath)) {
|
|
262
|
+
throw new Error(".env.local not found");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function setupVercelConfig(): void {
|
|
267
|
+
// Create vercel.json if not exists
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function setupWranglerConfig(version: string): void {
|
|
271
|
+
// Create wrangler.toml if not exists
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function updatePackageVersion(version: string): void {
|
|
275
|
+
const pkgPath = join(process.cwd(), "package.json");
|
|
276
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
277
|
+
pkg.version = version;
|
|
278
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Additional CLI commands
|
|
282
|
+
export async function deployHistoryCommand(args: string[]) {
|
|
283
|
+
const manager = createDeploymentManager(process.cwd(), {
|
|
284
|
+
projectName: getProjectName(),
|
|
285
|
+
platform: "vercel",
|
|
286
|
+
environment: "production",
|
|
287
|
+
versionStrategy: "semver",
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const history = manager.listDeployments({ limit: 10 });
|
|
291
|
+
|
|
292
|
+
console.log(pc.cyan(pc.bold("\n📜 Deployment History\n")));
|
|
293
|
+
|
|
294
|
+
for (const deployment of history) {
|
|
295
|
+
const statusIcon = deployment.status === "success" ? pc.green("✓") :
|
|
296
|
+
deployment.status === "failed" ? pc.red("✗") : pc.yellow("↺");
|
|
297
|
+
|
|
298
|
+
console.log(`${statusIcon} ${pc.bold(deployment.version)} ${pc.gray(deployment.platform)}`);
|
|
299
|
+
console.log(` ${deployment.gitSha} - ${deployment.gitMessage}`);
|
|
300
|
+
console.log(` ${deployment.timestamp}${deployment.url ? ` - ${deployment.url}` : ""}`);
|
|
301
|
+
console.log();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export async function deployRollbackCommand(args: string[]) {
|
|
306
|
+
const version = args[0];
|
|
307
|
+
|
|
308
|
+
const manager = createDeploymentManager(process.cwd(), {
|
|
309
|
+
projectName: getProjectName(),
|
|
310
|
+
platform: "vercel",
|
|
311
|
+
environment: "production",
|
|
312
|
+
versionStrategy: "semver",
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
console.log(pc.yellow(pc.bold("\n⚠️ Rollback\n")));
|
|
316
|
+
|
|
317
|
+
if (version) {
|
|
318
|
+
console.log(`Rolling back to version: ${version}`);
|
|
319
|
+
} else {
|
|
320
|
+
console.log("Rolling back to previous version");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const deployment = await manager.rollback(version);
|
|
325
|
+
if (deployment) {
|
|
326
|
+
console.log(pc.green(`\n✅ Rolled back to v${deployment.version}`));
|
|
327
|
+
}
|
|
328
|
+
} catch (error: any) {
|
|
329
|
+
console.error(pc.red(`\n❌ Rollback failed: ${error.message}`));
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export async function deployStatsCommand() {
|
|
335
|
+
const manager = createDeploymentManager(process.cwd(), {
|
|
336
|
+
projectName: getProjectName(),
|
|
337
|
+
platform: "vercel",
|
|
338
|
+
environment: "production",
|
|
339
|
+
versionStrategy: "semver",
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const stats = manager.getStats();
|
|
343
|
+
|
|
344
|
+
console.log(pc.cyan(pc.bold("\n📊 Deployment Statistics\n")));
|
|
345
|
+
console.log(`Total deployments: ${stats.totalDeployments}`);
|
|
346
|
+
console.log(`Successful: ${pc.green(stats.successfulDeployments.toString())}`);
|
|
347
|
+
console.log(`Failed: ${pc.red(stats.failedDeployments.toString())}`);
|
|
348
|
+
console.log(`Rollbacks: ${pc.yellow(stats.rollbackCount.toString())}`);
|
|
349
|
+
|
|
350
|
+
console.log(pc.cyan("\nBy Platform:"));
|
|
351
|
+
for (const [platform, count] of Object.entries(stats.deploymentsByPlatform)) {
|
|
352
|
+
console.log(` ${platform}: ${count}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// packages/cli/src/commands/deploy.ts
|
|
2
|
+
/**
|
|
3
|
+
* Deploy command for serverless platforms
|
|
4
|
+
* Currently supports Vercel
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import pc from "picocolors";
|
|
11
|
+
|
|
12
|
+
export async function deployCommand(args: string[]) {
|
|
13
|
+
const platform = args[0] || "vercel";
|
|
14
|
+
|
|
15
|
+
console.log(pc.cyan(pc.bold(`\n🚀 Deploy to ${platform}\n`)));
|
|
16
|
+
|
|
17
|
+
switch (platform) {
|
|
18
|
+
case "vercel":
|
|
19
|
+
await deployToVercel();
|
|
20
|
+
break;
|
|
21
|
+
case "help":
|
|
22
|
+
showHelp();
|
|
23
|
+
break;
|
|
24
|
+
default:
|
|
25
|
+
console.error(pc.red(`❌ Unknown platform: ${platform}`));
|
|
26
|
+
console.log(pc.yellow("Supported platforms: vercel"));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function deployToVercel() {
|
|
32
|
+
// Check if vercel CLI is installed
|
|
33
|
+
try {
|
|
34
|
+
execSync("which vercel", { stdio: "pipe" });
|
|
35
|
+
} catch {
|
|
36
|
+
console.log(pc.yellow("⚠️ Vercel CLI not found. Installing..."));
|
|
37
|
+
try {
|
|
38
|
+
execSync("npm install -g vercel", { stdio: "inherit" });
|
|
39
|
+
} catch {
|
|
40
|
+
console.error(pc.red("❌ Failed to install Vercel CLI"));
|
|
41
|
+
console.log(pc.gray("Install manually: npm install -g vercel"));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check project structure
|
|
47
|
+
const projectDir = process.cwd();
|
|
48
|
+
|
|
49
|
+
if (!existsSync(join(projectDir, "vercel.json"))) {
|
|
50
|
+
console.log(pc.yellow("⚠️ No vercel.json found. Creating serverless configuration..."));
|
|
51
|
+
await setupVercelProject(projectDir);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check for required environment variables
|
|
55
|
+
const envFile = join(projectDir, ".env.local");
|
|
56
|
+
if (existsSync(envFile)) {
|
|
57
|
+
const envContent = readFileSync(envFile, "utf-8");
|
|
58
|
+
if (!envContent.includes("DATABASE_URL")) {
|
|
59
|
+
console.error(pc.red("❌ DATABASE_URL not found in .env.local"));
|
|
60
|
+
console.log(pc.yellow("Serverless requires PostgreSQL. SQLite won't work."));
|
|
61
|
+
console.log(pc.gray("Set up a PostgreSQL database and add DATABASE_URL to .env.local"));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Build the project
|
|
67
|
+
console.log(pc.blue("📦 Building project..."));
|
|
68
|
+
try {
|
|
69
|
+
execSync("bun run build", { stdio: "inherit" });
|
|
70
|
+
} catch {
|
|
71
|
+
console.error(pc.red("❌ Build failed"));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Deploy
|
|
76
|
+
console.log(pc.blue("\n🚀 Deploying to Vercel...\n"));
|
|
77
|
+
try {
|
|
78
|
+
execSync("vercel --prod", { stdio: "inherit" });
|
|
79
|
+
} catch {
|
|
80
|
+
console.error(pc.red("❌ Deployment failed"));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(pc.green("\n✅ Deployment complete!"));
|
|
85
|
+
console.log(pc.gray("\nYour API is live at the URL shown above."));
|
|
86
|
+
console.log(pc.gray("Test it: curl https://your-app.vercel.app/api.health"));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function setupVercelProject(projectDir: string) {
|
|
90
|
+
// Create vercel.json
|
|
91
|
+
const vercelConfig = {
|
|
92
|
+
version: 2,
|
|
93
|
+
builds: [
|
|
94
|
+
{
|
|
95
|
+
src: "api/index.ts",
|
|
96
|
+
use: "@vercel/node",
|
|
97
|
+
config: {
|
|
98
|
+
includeFiles: "dist/**",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
routes: [
|
|
103
|
+
{
|
|
104
|
+
src: "/api/(.*)",
|
|
105
|
+
dest: "/api/index.ts",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
src: "/(.*)",
|
|
109
|
+
dest: "/api/index.ts",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
env: {
|
|
113
|
+
NODE_ENV: "production",
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
writeFileSync(
|
|
118
|
+
join(projectDir, "vercel.json"),
|
|
119
|
+
JSON.stringify(vercelConfig, null, 2)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Create api/index.ts if it doesn't exist
|
|
123
|
+
const apiDir = join(projectDir, "api");
|
|
124
|
+
if (!existsSync(apiDir)) {
|
|
125
|
+
require("fs").mkdirSync(apiDir, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!existsSync(join(apiDir, "index.ts"))) {
|
|
129
|
+
const handlerContent = `import { createVercelHandler } from "@donkeylabs/adapter-serverless";
|
|
130
|
+
import { createServer } from "../src/server";
|
|
131
|
+
|
|
132
|
+
// Create server instance
|
|
133
|
+
const server = createServer();
|
|
134
|
+
|
|
135
|
+
// Export handler for Vercel
|
|
136
|
+
export default createVercelHandler(() => server);
|
|
137
|
+
`;
|
|
138
|
+
writeFileSync(join(apiDir, "index.ts"), handlerContent);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Create src/server/index.ts that exports createServer
|
|
142
|
+
const serverDir = join(projectDir, "src", "server");
|
|
143
|
+
if (!existsSync(join(serverDir, "index.ts"))) {
|
|
144
|
+
const serverContent = `import { AppServer } from "@donkeylabs/server";
|
|
145
|
+
import { db } from "./db";
|
|
146
|
+
|
|
147
|
+
export function createServer() {
|
|
148
|
+
return new AppServer({
|
|
149
|
+
db,
|
|
150
|
+
logger: { level: "info" },
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
`;
|
|
154
|
+
writeFileSync(join(serverDir, "index.ts"), serverContent);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Update package.json with serverless adapter
|
|
158
|
+
const pkgPath = join(projectDir, "package.json");
|
|
159
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
160
|
+
|
|
161
|
+
if (!pkg.dependencies["@donkeylabs/adapter-serverless"]) {
|
|
162
|
+
pkg.dependencies["@donkeylabs/adapter-serverless"] = "latest";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!pkg.dependencies.pg) {
|
|
166
|
+
pkg.dependencies.pg = "^8.11.0";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
170
|
+
|
|
171
|
+
console.log(pc.green("✅ Vercel configuration created"));
|
|
172
|
+
console.log(pc.gray("Created:"));
|
|
173
|
+
console.log(pc.gray(" - vercel.json"));
|
|
174
|
+
console.log(pc.gray(" - api/index.ts (handler entry)"));
|
|
175
|
+
console.log(pc.gray(" - src/server/index.ts (server factory)"));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function showHelp() {
|
|
179
|
+
console.log(`
|
|
180
|
+
${pc.bold("Deploy Command")}
|
|
181
|
+
|
|
182
|
+
Usage:
|
|
183
|
+
donkeylabs deploy [platform]
|
|
184
|
+
|
|
185
|
+
Platforms:
|
|
186
|
+
vercel Deploy to Vercel (serverless)
|
|
187
|
+
|
|
188
|
+
Examples:
|
|
189
|
+
donkeylabs deploy vercel
|
|
190
|
+
|
|
191
|
+
Prerequisites:
|
|
192
|
+
- Vercel CLI: npm install -g vercel
|
|
193
|
+
- PostgreSQL database (SQLite won't work on serverless)
|
|
194
|
+
- DATABASE_URL in .env.local
|
|
195
|
+
|
|
196
|
+
First time setup:
|
|
197
|
+
1. Run: vercel login
|
|
198
|
+
2. Run: donkeylabs deploy vercel
|
|
199
|
+
3. Follow prompts to link project
|
|
200
|
+
|
|
201
|
+
Note: Serverless deployment requires PostgreSQL.
|
|
202
|
+
SQLite is file-based and won't persist across serverless invocations.
|
|
203
|
+
`);
|
|
204
|
+
}
|
package/src/commands/generate.ts
CHANGED
|
@@ -769,18 +769,11 @@ export async function generateCommand(_args: string[]): Promise<void> {
|
|
|
769
769
|
|
|
770
770
|
// Generate client using adapter (required for SvelteKit projects)
|
|
771
771
|
if (config.adapter) {
|
|
772
|
-
//
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
if (!existsSync(adapterPath)) {
|
|
776
|
-
console.error(pc.red(`Adapter not found: ${config.adapter}`));
|
|
777
|
-
console.error(pc.dim(`Expected path: ${adapterPath}`));
|
|
778
|
-
console.error(pc.dim(`Run: bun install`));
|
|
779
|
-
process.exit(1);
|
|
780
|
-
}
|
|
772
|
+
// Use the package's subpath export (e.g., @donkeylabs/adapter-sveltekit/generator)
|
|
773
|
+
const adapterGeneratorPath = `${config.adapter}/generator`;
|
|
781
774
|
|
|
782
775
|
try {
|
|
783
|
-
const adapterModule = await import(
|
|
776
|
+
const adapterModule = await import(adapterGeneratorPath);
|
|
784
777
|
if (!adapterModule.generateClient) {
|
|
785
778
|
console.error(pc.red(`Adapter ${config.adapter} does not export generateClient`));
|
|
786
779
|
process.exit(1);
|
|
@@ -788,9 +781,14 @@ export async function generateCommand(_args: string[]): Promise<void> {
|
|
|
788
781
|
await adapterModule.generateClient(config, serverRoutes, clientOutput);
|
|
789
782
|
generated.push(`client (${config.adapter})`);
|
|
790
783
|
} catch (e: any) {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
784
|
+
if (e.code === 'ERR_MODULE_NOT_FOUND' || e.message?.includes('Cannot find')) {
|
|
785
|
+
console.error(pc.red(`Adapter not found: ${config.adapter}`));
|
|
786
|
+
console.error(pc.dim(`Make sure the adapter is installed: bun add ${config.adapter}`));
|
|
787
|
+
} else {
|
|
788
|
+
console.error(pc.red(`Failed to run adapter generator: ${config.adapter}`));
|
|
789
|
+
console.error(pc.dim(e.message));
|
|
790
|
+
if (e.stack) console.error(pc.dim(e.stack));
|
|
791
|
+
}
|
|
794
792
|
process.exit(1);
|
|
795
793
|
}
|
|
796
794
|
} else {
|