@h-ai/deploy 0.1.0-alpha5

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/index.js ADDED
@@ -0,0 +1,1270 @@
1
+ import { z } from 'zod';
2
+ import { core, ok, err } from '@h-ai/core';
3
+ import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync, statSync } from 'fs';
4
+ import { homedir } from 'os';
5
+ import { join, dirname, relative } from 'path';
6
+ import process from 'process';
7
+ import { parse, stringify } from 'yaml';
8
+ import { execSync } from 'child_process';
9
+ import { createHash } from 'crypto';
10
+
11
+ // src/deploy-config.ts
12
+
13
+ // messages/en-US.json
14
+ var en_US_default = {
15
+ deploy_notInitialized: "Deploy module not initialized, call deploy.init() first",
16
+ deploy_initFailed: "Deploy module initialization failed: {error}",
17
+ deploy_unsupportedProviderType: "Unsupported deploy provider type: {type}",
18
+ deploy_unsupportedProvisionerType: "Unsupported service provisioner type: {type}",
19
+ deploy_configTokenRequired: "Deploy provider token is required",
20
+ deploy_authRequired: "Authentication required",
21
+ deploy_authFailed: "Authentication failed: {error}",
22
+ deploy_projectCreateFailed: "Project creation failed: {error}",
23
+ deploy_buildFailed: "Application build failed: {error}",
24
+ deploy_uploadFailed: "Build output upload failed: {error}",
25
+ deploy_deployFailed: "Deployment failed: {error}",
26
+ deploy_provisionFailed: "Service provisioning failed: {service} - {error}",
27
+ deploy_adapterMissing: "Target platform adapter not found, please install {adapter}",
28
+ deploy_scanFailed: "Application scan failed: {error}",
29
+ deploy_envVarFailed: "Environment variable setup failed: {error}",
30
+ deploy_credentialError: "Credential read/write failed: {error}",
31
+ deploy_credentialSaved: "Credentials saved",
32
+ deploy_credentialLoaded: "Credentials loaded",
33
+ deploy_configError: "Deploy config error: {error}",
34
+ deploy_credentialMissing: "Missing credential fields: {fields}",
35
+ deploy_apiError: "{service} API error ({status}): {body}",
36
+ deploy_fileUploadFailed: "File upload failed ({status}): {body}",
37
+ deploy_provisionNoResult: "No connection info returned from {service}"
38
+ };
39
+
40
+ // messages/zh-CN.json
41
+ var zh_CN_default = {
42
+ deploy_notInitialized: "Deploy \u6A21\u5757\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 deploy.init()",
43
+ deploy_initFailed: "Deploy \u6A21\u5757\u521D\u59CB\u5316\u5931\u8D25\uFF1A{error}",
44
+ deploy_unsupportedProviderType: "\u4E0D\u652F\u6301\u7684\u90E8\u7F72\u5E73\u53F0\u7C7B\u578B\uFF1A{type}",
45
+ deploy_unsupportedProvisionerType: "\u4E0D\u652F\u6301\u7684\u670D\u52A1 Provisioner \u7C7B\u578B\uFF1A{type}",
46
+ deploy_configTokenRequired: "\u90E8\u7F72\u5E73\u53F0 Token \u4E0D\u80FD\u4E3A\u7A7A",
47
+ deploy_authRequired: "\u8BF7\u5148\u5B8C\u6210\u8BA4\u8BC1",
48
+ deploy_authFailed: "\u8BA4\u8BC1\u5931\u8D25\uFF1A{error}",
49
+ deploy_projectCreateFailed: "\u9879\u76EE\u521B\u5EFA\u5931\u8D25\uFF1A{error}",
50
+ deploy_buildFailed: "\u5E94\u7528\u6784\u5EFA\u5931\u8D25\uFF1A{error}",
51
+ deploy_uploadFailed: "\u6784\u5EFA\u4EA7\u7269\u4E0A\u4F20\u5931\u8D25\uFF1A{error}",
52
+ deploy_deployFailed: "\u90E8\u7F72\u5931\u8D25\uFF1A{error}",
53
+ deploy_provisionFailed: "\u57FA\u7840\u8BBE\u65BD\u5F00\u901A\u5931\u8D25\uFF1A{service} - {error}",
54
+ deploy_adapterMissing: "\u672A\u68C0\u6D4B\u5230\u76EE\u6807\u5E73\u53F0 adapter\uFF0C\u8BF7\u5B89\u88C5 {adapter}",
55
+ deploy_scanFailed: "\u5E94\u7528\u626B\u63CF\u5931\u8D25\uFF1A{error}",
56
+ deploy_envVarFailed: "\u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E\u5931\u8D25\uFF1A{error}",
57
+ deploy_credentialError: "\u51ED\u8BC1\u8BFB\u5199\u5931\u8D25\uFF1A{error}",
58
+ deploy_credentialSaved: "\u51ED\u8BC1\u5DF2\u4FDD\u5B58",
59
+ deploy_credentialLoaded: "\u51ED\u8BC1\u5DF2\u52A0\u8F7D",
60
+ deploy_configError: "\u90E8\u7F72\u914D\u7F6E\u9519\u8BEF\uFF1A{error}",
61
+ deploy_credentialMissing: "\u7F3A\u5C11\u51ED\u8BC1\u5B57\u6BB5\uFF1A{fields}",
62
+ deploy_apiError: "{service} API \u8BF7\u6C42\u5931\u8D25\uFF08{status}\uFF09\uFF1A{body}",
63
+ deploy_fileUploadFailed: "\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF08{status}\uFF09\uFF1A{body}",
64
+ deploy_provisionNoResult: "{service} \u672A\u8FD4\u56DE\u8FDE\u63A5\u4FE1\u606F"
65
+ };
66
+
67
+ // src/deploy-i18n.ts
68
+ var deployM = core.i18n.createMessageGetter({
69
+ "zh-CN": zh_CN_default,
70
+ "en-US": en_US_default
71
+ });
72
+
73
+ // src/deploy-config.ts
74
+ var VercelProviderConfigSchema = z.object({
75
+ type: z.literal("vercel"),
76
+ token: z.string().min(1, deployM("deploy_configTokenRequired"))
77
+ });
78
+ var ProviderConfigSchema = z.discriminatedUnion("type", [
79
+ VercelProviderConfigSchema
80
+ ]);
81
+ var NeonServiceSchema = z.object({
82
+ provisioner: z.literal("neon"),
83
+ apiKey: z.string().min(1)
84
+ });
85
+ var UpstashServiceSchema = z.object({
86
+ provisioner: z.literal("upstash"),
87
+ email: z.string().email(),
88
+ apiKey: z.string().min(1)
89
+ });
90
+ var R2ServiceSchema = z.object({
91
+ provisioner: z.literal("cloudflare-r2"),
92
+ accountId: z.string().min(1),
93
+ apiToken: z.string().min(1)
94
+ });
95
+ var ResendServiceSchema = z.object({
96
+ provisioner: z.literal("resend"),
97
+ apiKey: z.string().min(1)
98
+ });
99
+ var AliyunSmsServiceSchema = z.object({
100
+ provisioner: z.literal("aliyun"),
101
+ accessKeyId: z.string().min(1),
102
+ accessKeySecret: z.string().min(1)
103
+ });
104
+ var ServicesConfigSchema = z.object({
105
+ db: z.discriminatedUnion("provisioner", [NeonServiceSchema]).optional(),
106
+ cache: z.discriminatedUnion("provisioner", [UpstashServiceSchema]).optional(),
107
+ storage: z.discriminatedUnion("provisioner", [R2ServiceSchema]).optional(),
108
+ email: z.discriminatedUnion("provisioner", [ResendServiceSchema]).optional(),
109
+ sms: z.discriminatedUnion("provisioner", [AliyunSmsServiceSchema]).optional()
110
+ }).optional();
111
+ var DeployConfigSchema = z.object({
112
+ provider: ProviderConfigSchema,
113
+ services: ServicesConfigSchema
114
+ });
115
+ var DeployErrorInfo = {
116
+ DEPLOY_FAILED: "001:500",
117
+ PROJECT_CREATE_FAILED: "002:500",
118
+ BUILD_FAILED: "003:500",
119
+ UPLOAD_FAILED: "004:500",
120
+ AUTH_REQUIRED: "005:401",
121
+ AUTH_FAILED: "006:401",
122
+ PROVISION_FAILED: "007:500",
123
+ ADAPTER_MISSING: "008:500",
124
+ SCAN_FAILED: "009:500",
125
+ ENV_VAR_FAILED: "010:500",
126
+ NOT_INITIALIZED: "011:500",
127
+ UNSUPPORTED_TYPE: "012:400",
128
+ CONFIG_ERROR: "013:500",
129
+ CREDENTIAL_ERROR: "014:500"
130
+ };
131
+ var HaiDeployError = core.error.buildHaiErrorsDef("deploy", DeployErrorInfo);
132
+
133
+ // src/deploy-credentials.ts
134
+ var logger = core.logger.child({ module: "deploy", scope: "credentials" });
135
+ function getCredentialsPath() {
136
+ return join(homedir(), ".hai", "credentials.yml");
137
+ }
138
+ function ensureDir(filePath) {
139
+ const dir = dirname(filePath);
140
+ if (!existsSync(dir)) {
141
+ mkdirSync(dir, { recursive: true });
142
+ }
143
+ }
144
+ function readCredentialsFile() {
145
+ const filePath = getCredentialsPath();
146
+ if (!existsSync(filePath)) {
147
+ return {};
148
+ }
149
+ const content = readFileSync(filePath, "utf-8");
150
+ const parsed = parse(content);
151
+ if (parsed === null || parsed === void 0 || typeof parsed !== "object") {
152
+ return {};
153
+ }
154
+ return parsed;
155
+ }
156
+ function writeCredentialsFile(data) {
157
+ const filePath = getCredentialsPath();
158
+ ensureDir(filePath);
159
+ const header = "# Managed by hai cli - do not edit manually\n";
160
+ const content = header + stringify(data);
161
+ writeFileSync(filePath, content, { encoding: "utf-8", mode: 384 });
162
+ }
163
+ function loadCredentials() {
164
+ try {
165
+ const data = readCredentialsFile();
166
+ const keys = Object.keys(data);
167
+ for (const key of keys) {
168
+ process.env[key] = String(data[key]);
169
+ }
170
+ logger.debug("Credentials loaded", { count: keys.length });
171
+ return ok(keys);
172
+ } catch (error) {
173
+ logger.error("Failed to load credentials", { error });
174
+ return err(
175
+ HaiDeployError.CREDENTIAL_ERROR,
176
+ deployM("deploy_credentialError", {
177
+ params: { error: error instanceof Error ? error.message : String(error) }
178
+ }),
179
+ error
180
+ );
181
+ }
182
+ }
183
+ function saveCredential(key, value) {
184
+ try {
185
+ const data = readCredentialsFile();
186
+ data[key] = value;
187
+ writeCredentialsFile(data);
188
+ process.env[key] = value;
189
+ logger.debug("Credential saved", { key });
190
+ return ok(void 0);
191
+ } catch (error) {
192
+ logger.error("Failed to save credential", { key, error });
193
+ return err(
194
+ HaiDeployError.CREDENTIAL_ERROR,
195
+ deployM("deploy_credentialError", {
196
+ params: { error: error instanceof Error ? error.message : String(error) }
197
+ }),
198
+ error
199
+ );
200
+ }
201
+ }
202
+ function saveCredentials(entries) {
203
+ try {
204
+ const data = readCredentialsFile();
205
+ for (const [key, value] of Object.entries(entries)) {
206
+ data[key] = value;
207
+ process.env[key] = value;
208
+ }
209
+ writeCredentialsFile(data);
210
+ logger.debug("Credentials saved", { count: Object.keys(entries).length });
211
+ return ok(void 0);
212
+ } catch (error) {
213
+ logger.error("Failed to save credentials", { error });
214
+ return err(
215
+ HaiDeployError.CREDENTIAL_ERROR,
216
+ deployM("deploy_credentialError", {
217
+ params: { error: error instanceof Error ? error.message : String(error) }
218
+ }),
219
+ error
220
+ );
221
+ }
222
+ }
223
+ var logger2 = core.logger.child({ module: "deploy", scope: "functions" });
224
+ function buildApp(appDir, buildCommand) {
225
+ logger2.info("Building application", { appDir, buildCommand });
226
+ try {
227
+ execSync(buildCommand, {
228
+ cwd: appDir,
229
+ stdio: "inherit",
230
+ env: { ...process.env }
231
+ });
232
+ return ok(void 0);
233
+ } catch (error) {
234
+ return err(
235
+ HaiDeployError.BUILD_FAILED,
236
+ deployM("deploy_buildFailed", {
237
+ params: { error: error instanceof Error ? error.message : String(error) }
238
+ }),
239
+ error
240
+ );
241
+ }
242
+ }
243
+ function resolveOutputDir(appDir, providerType) {
244
+ switch (providerType) {
245
+ case "vercel": {
246
+ const vercelOutput = join(appDir, ".vercel", "output");
247
+ if (existsSync(vercelOutput)) {
248
+ return vercelOutput;
249
+ }
250
+ return join(appDir, "build");
251
+ }
252
+ default:
253
+ return join(appDir, "build");
254
+ }
255
+ }
256
+ var logger3 = core.logger.child({ module: "deploy", scope: "scanner" });
257
+ function readYaml(filePath) {
258
+ if (!existsSync(filePath)) {
259
+ return null;
260
+ }
261
+ try {
262
+ const content = readFileSync(filePath, "utf-8");
263
+ const parsed = parse(content);
264
+ if (parsed === null || parsed === void 0 || typeof parsed !== "object") {
265
+ return null;
266
+ }
267
+ return parsed;
268
+ } catch {
269
+ return null;
270
+ }
271
+ }
272
+ function readJson(filePath) {
273
+ if (!existsSync(filePath)) {
274
+ return null;
275
+ }
276
+ try {
277
+ const content = readFileSync(filePath, "utf-8");
278
+ return JSON.parse(content);
279
+ } catch {
280
+ return null;
281
+ }
282
+ }
283
+ function hasDependency(pkg, name) {
284
+ const deps = pkg.dependencies;
285
+ const devDeps = pkg.devDependencies;
286
+ return deps !== void 0 && name in deps || devDeps !== void 0 && name in devDeps;
287
+ }
288
+ function detectRequiredServices(configDir) {
289
+ const services = [];
290
+ if (!existsSync(configDir)) {
291
+ return services;
292
+ }
293
+ const files = readdirSync(configDir);
294
+ if (files.includes("_db.yml") || files.includes("_db.yaml")) {
295
+ const dbConfig = readYaml(join(configDir, files.find((f) => f.startsWith("_db."))));
296
+ if (dbConfig !== null) {
297
+ services.push("db");
298
+ }
299
+ }
300
+ if (files.includes("_cache.yml") || files.includes("_cache.yaml")) {
301
+ const cacheConfig = readYaml(join(configDir, files.find((f) => f.startsWith("_cache."))));
302
+ if (cacheConfig !== null) {
303
+ const cacheType = cacheConfig.type;
304
+ if (cacheType === "redis") {
305
+ services.push("cache");
306
+ }
307
+ }
308
+ }
309
+ if (files.includes("_storage.yml") || files.includes("_storage.yaml")) {
310
+ const storageConfig = readYaml(join(configDir, files.find((f) => f.startsWith("_storage."))));
311
+ if (storageConfig !== null) {
312
+ detectStorageServices(storageConfig, services);
313
+ }
314
+ }
315
+ if (files.includes("_reach.yml") || files.includes("_reach.yaml")) {
316
+ const reachConfig = readYaml(join(configDir, files.find((f) => f.startsWith("_reach."))));
317
+ if (reachConfig !== null) {
318
+ detectReachServices(reachConfig, services);
319
+ }
320
+ }
321
+ return services;
322
+ }
323
+ function detectStorageServices(config, services) {
324
+ if (config.type === "s3") {
325
+ services.push("storage");
326
+ return;
327
+ }
328
+ const providers = config.providers;
329
+ if (providers === void 0) {
330
+ return;
331
+ }
332
+ for (const providerConfig of Object.values(providers)) {
333
+ const typed = providerConfig;
334
+ if (typed?.type === "s3") {
335
+ services.push("storage");
336
+ return;
337
+ }
338
+ }
339
+ }
340
+ function detectReachServices(config, services) {
341
+ const providers = config.providers;
342
+ if (!Array.isArray(providers)) {
343
+ return;
344
+ }
345
+ let hasEmail = false;
346
+ let hasSms = false;
347
+ for (const provider of providers) {
348
+ const providerType = provider.type;
349
+ if (!hasEmail && (providerType === "smtp" || providerType === "api")) {
350
+ hasEmail = true;
351
+ services.push("email");
352
+ }
353
+ if (!hasSms && providerType === "aliyun-sms") {
354
+ hasSms = true;
355
+ services.push("sms");
356
+ }
357
+ }
358
+ }
359
+ async function scanApp(appDir) {
360
+ logger3.debug("Scanning application", { appDir });
361
+ try {
362
+ const pkg = readJson(join(appDir, "package.json"));
363
+ if (pkg === null) {
364
+ return err(
365
+ HaiDeployError.SCAN_FAILED,
366
+ deployM("deploy_scanFailed", { params: { error: "package.json not found" } })
367
+ );
368
+ }
369
+ const appName = extractAppName(pkg);
370
+ const isSvelteKit = existsSync(join(appDir, "svelte.config.js")) || existsSync(join(appDir, "svelte.config.ts"));
371
+ const adapterInstalled = hasDependency(pkg, "@sveltejs/adapter-vercel");
372
+ const configDir = join(appDir, "config");
373
+ const configServices = detectRequiredServices(configDir);
374
+ const depServices = detectServicesFromDependencies(pkg);
375
+ const requiredServices = Array.from(/* @__PURE__ */ new Set([...configServices, ...depServices]));
376
+ const scripts = pkg.scripts;
377
+ const buildCommand = scripts?.build ?? "pnpm build";
378
+ const scanResult = {
379
+ appName,
380
+ isSvelteKit,
381
+ adapterInstalled,
382
+ requiredServices,
383
+ buildCommand
384
+ };
385
+ logger3.info("Application scanned", {
386
+ appName,
387
+ isSvelteKit,
388
+ adapterInstalled,
389
+ requiredServices
390
+ });
391
+ return ok(scanResult);
392
+ } catch (error) {
393
+ logger3.error("Application scan failed", { appDir, error });
394
+ return err(
395
+ HaiDeployError.SCAN_FAILED,
396
+ deployM("deploy_scanFailed", {
397
+ params: { error: error instanceof Error ? error.message : String(error) }
398
+ }),
399
+ error
400
+ );
401
+ }
402
+ }
403
+ function detectServicesFromDependencies(pkg) {
404
+ const depMap = {
405
+ "@h-ai/reldb": "db",
406
+ "@h-ai/cache": "cache",
407
+ "@h-ai/storage": "storage"
408
+ };
409
+ const services = [];
410
+ for (const [depName, serviceType] of Object.entries(depMap)) {
411
+ if (hasDependency(pkg, depName)) {
412
+ services.push(serviceType);
413
+ }
414
+ }
415
+ return services;
416
+ }
417
+ function extractAppName(pkg) {
418
+ const name = pkg.name ?? "app";
419
+ const slashIndex = name.indexOf("/");
420
+ if (slashIndex >= 0) {
421
+ return name.slice(slashIndex + 1);
422
+ }
423
+ return name;
424
+ }
425
+ var logger4 = core.logger.child({ module: "deploy", scope: "provider-vercel" });
426
+ var VERCEL_API = "https://api.vercel.com";
427
+ var POLL_INTERVAL = 3e3;
428
+ var MAX_POLL_ATTEMPTS = 60;
429
+ function toDeployError(codeDef, messageKey, error) {
430
+ return {
431
+ ...codeDef,
432
+ message: deployM(messageKey, {
433
+ params: { error: error instanceof Error ? error.message : String(error) }
434
+ }),
435
+ cause: error
436
+ };
437
+ }
438
+ async function vercelFetch(token, path, options) {
439
+ const url = `${VERCEL_API}${path}`;
440
+ const response = await fetch(url, {
441
+ ...options,
442
+ headers: {
443
+ "Authorization": `Bearer ${token}`,
444
+ "Content-Type": "application/json",
445
+ ...options?.headers
446
+ }
447
+ });
448
+ if (!response.ok) {
449
+ const body = await response.text();
450
+ throw new Error(deployM("deploy_apiError", { params: { service: "Vercel", status: String(response.status), body } }));
451
+ }
452
+ return response.json();
453
+ }
454
+ function collectFiles(dir, baseDir) {
455
+ const results = [];
456
+ const entries = readdirSync(dir, { withFileTypes: true });
457
+ for (const entry of entries) {
458
+ const fullPath = join(dir, entry.name);
459
+ if (entry.isDirectory()) {
460
+ results.push(...collectFiles(fullPath, baseDir));
461
+ } else {
462
+ results.push(relative(baseDir, fullPath));
463
+ }
464
+ }
465
+ return results;
466
+ }
467
+ function createVercelProvider() {
468
+ let token = null;
469
+ return {
470
+ name: "vercel",
471
+ async authenticate(apiToken) {
472
+ logger4.debug("Authenticating with Vercel");
473
+ try {
474
+ const user = await vercelFetch(
475
+ apiToken,
476
+ "/v2/user"
477
+ );
478
+ token = apiToken;
479
+ const username = user.user.username || user.user.email;
480
+ logger4.info("Vercel authenticated", { user: username });
481
+ return ok(username);
482
+ } catch (error) {
483
+ logger4.error("Vercel authentication failed", { error });
484
+ return err(toDeployError(HaiDeployError.AUTH_FAILED, "deploy_authFailed", error));
485
+ }
486
+ },
487
+ async createProject(projectName) {
488
+ if (!token) {
489
+ return err(
490
+ HaiDeployError.AUTH_REQUIRED,
491
+ deployM("deploy_authRequired")
492
+ );
493
+ }
494
+ logger4.debug("Creating Vercel project", { projectName });
495
+ try {
496
+ const existingId = await findExistingProject(token, projectName);
497
+ if (existingId !== null) {
498
+ logger4.info("Vercel project found", { projectName, projectId: existingId });
499
+ return ok(existingId);
500
+ }
501
+ const project = await vercelFetch(
502
+ token,
503
+ "/v10/projects",
504
+ {
505
+ method: "POST",
506
+ body: JSON.stringify({
507
+ name: projectName,
508
+ framework: "sveltekit"
509
+ })
510
+ }
511
+ );
512
+ logger4.info("Vercel project created", { projectName, projectId: project.id });
513
+ return ok(project.id);
514
+ } catch (error) {
515
+ logger4.error("Vercel project creation failed", { projectName, error });
516
+ return err(toDeployError(HaiDeployError.PROJECT_CREATE_FAILED, "deploy_projectCreateFailed", error));
517
+ }
518
+ },
519
+ async setEnvVars(projectId, envVars) {
520
+ if (!token) {
521
+ return err(
522
+ HaiDeployError.AUTH_REQUIRED,
523
+ deployM("deploy_authRequired")
524
+ );
525
+ }
526
+ logger4.debug("Setting Vercel env vars", { projectId, count: Object.keys(envVars).length });
527
+ try {
528
+ const envEntries = Object.entries(envVars).map(([key, value]) => ({
529
+ key,
530
+ value,
531
+ type: "encrypted",
532
+ target: ["production", "preview", "development"]
533
+ }));
534
+ await vercelFetch(
535
+ token,
536
+ `/v10/projects/${projectId}/env`,
537
+ {
538
+ method: "POST",
539
+ body: JSON.stringify(envEntries)
540
+ }
541
+ );
542
+ logger4.info("Vercel env vars set", { projectId, count: envEntries.length });
543
+ return ok(void 0);
544
+ } catch (error) {
545
+ logger4.error("Vercel env vars setup failed", { projectId, error });
546
+ return err(toDeployError(HaiDeployError.ENV_VAR_FAILED, "deploy_envVarFailed", error));
547
+ }
548
+ },
549
+ async deploy(projectId, outputDir) {
550
+ if (!token) {
551
+ return err(
552
+ HaiDeployError.AUTH_REQUIRED,
553
+ deployM("deploy_authRequired")
554
+ );
555
+ }
556
+ logger4.debug("Deploying to Vercel", { projectId, outputDir });
557
+ try {
558
+ const filePaths = collectFiles(outputDir, outputDir);
559
+ const files = [];
560
+ for (const filePath of filePaths) {
561
+ const fullPath = join(outputDir, filePath);
562
+ const content = readFileSync(fullPath);
563
+ const sha = createHash("sha1").update(content).digest("hex");
564
+ const size = statSync(fullPath).size;
565
+ await uploadFile(token, content, sha);
566
+ files.push({ file: filePath, sha, size });
567
+ }
568
+ logger4.debug("Files uploaded", { count: files.length });
569
+ const deployment = await vercelFetch(
570
+ token,
571
+ "/v13/deployments",
572
+ {
573
+ method: "POST",
574
+ body: JSON.stringify({
575
+ name: projectId,
576
+ files,
577
+ projectSettings: { framework: "sveltekit" }
578
+ })
579
+ }
580
+ );
581
+ const finalStatus = await pollDeploymentStatus(token, deployment.id);
582
+ const result = {
583
+ url: `https://${deployment.url}`,
584
+ deploymentId: deployment.id,
585
+ status: finalStatus,
586
+ envVarsSet: []
587
+ };
588
+ logger4.info("Vercel deployment complete", {
589
+ deploymentId: deployment.id,
590
+ url: result.url,
591
+ status: finalStatus
592
+ });
593
+ return ok(result);
594
+ } catch (error) {
595
+ logger4.error("Vercel deployment failed", { projectId, error });
596
+ return err(toDeployError(HaiDeployError.UPLOAD_FAILED, "deploy_uploadFailed", error));
597
+ }
598
+ }
599
+ };
600
+ }
601
+ async function findExistingProject(token, projectName) {
602
+ try {
603
+ const project = await vercelFetch(token, `/v10/projects/${projectName}`);
604
+ return project.id;
605
+ } catch {
606
+ return null;
607
+ }
608
+ }
609
+ async function uploadFile(token, content, sha) {
610
+ const url = `${VERCEL_API}/v2/files`;
611
+ const response = await fetch(url, {
612
+ method: "POST",
613
+ headers: {
614
+ "Authorization": `Bearer ${token}`,
615
+ "Content-Type": "application/octet-stream",
616
+ "x-vercel-digest": sha
617
+ },
618
+ body: new Blob([content.buffer])
619
+ });
620
+ if (!response.ok && response.status !== 409) {
621
+ const body = await response.text();
622
+ throw new Error(deployM("deploy_fileUploadFailed", { params: { status: String(response.status), body } }));
623
+ }
624
+ }
625
+ async function pollDeploymentStatus(token, deploymentId) {
626
+ for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
627
+ const deployment = await vercelFetch(
628
+ token,
629
+ `/v13/deployments/${deploymentId}`
630
+ );
631
+ if (deployment.readyState === "READY") {
632
+ return "ready";
633
+ }
634
+ if (deployment.readyState === "ERROR" || deployment.readyState === "CANCELED") {
635
+ return "error";
636
+ }
637
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
638
+ }
639
+ return "building";
640
+ }
641
+ var logger5 = core.logger.child({ module: "deploy", scope: "provisioner-aliyun" });
642
+ function createAliyunProvisioner() {
643
+ let accessKey = null;
644
+ let secretKey = null;
645
+ return {
646
+ name: "aliyun",
647
+ serviceType: "sms",
648
+ async authenticate(credentials) {
649
+ logger5.debug("Authenticating with Aliyun");
650
+ try {
651
+ const akId = credentials.accessKeyId ?? credentials.access_key_id ?? credentials.access_key ?? "";
652
+ const akSecret = credentials.accessKeySecret ?? credentials.access_key_secret ?? credentials.secret_key ?? "";
653
+ if (!akId || !akSecret) {
654
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "access_key_id, access_key_secret" } }));
655
+ }
656
+ accessKey = akId;
657
+ secretKey = akSecret;
658
+ logger5.info("Aliyun authenticated", { accessKeyId: `${akId.slice(0, 6)}***` });
659
+ return ok(akId);
660
+ } catch (error) {
661
+ logger5.error("Aliyun authentication failed", { error });
662
+ return err(
663
+ HaiDeployError.AUTH_FAILED,
664
+ deployM("deploy_authFailed", {
665
+ params: { error: error instanceof Error ? error.message : String(error) }
666
+ }),
667
+ error
668
+ );
669
+ }
670
+ },
671
+ async provision(_appName) {
672
+ if (!accessKey || !secretKey) {
673
+ return err(
674
+ HaiDeployError.AUTH_REQUIRED,
675
+ deployM("deploy_authRequired")
676
+ );
677
+ }
678
+ logger5.debug("Provisioning Aliyun SMS service");
679
+ return ok({
680
+ serviceType: "sms",
681
+ provisionerName: "aliyun",
682
+ envVars: {
683
+ HAI_REACH_SMS_ACCESS_KEY: accessKey,
684
+ HAI_REACH_SMS_SECRET_KEY: secretKey
685
+ },
686
+ resourceInfo: "aliyun:verify-only"
687
+ });
688
+ }
689
+ };
690
+ }
691
+ var logger6 = core.logger.child({ module: "deploy", scope: "provisioner-neon" });
692
+ var NEON_API = "https://console.neon.tech/api/v2";
693
+ function createNeonProvisioner() {
694
+ let token = null;
695
+ return {
696
+ name: "neon",
697
+ serviceType: "db",
698
+ async authenticate(credentials) {
699
+ logger6.debug("Authenticating with Neon");
700
+ try {
701
+ const apiToken = credentials.apiKey ?? credentials.token ?? credentials.api_key ?? "";
702
+ if (!apiToken) {
703
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "token" } }));
704
+ }
705
+ const res = await fetch(`${NEON_API}/projects`, {
706
+ headers: { Authorization: `Bearer ${apiToken}` }
707
+ });
708
+ if (!res.ok) {
709
+ throw new Error(deployM("deploy_apiError", { params: { service: "Neon", status: String(res.status), body: await res.text() } }));
710
+ }
711
+ token = apiToken;
712
+ logger6.info("Neon authenticated");
713
+ return ok("neon-user");
714
+ } catch (error) {
715
+ logger6.error("Neon authentication failed", { error });
716
+ return err(
717
+ HaiDeployError.AUTH_FAILED,
718
+ deployM("deploy_authFailed", {
719
+ params: { error: error instanceof Error ? error.message : String(error) }
720
+ }),
721
+ error
722
+ );
723
+ }
724
+ },
725
+ async provision(appName) {
726
+ if (!token) {
727
+ return err(
728
+ HaiDeployError.AUTH_REQUIRED,
729
+ deployM("deploy_authRequired")
730
+ );
731
+ }
732
+ logger6.debug("Provisioning Neon database", { appName });
733
+ try {
734
+ const res = await fetch(`${NEON_API}/projects`, {
735
+ method: "POST",
736
+ headers: {
737
+ "Authorization": `Bearer ${token}`,
738
+ "Content-Type": "application/json"
739
+ },
740
+ body: JSON.stringify({
741
+ project: {
742
+ name: `${appName}-db`,
743
+ pg_version: 16
744
+ }
745
+ })
746
+ });
747
+ if (!res.ok) {
748
+ throw new Error(deployM("deploy_apiError", { params: { service: "Neon", status: String(res.status), body: await res.text() } }));
749
+ }
750
+ const data = await res.json();
751
+ const connectionUri = data.connection_uris?.[0]?.connection_uri;
752
+ if (!connectionUri) {
753
+ throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Neon" } }));
754
+ }
755
+ logger6.info("Neon database provisioned", { projectId: data.project.id });
756
+ return ok({
757
+ serviceType: "db",
758
+ provisionerName: "neon",
759
+ envVars: {
760
+ HAI_RELDB_URL: connectionUri
761
+ },
762
+ resourceInfo: `neon-project:${data.project.id}`
763
+ });
764
+ } catch (error) {
765
+ logger6.error("Neon provisioning failed", { appName, error });
766
+ return err(
767
+ HaiDeployError.PROVISION_FAILED,
768
+ deployM("deploy_provisionFailed", {
769
+ params: {
770
+ service: "neon",
771
+ error: error instanceof Error ? error.message : String(error)
772
+ }
773
+ }),
774
+ error
775
+ );
776
+ }
777
+ }
778
+ };
779
+ }
780
+ var logger7 = core.logger.child({ module: "deploy", scope: "provisioner-r2" });
781
+ var CF_API = "https://api.cloudflare.com/client/v4";
782
+ function createR2Provisioner() {
783
+ let token = null;
784
+ let accountId = null;
785
+ return {
786
+ name: "r2",
787
+ serviceType: "storage",
788
+ async authenticate(credentials) {
789
+ logger7.debug("Authenticating with Cloudflare");
790
+ try {
791
+ const acctId = credentials.accountId ?? credentials.account_id ?? "";
792
+ const apiTok = credentials.apiToken ?? credentials.api_token ?? credentials.token ?? "";
793
+ if (!acctId || !apiTok) {
794
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "account_id, api_token" } }));
795
+ }
796
+ const res = await fetch(`${CF_API}/accounts/${acctId}/r2/buckets`, {
797
+ headers: { Authorization: `Bearer ${apiTok}` }
798
+ });
799
+ if (!res.ok) {
800
+ throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(res.status), body: await res.text() } }));
801
+ }
802
+ accountId = acctId;
803
+ token = apiTok;
804
+ logger7.info("Cloudflare R2 authenticated", { accountId: acctId });
805
+ return ok(acctId);
806
+ } catch (error) {
807
+ logger7.error("Cloudflare R2 authentication failed", { error });
808
+ return err(
809
+ HaiDeployError.AUTH_FAILED,
810
+ deployM("deploy_authFailed", {
811
+ params: { error: error instanceof Error ? error.message : String(error) }
812
+ }),
813
+ error
814
+ );
815
+ }
816
+ },
817
+ async provision(appName) {
818
+ if (!token || !accountId) {
819
+ return err(
820
+ HaiDeployError.AUTH_REQUIRED,
821
+ deployM("deploy_authRequired")
822
+ );
823
+ }
824
+ const bucketName = `${appName}-storage`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
825
+ logger7.debug("Provisioning R2 bucket", { appName, bucketName });
826
+ try {
827
+ const createRes = await fetch(
828
+ `${CF_API}/accounts/${accountId}/r2/buckets`,
829
+ {
830
+ method: "POST",
831
+ headers: {
832
+ "Authorization": `Bearer ${token}`,
833
+ "Content-Type": "application/json"
834
+ },
835
+ body: JSON.stringify({ name: bucketName })
836
+ }
837
+ );
838
+ if (!createRes.ok && createRes.status !== 409) {
839
+ throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(createRes.status), body: await createRes.text() } }));
840
+ }
841
+ const tokenRes = await fetch(
842
+ `${CF_API}/user/tokens`,
843
+ {
844
+ method: "POST",
845
+ headers: {
846
+ "Authorization": `Bearer ${token}`,
847
+ "Content-Type": "application/json"
848
+ },
849
+ body: JSON.stringify({
850
+ name: `${appName}-r2-token`,
851
+ policies: [{
852
+ effect: "allow",
853
+ permission_groups: [{ id: "r2-read-write" }],
854
+ resources: { [`com.cloudflare.edge.r2.bucket.${accountId}_default_${bucketName}`]: "*" }
855
+ }]
856
+ })
857
+ }
858
+ );
859
+ let accessKey = "";
860
+ let secretKey = "";
861
+ if (tokenRes.ok) {
862
+ const tokenData = await tokenRes.json();
863
+ accessKey = tokenData.result.id;
864
+ secretKey = tokenData.result.value;
865
+ }
866
+ logger7.info("R2 bucket provisioned", { bucketName });
867
+ return ok({
868
+ serviceType: "storage",
869
+ provisionerName: "r2",
870
+ envVars: {
871
+ HAI_STORAGE_S3_ENDPOINT: `https://${accountId}.r2.cloudflarestorage.com`,
872
+ HAI_STORAGE_S3_BUCKET: bucketName,
873
+ HAI_STORAGE_S3_ACCESS_KEY: accessKey,
874
+ HAI_STORAGE_S3_SECRET_KEY: secretKey
875
+ },
876
+ resourceInfo: `r2-bucket:${bucketName}`
877
+ });
878
+ } catch (error) {
879
+ logger7.error("R2 provisioning failed", { appName, error });
880
+ return err(
881
+ HaiDeployError.PROVISION_FAILED,
882
+ deployM("deploy_provisionFailed", {
883
+ params: {
884
+ service: "r2",
885
+ error: error instanceof Error ? error.message : String(error)
886
+ }
887
+ }),
888
+ error
889
+ );
890
+ }
891
+ }
892
+ };
893
+ }
894
+ var logger8 = core.logger.child({ module: "deploy", scope: "provisioner-resend" });
895
+ var RESEND_API = "https://api.resend.com";
896
+ function createResendProvisioner() {
897
+ let token = null;
898
+ return {
899
+ name: "resend",
900
+ serviceType: "email",
901
+ async authenticate(credentials) {
902
+ logger8.debug("Authenticating with Resend");
903
+ try {
904
+ const apiToken = credentials.apiKey ?? credentials.api_key ?? credentials.token ?? "";
905
+ if (!apiToken) {
906
+ throw new Error('Missing "api_key" in credentials');
907
+ }
908
+ const res = await fetch(`${RESEND_API}/domains`, {
909
+ headers: { Authorization: `Bearer ${apiToken}` }
910
+ });
911
+ if (!res.ok) {
912
+ throw new Error(`Resend API ${res.status}: ${await res.text()}`);
913
+ }
914
+ token = apiToken;
915
+ logger8.info("Resend authenticated");
916
+ return ok("resend-user");
917
+ } catch (error) {
918
+ logger8.error("Resend authentication failed", { error });
919
+ return err(
920
+ HaiDeployError.AUTH_FAILED,
921
+ deployM("deploy_authFailed", {
922
+ params: { error: error instanceof Error ? error.message : String(error) }
923
+ }),
924
+ error
925
+ );
926
+ }
927
+ },
928
+ async provision(_appName) {
929
+ if (!token) {
930
+ return err(
931
+ HaiDeployError.AUTH_REQUIRED,
932
+ deployM("deploy_authRequired")
933
+ );
934
+ }
935
+ logger8.debug("Provisioning Resend email service");
936
+ return ok({
937
+ serviceType: "email",
938
+ provisionerName: "resend",
939
+ envVars: {
940
+ HAI_REACH_RESEND_KEY: token
941
+ },
942
+ resourceInfo: "resend:verify-only"
943
+ });
944
+ }
945
+ };
946
+ }
947
+ var logger9 = core.logger.child({ module: "deploy", scope: "provisioner-upstash" });
948
+ var UPSTASH_API = "https://api.upstash.com";
949
+ function createUpstashProvisioner() {
950
+ let apiKey = null;
951
+ let email = null;
952
+ return {
953
+ name: "upstash",
954
+ serviceType: "cache",
955
+ async authenticate(credentials) {
956
+ logger9.debug("Authenticating with Upstash");
957
+ try {
958
+ const userEmail = credentials.email ?? "";
959
+ const userKey = credentials.apiKey ?? credentials.api_key ?? credentials.token ?? "";
960
+ if (!userEmail || !userKey) {
961
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "email, api_key" } }));
962
+ }
963
+ const res = await fetch(`${UPSTASH_API}/v2/redis/databases`, {
964
+ headers: {
965
+ Authorization: `Basic ${btoa(`${userEmail}:${userKey}`)}`
966
+ }
967
+ });
968
+ if (!res.ok) {
969
+ throw new Error(deployM("deploy_apiError", { params: { service: "Upstash", status: String(res.status), body: await res.text() } }));
970
+ }
971
+ email = userEmail;
972
+ apiKey = userKey;
973
+ logger9.info("Upstash authenticated", { email: userEmail });
974
+ return ok(userEmail);
975
+ } catch (error) {
976
+ logger9.error("Upstash authentication failed", { error });
977
+ return err(
978
+ HaiDeployError.AUTH_FAILED,
979
+ deployM("deploy_authFailed", {
980
+ params: { error: error instanceof Error ? error.message : String(error) }
981
+ }),
982
+ error
983
+ );
984
+ }
985
+ },
986
+ async provision(appName) {
987
+ if (!apiKey || !email) {
988
+ return err(
989
+ HaiDeployError.AUTH_REQUIRED,
990
+ deployM("deploy_authRequired")
991
+ );
992
+ }
993
+ logger9.debug("Provisioning Upstash Redis", { appName });
994
+ try {
995
+ const res = await fetch(`${UPSTASH_API}/v2/redis/database`, {
996
+ method: "POST",
997
+ headers: {
998
+ "Authorization": `Basic ${btoa(`${email}:${apiKey}`)}`,
999
+ "Content-Type": "application/json"
1000
+ },
1001
+ body: JSON.stringify({
1002
+ name: `${appName}-cache`,
1003
+ region: "global",
1004
+ tls: true
1005
+ })
1006
+ });
1007
+ if (!res.ok) {
1008
+ throw new Error(deployM("deploy_apiError", { params: { service: "Upstash", status: String(res.status), body: await res.text() } }));
1009
+ }
1010
+ const data = await res.json();
1011
+ logger9.info("Upstash Redis provisioned", { databaseId: data.database_id });
1012
+ return ok({
1013
+ serviceType: "cache",
1014
+ provisionerName: "upstash",
1015
+ envVars: {
1016
+ HAI_CACHE_UPSTASH_URL: data.rest_url,
1017
+ HAI_CACHE_UPSTASH_TOKEN: data.rest_token
1018
+ },
1019
+ resourceInfo: `upstash-db:${data.database_id}`
1020
+ });
1021
+ } catch (error) {
1022
+ logger9.error("Upstash provisioning failed", { appName, error });
1023
+ return err(
1024
+ HaiDeployError.PROVISION_FAILED,
1025
+ deployM("deploy_provisionFailed", {
1026
+ params: {
1027
+ service: "upstash",
1028
+ error: error instanceof Error ? error.message : String(error)
1029
+ }
1030
+ }),
1031
+ error
1032
+ );
1033
+ }
1034
+ }
1035
+ };
1036
+ }
1037
+
1038
+ // src/deploy-main.ts
1039
+ var logger10 = core.logger.child({ module: "deploy", scope: "main" });
1040
+ var currentConfig = null;
1041
+ var currentProvider = null;
1042
+ var currentProvisioners = /* @__PURE__ */ new Map();
1043
+ var initInProgress = false;
1044
+ var notInitialized = core.module.createNotInitializedKit(
1045
+ HaiDeployError.NOT_INITIALIZED,
1046
+ () => deployM("deploy_notInitialized")
1047
+ );
1048
+ function createProviderByType(type) {
1049
+ switch (type) {
1050
+ case "vercel":
1051
+ return createVercelProvider();
1052
+ default:
1053
+ throw new Error(deployM("deploy_unsupportedProviderType", { params: { type } }));
1054
+ }
1055
+ }
1056
+ function createProvisionerByName(provisioner) {
1057
+ switch (provisioner) {
1058
+ case "neon":
1059
+ return createNeonProvisioner();
1060
+ case "upstash":
1061
+ return createUpstashProvisioner();
1062
+ case "cloudflare-r2":
1063
+ return createR2Provisioner();
1064
+ case "resend":
1065
+ return createResendProvisioner();
1066
+ case "aliyun":
1067
+ return createAliyunProvisioner();
1068
+ default:
1069
+ throw new Error(deployM("deploy_unsupportedProvisionerType", { params: { type: provisioner } }));
1070
+ }
1071
+ }
1072
+ function extractCredentials(serviceConfig) {
1073
+ const result = {};
1074
+ for (const [key, value] of Object.entries(serviceConfig)) {
1075
+ if (key !== "provisioner" && typeof value === "string") {
1076
+ result[key] = value;
1077
+ }
1078
+ }
1079
+ return result;
1080
+ }
1081
+ var deploy = {
1082
+ async init(config) {
1083
+ if (initInProgress) {
1084
+ logger10.warn("Deploy init already in progress, skipping concurrent call");
1085
+ return err(
1086
+ HaiDeployError.CONFIG_ERROR,
1087
+ deployM("deploy_configError", { params: { error: "init already in progress" } })
1088
+ );
1089
+ }
1090
+ initInProgress = true;
1091
+ try {
1092
+ if (currentProvider) {
1093
+ logger10.warn("Deploy module is already initialized, reinitializing");
1094
+ await deploy.close();
1095
+ }
1096
+ logger10.info("Initializing deploy module");
1097
+ const parseResult = DeployConfigSchema.safeParse(config);
1098
+ if (!parseResult.success) {
1099
+ logger10.error("Deploy config validation failed", { error: parseResult.error.message });
1100
+ return err(
1101
+ HaiDeployError.CONFIG_ERROR,
1102
+ deployM("deploy_configError", { params: { error: parseResult.error.message } }),
1103
+ parseResult.error
1104
+ );
1105
+ }
1106
+ const parsed = parseResult.data;
1107
+ try {
1108
+ const provider = createProviderByType(parsed.provider.type);
1109
+ const authResult = await provider.authenticate(parsed.provider.token);
1110
+ if (!authResult.success) {
1111
+ return err(authResult.error);
1112
+ }
1113
+ logger10.info("Provider authenticated", { provider: parsed.provider.type, user: authResult.data });
1114
+ currentProvisioners.clear();
1115
+ if (parsed.services) {
1116
+ const serviceEntries = Object.entries(parsed.services);
1117
+ for (const [serviceType, serviceConfig] of serviceEntries) {
1118
+ if (!serviceConfig)
1119
+ continue;
1120
+ const provisioner = createProvisionerByName(serviceConfig.provisioner);
1121
+ const credentials = extractCredentials(serviceConfig);
1122
+ const provAuthResult = await provisioner.authenticate(credentials);
1123
+ if (!provAuthResult.success) {
1124
+ logger10.warn("Provisioner authentication failed", {
1125
+ service: serviceType,
1126
+ provisioner: serviceConfig.provisioner
1127
+ });
1128
+ } else {
1129
+ logger10.info("Provisioner authenticated", {
1130
+ service: serviceType,
1131
+ user: provAuthResult.data
1132
+ });
1133
+ }
1134
+ currentProvisioners.set(serviceType, provisioner);
1135
+ }
1136
+ }
1137
+ currentProvider = provider;
1138
+ currentConfig = parsed;
1139
+ logger10.info("Deploy module initialized", {
1140
+ provider: parsed.provider.type,
1141
+ provisioners: [...currentProvisioners.keys()]
1142
+ });
1143
+ return ok(void 0);
1144
+ } catch (error) {
1145
+ currentProvider = null;
1146
+ currentConfig = null;
1147
+ currentProvisioners.clear();
1148
+ logger10.error("Deploy module initialization failed", { error });
1149
+ return err(
1150
+ HaiDeployError.CONFIG_ERROR,
1151
+ deployM("deploy_configError", {
1152
+ params: { error: error instanceof Error ? error.message : String(error) }
1153
+ }),
1154
+ error
1155
+ );
1156
+ }
1157
+ } finally {
1158
+ initInProgress = false;
1159
+ }
1160
+ },
1161
+ async close() {
1162
+ if (!currentProvider) {
1163
+ logger10.info("Deploy module already closed, skipping");
1164
+ return;
1165
+ }
1166
+ logger10.info("Closing deploy module");
1167
+ currentProvider = null;
1168
+ currentConfig = null;
1169
+ currentProvisioners.clear();
1170
+ logger10.info("Deploy module closed");
1171
+ },
1172
+ get config() {
1173
+ return currentConfig;
1174
+ },
1175
+ get isInitialized() {
1176
+ return currentProvider !== null;
1177
+ },
1178
+ async scan(appDir) {
1179
+ logger10.debug("Scanning application", { appDir });
1180
+ return scanApp(appDir);
1181
+ },
1182
+ async provisionAll(projectName) {
1183
+ if (!currentProvider) {
1184
+ return notInitialized.result();
1185
+ }
1186
+ logger10.info("Provisioning all services", { projectName, count: currentProvisioners.size });
1187
+ const results = [];
1188
+ for (const [serviceType, provisioner] of currentProvisioners) {
1189
+ logger10.debug("Provisioning service", { serviceType, provisioner: provisioner.name });
1190
+ const provResult = await provisioner.provision(projectName);
1191
+ if (!provResult.success) {
1192
+ logger10.error("Service provisioning failed", {
1193
+ serviceType,
1194
+ provisioner: provisioner.name,
1195
+ error: provResult.error
1196
+ });
1197
+ return err(provResult.error);
1198
+ }
1199
+ results.push(provResult.data);
1200
+ logger10.info("Service provisioned", { serviceType, provisioner: provisioner.name });
1201
+ }
1202
+ return ok(results);
1203
+ },
1204
+ async deployApp(appDir, options) {
1205
+ if (!currentProvider) {
1206
+ return notInitialized.result();
1207
+ }
1208
+ logger10.info("Starting deployment", { appDir, options });
1209
+ const scanResult = await scanApp(appDir);
1210
+ if (!scanResult.success) {
1211
+ return err(scanResult.error);
1212
+ }
1213
+ const scan = scanResult.data;
1214
+ const projectName = options?.projectName ?? scan.appName;
1215
+ if (scan.isSvelteKit && !scan.adapterInstalled) {
1216
+ return err(
1217
+ HaiDeployError.ADAPTER_MISSING,
1218
+ deployM("deploy_adapterMissing", {
1219
+ params: { adapter: "@sveltejs/adapter-vercel" }
1220
+ })
1221
+ );
1222
+ }
1223
+ let allEnvVars = {};
1224
+ if (!options?.skipProvision) {
1225
+ const provResults = await deploy.provisionAll(projectName);
1226
+ if (!provResults.success) {
1227
+ return err(provResults.error);
1228
+ }
1229
+ for (const prov of provResults.data) {
1230
+ allEnvVars = { ...allEnvVars, ...prov.envVars };
1231
+ }
1232
+ }
1233
+ const projectResult = await currentProvider.createProject(projectName);
1234
+ if (!projectResult.success) {
1235
+ return err(projectResult.error);
1236
+ }
1237
+ const projectId = projectResult.data;
1238
+ if (Object.keys(allEnvVars).length > 0) {
1239
+ const envResult = await currentProvider.setEnvVars(projectId, allEnvVars);
1240
+ if (!envResult.success) {
1241
+ return err(envResult.error);
1242
+ }
1243
+ }
1244
+ if (!options?.skipBuild) {
1245
+ const buildResult = buildApp(appDir, scan.buildCommand);
1246
+ if (!buildResult.success) {
1247
+ return err(buildResult.error);
1248
+ }
1249
+ }
1250
+ const outputDir = resolveOutputDir(appDir, currentConfig?.provider.type ?? "vercel");
1251
+ const deployResult = await currentProvider.deploy(projectId, outputDir);
1252
+ if (!deployResult.success) {
1253
+ return err(deployResult.error);
1254
+ }
1255
+ const result = {
1256
+ ...deployResult.data,
1257
+ envVarsSet: Object.keys(allEnvVars)
1258
+ };
1259
+ logger10.info("Deployment completed", {
1260
+ url: result.url,
1261
+ deploymentId: result.deploymentId,
1262
+ status: result.status
1263
+ });
1264
+ return ok(result);
1265
+ }
1266
+ };
1267
+
1268
+ export { DeployConfigSchema, HaiDeployError, ProviderConfigSchema, deploy, getCredentialsPath, loadCredentials, saveCredential, saveCredentials, scanApp };
1269
+ //# sourceMappingURL=index.js.map
1270
+ //# sourceMappingURL=index.js.map