@sylphx/cli 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1608 -417
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,13 +24,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var import_chalk23 = __toESM(require("chalk"));
|
|
28
|
+
var import_commander22 = require("commander");
|
|
29
29
|
|
|
30
30
|
// package.json
|
|
31
31
|
var package_default = {
|
|
32
32
|
name: "@sylphx/cli",
|
|
33
|
-
version: "0.
|
|
33
|
+
version: "0.2.0",
|
|
34
34
|
description: "Sylphx Platform CLI \u2014 deploy, manage logs, env vars, and more",
|
|
35
35
|
type: "commonjs",
|
|
36
36
|
bin: {
|
|
@@ -73,8 +73,7 @@ var package_default = {
|
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
// src/commands/
|
|
77
|
-
var import_node_readline = __toESM(require("readline"));
|
|
76
|
+
// src/commands/config-cmd.ts
|
|
78
77
|
var import_chalk = __toESM(require("chalk"));
|
|
79
78
|
var import_commander = require("commander");
|
|
80
79
|
var import_ora = __toESM(require("ora"));
|
|
@@ -154,6 +153,7 @@ var config = {
|
|
|
154
153
|
};
|
|
155
154
|
|
|
156
155
|
// src/api.ts
|
|
156
|
+
var { version } = package_default;
|
|
157
157
|
var BASE_URL = process.env.SYLPHX_API_URL ?? "https://api.sylphx.com";
|
|
158
158
|
var API_BASE = `${BASE_URL}/v1`;
|
|
159
159
|
var AUTH_BASE_URL = "https://sylphx.com";
|
|
@@ -178,7 +178,7 @@ async function request(method, path5, options) {
|
|
|
178
178
|
const headers = {
|
|
179
179
|
Authorization: `Bearer ${token}`,
|
|
180
180
|
"Content-Type": "application/json",
|
|
181
|
-
"User-Agent":
|
|
181
|
+
"User-Agent": `sylphx-cli/${version}`
|
|
182
182
|
};
|
|
183
183
|
const res = await fetch(url, {
|
|
184
184
|
method,
|
|
@@ -218,13 +218,12 @@ var api = {
|
|
|
218
218
|
query: { envType }
|
|
219
219
|
});
|
|
220
220
|
},
|
|
221
|
-
/** Rollback to previous deployment
|
|
222
|
-
*
|
|
221
|
+
/** Rollback to previous deployment.
|
|
222
|
+
* Accepts envId (rollback env to previous deploy), deploymentRecordId, or buildRecordId.
|
|
223
|
+
* One of these is required.
|
|
223
224
|
*/
|
|
224
|
-
async rollback(projectId,
|
|
225
|
-
return request("POST", `/projects/${projectId}/rollback`, {
|
|
226
|
-
body: { envType }
|
|
227
|
-
});
|
|
225
|
+
async rollback(projectId, body) {
|
|
226
|
+
return request("POST", `/projects/${projectId}/rollback`, { body });
|
|
228
227
|
},
|
|
229
228
|
/** List env vars for a project environment */
|
|
230
229
|
async listEnvVars(projectId, envType) {
|
|
@@ -252,30 +251,77 @@ var api = {
|
|
|
252
251
|
query: { envType }
|
|
253
252
|
});
|
|
254
253
|
},
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
254
|
+
// ── Domain Management (Unified System) ──────────────────────────────────
|
|
255
|
+
/** List all apex domains for a project environment */
|
|
256
|
+
async listDomains(projectId, envType = "production") {
|
|
257
|
+
return request("GET", `/domains/projects/${projectId}/domains`, {
|
|
258
|
+
query: { envType }
|
|
259
|
+
});
|
|
260
|
+
},
|
|
261
|
+
/** Add a custom domain to a project */
|
|
262
|
+
async addDomain(projectId, domain, envType = "production") {
|
|
263
|
+
return request(
|
|
264
|
+
"POST",
|
|
265
|
+
`/domains/projects/${projectId}/domains`,
|
|
266
|
+
{ query: { envType }, body: { domain } }
|
|
267
|
+
);
|
|
268
|
+
},
|
|
269
|
+
/** Remove a custom domain from a project */
|
|
270
|
+
async removeDomain(projectId, domain, envType = "production") {
|
|
271
|
+
return request(
|
|
272
|
+
"DELETE",
|
|
273
|
+
`/domains/projects/${projectId}/domains/${encodeURIComponent(domain)}`,
|
|
274
|
+
{ query: { envType } }
|
|
261
275
|
);
|
|
262
|
-
return res.customDomains ?? [];
|
|
263
276
|
},
|
|
264
|
-
/**
|
|
265
|
-
async
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
body: { domain }
|
|
277
|
+
/** Register an apex domain for a project (full domain object with DNS records) */
|
|
278
|
+
async createDomain(projectId, apexDomain, envType = "production") {
|
|
279
|
+
return request("POST", `/domains/projects/${projectId}/domains`, {
|
|
280
|
+
body: { apexDomain, envType }
|
|
269
281
|
});
|
|
270
282
|
},
|
|
271
|
-
/**
|
|
272
|
-
async
|
|
273
|
-
const
|
|
283
|
+
/** Add a hostname binding to an existing domain */
|
|
284
|
+
async addHostname(projectId, domainId, hostname, opts) {
|
|
285
|
+
const { envType = "production", ...bodyOpts } = opts ?? {};
|
|
286
|
+
return request(
|
|
287
|
+
"POST",
|
|
288
|
+
`/domains/projects/${projectId}/domains/${domainId}/hostnames`,
|
|
289
|
+
{ query: { envType }, body: { hostname, ...bodyOpts } }
|
|
290
|
+
);
|
|
291
|
+
},
|
|
292
|
+
/** Remove a hostname binding */
|
|
293
|
+
async removeHostname(projectId, domainId, hostnameId) {
|
|
274
294
|
return request(
|
|
275
295
|
"DELETE",
|
|
276
|
-
`/domains/projects/${projectId}/domains/${
|
|
296
|
+
`/domains/projects/${projectId}/domains/${domainId}/hostnames/${hostnameId}`
|
|
297
|
+
);
|
|
298
|
+
},
|
|
299
|
+
/** Trigger DNS check for a hostname */
|
|
300
|
+
async checkHostname(projectId, domainId, hostnameId) {
|
|
301
|
+
return request(
|
|
302
|
+
"POST",
|
|
303
|
+
`/domains/projects/${projectId}/domains/${domainId}/hostnames/${hostnameId}/check`
|
|
304
|
+
);
|
|
305
|
+
},
|
|
306
|
+
/** Enable email sending for a domain (generates DKIM keypair) */
|
|
307
|
+
async enableEmail(projectId, domainId, opts) {
|
|
308
|
+
return request(
|
|
309
|
+
"POST",
|
|
310
|
+
`/domains/projects/${projectId}/domains/${domainId}/email`,
|
|
311
|
+
{ body: opts ?? {} }
|
|
312
|
+
);
|
|
313
|
+
},
|
|
314
|
+
/** Check DKIM DNS propagation and load into Stalwart */
|
|
315
|
+
async checkEmail(projectId, domainId) {
|
|
316
|
+
return request(
|
|
317
|
+
"POST",
|
|
318
|
+
`/domains/${projectId}/domains/${domainId}/email/check`
|
|
277
319
|
);
|
|
278
320
|
},
|
|
321
|
+
/** Disable email sending for a domain */
|
|
322
|
+
async disableEmail(projectId, domainId) {
|
|
323
|
+
return request("DELETE", `/domains/${projectId}/domains/${domainId}/email`);
|
|
324
|
+
},
|
|
279
325
|
/** Get current user and orgs */
|
|
280
326
|
async whoami() {
|
|
281
327
|
return request("GET", "/user/me");
|
|
@@ -333,12 +379,224 @@ var api = {
|
|
|
333
379
|
/** CLI auth: poll for token (uses main sylphx.com domain) */
|
|
334
380
|
async pollCliAuth(code) {
|
|
335
381
|
const res = await fetch(`${AUTH_BASE_URL}/api/auth/cli/poll?code=${encodeURIComponent(code)}`, {
|
|
336
|
-
headers: { "User-Agent":
|
|
382
|
+
headers: { "User-Agent": `sylphx-cli/${version}` }
|
|
337
383
|
});
|
|
338
384
|
if (!res.ok) {
|
|
339
385
|
throw new ApiError(res.status, "Failed to poll auth status");
|
|
340
386
|
}
|
|
341
387
|
return res.json();
|
|
388
|
+
},
|
|
389
|
+
// ── Services ─────────────────────────────────────────────────────────────
|
|
390
|
+
/** List services for a project */
|
|
391
|
+
async listServices(projectId) {
|
|
392
|
+
const res = await request(
|
|
393
|
+
"GET",
|
|
394
|
+
`/projects/${encodeURIComponent(projectId)}/services`
|
|
395
|
+
);
|
|
396
|
+
return Array.isArray(res) ? res : res.data ?? [];
|
|
397
|
+
},
|
|
398
|
+
/** Get a service by name */
|
|
399
|
+
async getService(projectId, name) {
|
|
400
|
+
return request(
|
|
401
|
+
"GET",
|
|
402
|
+
`/projects/${encodeURIComponent(projectId)}/services/${encodeURIComponent(name)}`
|
|
403
|
+
);
|
|
404
|
+
},
|
|
405
|
+
/**
|
|
406
|
+
* Deploy a specific service.
|
|
407
|
+
* Backend expects { environmentId?, force?, image? } — NOT envType.
|
|
408
|
+
* Resolves envType → environmentId automatically.
|
|
409
|
+
*/
|
|
410
|
+
async deployService(projectId, name, envType) {
|
|
411
|
+
const environmentId = await this.resolveEnvId(projectId, envType);
|
|
412
|
+
return request(
|
|
413
|
+
"POST",
|
|
414
|
+
`/projects/${encodeURIComponent(projectId)}/services/${encodeURIComponent(name)}/deploy`,
|
|
415
|
+
{ body: environmentId ? { environmentId } : {} }
|
|
416
|
+
);
|
|
417
|
+
},
|
|
418
|
+
/** Delete a service */
|
|
419
|
+
async deleteService(projectId, name) {
|
|
420
|
+
return request(
|
|
421
|
+
"DELETE",
|
|
422
|
+
`/projects/${encodeURIComponent(projectId)}/services/${encodeURIComponent(name)}`
|
|
423
|
+
);
|
|
424
|
+
},
|
|
425
|
+
// ── Volumes ───────────────────────────────────────────────────────────────
|
|
426
|
+
/** List volumes for a project */
|
|
427
|
+
async listVolumes(projectId) {
|
|
428
|
+
const res = await request(
|
|
429
|
+
"GET",
|
|
430
|
+
`/projects/${encodeURIComponent(projectId)}/volumes`
|
|
431
|
+
);
|
|
432
|
+
return Array.isArray(res) ? res : res.volumes ?? [];
|
|
433
|
+
},
|
|
434
|
+
/** Create a volume */
|
|
435
|
+
async createVolume(projectId, data) {
|
|
436
|
+
return request(
|
|
437
|
+
"POST",
|
|
438
|
+
`/projects/${encodeURIComponent(projectId)}/volumes`,
|
|
439
|
+
{ body: data }
|
|
440
|
+
);
|
|
441
|
+
},
|
|
442
|
+
/** Delete a volume */
|
|
443
|
+
async deleteVolume(projectId, volId) {
|
|
444
|
+
return request(
|
|
445
|
+
"DELETE",
|
|
446
|
+
`/projects/${encodeURIComponent(projectId)}/volumes/${encodeURIComponent(volId)}`
|
|
447
|
+
);
|
|
448
|
+
},
|
|
449
|
+
// ── Storage (Blob) ────────────────────────────────────────────────────────
|
|
450
|
+
/** List storage resources for a project.
|
|
451
|
+
* Backend returns a plain array of binding objects (not wrapped in { data: [...] }).
|
|
452
|
+
* Optionally filter by envType client-side.
|
|
453
|
+
*/
|
|
454
|
+
async listStorage(projectId, envType) {
|
|
455
|
+
const res = await request(
|
|
456
|
+
"GET",
|
|
457
|
+
`/projects/${encodeURIComponent(projectId)}/storage`
|
|
458
|
+
);
|
|
459
|
+
const list = Array.isArray(res) ? res : [];
|
|
460
|
+
return envType ? list.filter((r) => !r.envType || r.envType === envType) : list;
|
|
461
|
+
},
|
|
462
|
+
/** Provision blob storage for a project environment */
|
|
463
|
+
async createStorage(projectId, data) {
|
|
464
|
+
return request("POST", `/projects/${encodeURIComponent(projectId)}/storage`, {
|
|
465
|
+
body: data
|
|
466
|
+
});
|
|
467
|
+
},
|
|
468
|
+
/** Delete a storage resource */
|
|
469
|
+
async deleteStorage(projectId, storageId) {
|
|
470
|
+
return request(
|
|
471
|
+
"DELETE",
|
|
472
|
+
`/projects/${encodeURIComponent(projectId)}/storage/${encodeURIComponent(storageId)}`
|
|
473
|
+
);
|
|
474
|
+
},
|
|
475
|
+
// ── Config (remote key-value) ─────────────────────────────────────────────
|
|
476
|
+
/**
|
|
477
|
+
* List all config keys for a project.
|
|
478
|
+
* Backend uses ?env=<envId> (not ?envType). Resolves envType → envId automatically.
|
|
479
|
+
*/
|
|
480
|
+
async listConfig(projectId, envType) {
|
|
481
|
+
const query = {};
|
|
482
|
+
if (envType) {
|
|
483
|
+
const envId = await this.resolveEnvId(projectId, envType);
|
|
484
|
+
if (envId) query.env = envId;
|
|
485
|
+
}
|
|
486
|
+
const res = await request(
|
|
487
|
+
"GET",
|
|
488
|
+
`/projects/${encodeURIComponent(projectId)}/config`,
|
|
489
|
+
{ query }
|
|
490
|
+
);
|
|
491
|
+
return Array.isArray(res) ? res : res.data ?? [];
|
|
492
|
+
},
|
|
493
|
+
/**
|
|
494
|
+
* Get a single config key.
|
|
495
|
+
* Backend uses ?env=<envId> (not ?envType). Resolves envType → envId automatically.
|
|
496
|
+
*/
|
|
497
|
+
async getConfig(projectId, key, envType) {
|
|
498
|
+
const query = {};
|
|
499
|
+
if (envType) {
|
|
500
|
+
const envId = await this.resolveEnvId(projectId, envType);
|
|
501
|
+
if (envId) query.env = envId;
|
|
502
|
+
}
|
|
503
|
+
return request(
|
|
504
|
+
"GET",
|
|
505
|
+
`/projects/${encodeURIComponent(projectId)}/config/${encodeURIComponent(key)}`,
|
|
506
|
+
{ query }
|
|
507
|
+
);
|
|
508
|
+
},
|
|
509
|
+
/**
|
|
510
|
+
* Set a config key.
|
|
511
|
+
* Backend expects { key, value, environmentId } (not envType).
|
|
512
|
+
* Resolves envType → environmentId automatically when envType is provided.
|
|
513
|
+
*/
|
|
514
|
+
async setConfig(projectId, key, value, envType) {
|
|
515
|
+
let environmentId = null;
|
|
516
|
+
if (envType) {
|
|
517
|
+
environmentId = await this.resolveEnvId(projectId, envType);
|
|
518
|
+
}
|
|
519
|
+
return request("POST", `/projects/${encodeURIComponent(projectId)}/config`, {
|
|
520
|
+
body: { key, value, environmentId }
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
/**
|
|
524
|
+
* Delete a config key.
|
|
525
|
+
* Backend uses ?env=<envId> (not ?envType). Resolves envType → envId automatically.
|
|
526
|
+
*/
|
|
527
|
+
async deleteConfig(projectId, key, envType) {
|
|
528
|
+
const query = {};
|
|
529
|
+
if (envType) {
|
|
530
|
+
const envId = await this.resolveEnvId(projectId, envType);
|
|
531
|
+
if (envId) query.env = envId;
|
|
532
|
+
}
|
|
533
|
+
return request(
|
|
534
|
+
"DELETE",
|
|
535
|
+
`/projects/${encodeURIComponent(projectId)}/config/${encodeURIComponent(key)}`,
|
|
536
|
+
{ query }
|
|
537
|
+
);
|
|
538
|
+
},
|
|
539
|
+
// ── Environments ──────────────────────────────────────────────────────────
|
|
540
|
+
/** List environments for a project */
|
|
541
|
+
async listEnvironments(projectId) {
|
|
542
|
+
const res = await request(
|
|
543
|
+
"GET",
|
|
544
|
+
`/projects/${encodeURIComponent(projectId)}/environments`
|
|
545
|
+
);
|
|
546
|
+
return Array.isArray(res) ? res : res.data ?? [];
|
|
547
|
+
},
|
|
548
|
+
/**
|
|
549
|
+
* Resolve an environment type string (e.g. "production") → its envId for the given project.
|
|
550
|
+
* Returns null if not found. Used internally by config commands.
|
|
551
|
+
*/
|
|
552
|
+
async resolveEnvId(projectId, envType) {
|
|
553
|
+
const envs = await this.listEnvironments(projectId);
|
|
554
|
+
const match = envs.find((e) => (e.envType ?? "").toLowerCase() === envType.toLowerCase());
|
|
555
|
+
return match?.id ?? null;
|
|
556
|
+
},
|
|
557
|
+
/** Promote an environment (e.g. staging → production).
|
|
558
|
+
* Backend returns { promoted: number, services: [...], sourceEnvironment, targetEnvironment }
|
|
559
|
+
*/
|
|
560
|
+
async promoteEnvironment(projectId, targetEnvId, sourceEnvId) {
|
|
561
|
+
return request(
|
|
562
|
+
"POST",
|
|
563
|
+
`/projects/${encodeURIComponent(projectId)}/environments/${encodeURIComponent(targetEnvId)}/promote`,
|
|
564
|
+
{ body: { sourceEnvId } }
|
|
565
|
+
);
|
|
566
|
+
},
|
|
567
|
+
// ── Resource Bindings ─────────────────────────────────────────────────────
|
|
568
|
+
/** List bindings for a resource (which envs it's linked to).
|
|
569
|
+
* Backend returns { bindings: [...] }.
|
|
570
|
+
*/
|
|
571
|
+
async listResourceBindings(resourceId) {
|
|
572
|
+
const res = await request(
|
|
573
|
+
"GET",
|
|
574
|
+
`/resources/${encodeURIComponent(resourceId)}/bindings`
|
|
575
|
+
);
|
|
576
|
+
return res.bindings ?? [];
|
|
577
|
+
},
|
|
578
|
+
/**
|
|
579
|
+
* Bind a resource to a project environment.
|
|
580
|
+
* Backend expects { appEnvironmentId, role? }.
|
|
581
|
+
* Accepts projectId + envType and resolves to appEnvironmentId automatically.
|
|
582
|
+
*/
|
|
583
|
+
async bindResource(resourceId, data) {
|
|
584
|
+
const appEnvironmentId = await this.resolveEnvId(data.projectId, data.envType);
|
|
585
|
+
if (!appEnvironmentId) {
|
|
586
|
+
throw new Error(`Environment '${data.envType}' not found for project '${data.projectId}'`);
|
|
587
|
+
}
|
|
588
|
+
return request(
|
|
589
|
+
"POST",
|
|
590
|
+
`/resources/${encodeURIComponent(resourceId)}/bindings`,
|
|
591
|
+
{ body: { appEnvironmentId, role: data.role ?? "primary" } }
|
|
592
|
+
);
|
|
593
|
+
},
|
|
594
|
+
/** Unbind a resource from a project environment */
|
|
595
|
+
async unbindResource(resourceId, bindingId) {
|
|
596
|
+
return request(
|
|
597
|
+
"DELETE",
|
|
598
|
+
`/resources/${encodeURIComponent(resourceId)}/bindings/${encodeURIComponent(bindingId)}`
|
|
599
|
+
);
|
|
342
600
|
}
|
|
343
601
|
};
|
|
344
602
|
function createLogStream(projectId, envType) {
|
|
@@ -349,13 +607,105 @@ function createLogStream(projectId, envType) {
|
|
|
349
607
|
};
|
|
350
608
|
}
|
|
351
609
|
|
|
610
|
+
// src/commands/config-cmd.ts
|
|
611
|
+
function requireLinkedApp() {
|
|
612
|
+
const linked = config.getLinkedApp();
|
|
613
|
+
if (!linked) {
|
|
614
|
+
console.log(import_chalk.default.red("No app linked. Run `sylphx link` first."));
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
return linked;
|
|
618
|
+
}
|
|
619
|
+
function requireToken() {
|
|
620
|
+
const token = config.getToken();
|
|
621
|
+
if (!token) {
|
|
622
|
+
console.log(import_chalk.default.red("Not authenticated. Run `sylphx login` first."));
|
|
623
|
+
process.exit(1);
|
|
624
|
+
}
|
|
625
|
+
return token;
|
|
626
|
+
}
|
|
627
|
+
var listCmd = new import_commander.Command("list").description("List all remote config keys").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
|
|
628
|
+
requireToken();
|
|
629
|
+
const linked = requireLinkedApp();
|
|
630
|
+
const spinner = (0, import_ora.default)("Fetching config...").start();
|
|
631
|
+
try {
|
|
632
|
+
const entries = await api.listConfig(linked.appId, opts.env);
|
|
633
|
+
spinner.stop();
|
|
634
|
+
if (entries.length === 0) {
|
|
635
|
+
console.log(import_chalk.default.dim(" No config keys set."));
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
console.log();
|
|
639
|
+
const maxKey = Math.max(...entries.map((e) => e.key.length), 3);
|
|
640
|
+
for (const e of entries) {
|
|
641
|
+
const display = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
|
|
642
|
+
console.log(` ${import_chalk.default.cyan(e.key.padEnd(maxKey))} ${import_chalk.default.white(display)}`);
|
|
643
|
+
}
|
|
644
|
+
console.log();
|
|
645
|
+
} catch (err) {
|
|
646
|
+
spinner.fail(import_chalk.default.red(err instanceof Error ? err.message : String(err)));
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
var getCmd = new import_commander.Command("get").description("Get a remote config value").argument("<key>", "Config key name").option("--env <env>", "Environment (e.g. production, staging)").action(async (key, opts) => {
|
|
651
|
+
requireToken();
|
|
652
|
+
const linked = requireLinkedApp();
|
|
653
|
+
const spinner = (0, import_ora.default)(`Fetching config key '${key}'...`).start();
|
|
654
|
+
try {
|
|
655
|
+
const entry = await api.getConfig(linked.appId, key, opts.env);
|
|
656
|
+
spinner.stop();
|
|
657
|
+
console.log(
|
|
658
|
+
typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value, null, 2)
|
|
659
|
+
);
|
|
660
|
+
} catch (err) {
|
|
661
|
+
spinner.fail(import_chalk.default.red(err instanceof Error ? err.message : String(err)));
|
|
662
|
+
process.exit(1);
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
var setCmd = new import_commander.Command("set").description("Set a remote config key").argument("<assignment>", "KEY=VALUE assignment").option("--env <env>", "Environment (e.g. production, staging)").action(async (assignment, opts) => {
|
|
666
|
+
requireToken();
|
|
667
|
+
const linked = requireLinkedApp();
|
|
668
|
+
const eqIdx = assignment.indexOf("=");
|
|
669
|
+
if (eqIdx === -1) {
|
|
670
|
+
console.log(import_chalk.default.red(" Invalid format. Use KEY=VALUE."));
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
const key = assignment.slice(0, eqIdx);
|
|
674
|
+
const value = assignment.slice(eqIdx + 1);
|
|
675
|
+
const spinner = (0, import_ora.default)(`Setting config '${key}'...`).start();
|
|
676
|
+
try {
|
|
677
|
+
await api.setConfig(linked.appId, key, value, opts.env);
|
|
678
|
+
spinner.succeed(import_chalk.default.green(`\u2713 Config '${key}' set`));
|
|
679
|
+
} catch (err) {
|
|
680
|
+
spinner.fail(import_chalk.default.red(err instanceof Error ? err.message : String(err)));
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
var rmCmd = new import_commander.Command("rm").description("Delete a remote config key").argument("<key>", "Config key to delete").option("--env <env>", "Environment (e.g. production, staging)").action(async (key, opts) => {
|
|
685
|
+
requireToken();
|
|
686
|
+
const linked = requireLinkedApp();
|
|
687
|
+
const spinner = (0, import_ora.default)(`Deleting config '${key}'...`).start();
|
|
688
|
+
try {
|
|
689
|
+
await api.deleteConfig(linked.appId, key, opts.env);
|
|
690
|
+
spinner.succeed(import_chalk.default.green(`\u2713 Config '${key}' deleted`));
|
|
691
|
+
} catch (err) {
|
|
692
|
+
spinner.fail(import_chalk.default.red(err instanceof Error ? err.message : String(err)));
|
|
693
|
+
process.exit(1);
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
var configCommand = new import_commander.Command("config").description("Manage remote config (non-secret key-value store)").addCommand(listCmd).addCommand(getCmd).addCommand(setCmd).addCommand(rmCmd).action(() => configCommand.help());
|
|
697
|
+
|
|
352
698
|
// src/commands/db.ts
|
|
699
|
+
var import_node_readline = __toESM(require("readline"));
|
|
700
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
701
|
+
var import_commander2 = require("commander");
|
|
702
|
+
var import_ora2 = __toESM(require("ora"));
|
|
353
703
|
var POLL_INTERVAL_MS = 3e3;
|
|
354
704
|
var POLL_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
355
705
|
function requireAuth() {
|
|
356
706
|
const token = config.getToken();
|
|
357
707
|
if (!token) {
|
|
358
|
-
console.log(
|
|
708
|
+
console.log(import_chalk2.default.red("Not authenticated. Run `sylphx login` first."));
|
|
359
709
|
process.exit(1);
|
|
360
710
|
}
|
|
361
711
|
return token;
|
|
@@ -371,36 +721,36 @@ function prompt(question) {
|
|
|
371
721
|
}
|
|
372
722
|
function printDatabase(db) {
|
|
373
723
|
console.log("");
|
|
374
|
-
console.log(
|
|
375
|
-
console.log(
|
|
376
|
-
console.log(` ID : ${
|
|
724
|
+
console.log(import_chalk2.default.bold(` ${db.name}`));
|
|
725
|
+
console.log(import_chalk2.default.dim(` ${"\u2500".repeat(50)}`));
|
|
726
|
+
console.log(` ID : ${import_chalk2.default.white(db.id)}`);
|
|
377
727
|
console.log(` Status : ${statusColour(db.status)}`);
|
|
378
|
-
console.log(` Tier : ${
|
|
379
|
-
if (db.env) console.log(` Env : ${
|
|
380
|
-
if (db.host) console.log(` Host : ${
|
|
381
|
-
if (db.port) console.log(` Port : ${
|
|
382
|
-
if (db.dbName) console.log(` DB Name : ${
|
|
383
|
-
if (db.dbUser) console.log(` DB User : ${
|
|
384
|
-
if (db.storageGb) console.log(` Storage : ${
|
|
728
|
+
console.log(` Tier : ${import_chalk2.default.white(db.tier)}`);
|
|
729
|
+
if (db.env) console.log(` Env : ${import_chalk2.default.white(db.env)}`);
|
|
730
|
+
if (db.host) console.log(` Host : ${import_chalk2.default.white(db.host)}`);
|
|
731
|
+
if (db.port) console.log(` Port : ${import_chalk2.default.white(String(db.port))}`);
|
|
732
|
+
if (db.dbName) console.log(` DB Name : ${import_chalk2.default.white(db.dbName)}`);
|
|
733
|
+
if (db.dbUser) console.log(` DB User : ${import_chalk2.default.white(db.dbUser)}`);
|
|
734
|
+
if (db.storageGb) console.log(` Storage : ${import_chalk2.default.white(`${db.storageGb} GB`)}`);
|
|
385
735
|
if (db.connectionString) {
|
|
386
736
|
console.log(` Connection :`);
|
|
387
|
-
console.log(` ${
|
|
737
|
+
console.log(` ${import_chalk2.default.green(db.connectionString)}`);
|
|
388
738
|
}
|
|
389
|
-
if (db.createdAt) console.log(
|
|
739
|
+
if (db.createdAt) console.log(import_chalk2.default.dim(` Created : ${db.createdAt}`));
|
|
390
740
|
console.log("");
|
|
391
741
|
}
|
|
392
742
|
function statusColour(status) {
|
|
393
743
|
switch (status) {
|
|
394
744
|
case "ready":
|
|
395
|
-
return
|
|
745
|
+
return import_chalk2.default.green(status);
|
|
396
746
|
case "provisioning":
|
|
397
747
|
case "branching":
|
|
398
|
-
return
|
|
748
|
+
return import_chalk2.default.yellow(status);
|
|
399
749
|
case "error":
|
|
400
750
|
case "failed":
|
|
401
|
-
return
|
|
751
|
+
return import_chalk2.default.red(status);
|
|
402
752
|
default:
|
|
403
|
-
return
|
|
753
|
+
return import_chalk2.default.white(status);
|
|
404
754
|
}
|
|
405
755
|
}
|
|
406
756
|
async function pollUntilReady(id, spinner) {
|
|
@@ -417,152 +767,153 @@ async function pollUntilReady(id, spinner) {
|
|
|
417
767
|
}
|
|
418
768
|
throw new Error("Timed out waiting for database to become ready (2 min).");
|
|
419
769
|
}
|
|
420
|
-
var dbListCommand = new
|
|
770
|
+
var dbListCommand = new import_commander2.Command("list").description("List all databases").action(async () => {
|
|
421
771
|
requireAuth();
|
|
422
|
-
const spinner = (0,
|
|
772
|
+
const spinner = (0, import_ora2.default)("Fetching databases...").start();
|
|
423
773
|
try {
|
|
424
774
|
const dbs = await api.listDatabases();
|
|
425
775
|
spinner.stop();
|
|
426
776
|
if (dbs.length === 0) {
|
|
427
|
-
console.log(
|
|
777
|
+
console.log(import_chalk2.default.dim("\n No databases found.\n"));
|
|
428
778
|
return;
|
|
429
779
|
}
|
|
430
780
|
const colName = Math.max(4, ...dbs.map((d) => d.name.length));
|
|
431
781
|
const colStatus = Math.max(6, ...dbs.map((d) => d.status.length));
|
|
432
782
|
const colTier = Math.max(4, ...dbs.map((d) => d.tier.length));
|
|
433
783
|
console.log("");
|
|
434
|
-
console.log(
|
|
435
|
-
console.log(
|
|
784
|
+
console.log(import_chalk2.default.bold(" Databases"));
|
|
785
|
+
console.log(import_chalk2.default.dim(` ${"\u2500".repeat(colName + colStatus + colTier + 20)}`));
|
|
436
786
|
console.log(
|
|
437
|
-
|
|
787
|
+
import_chalk2.default.dim(
|
|
438
788
|
` ${"NAME".padEnd(colName)} ${"STATUS".padEnd(colStatus)} ${"TIER".padEnd(colTier)} ID`
|
|
439
789
|
)
|
|
440
790
|
);
|
|
441
|
-
console.log(
|
|
791
|
+
console.log(import_chalk2.default.dim(` ${"\u2500".repeat(colName + colStatus + colTier + 20)}`));
|
|
442
792
|
for (const db of dbs) {
|
|
443
793
|
console.log(
|
|
444
|
-
` ${
|
|
794
|
+
` ${import_chalk2.default.cyan(db.name.padEnd(colName))} ${statusColour(db.status).padEnd(colStatus + 10)} ${db.tier.padEnd(colTier)} ${import_chalk2.default.dim(db.id)}`
|
|
445
795
|
);
|
|
446
796
|
}
|
|
447
797
|
console.log("");
|
|
448
798
|
} catch (err) {
|
|
449
|
-
spinner.fail(
|
|
799
|
+
spinner.fail(import_chalk2.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
450
800
|
process.exit(1);
|
|
451
801
|
}
|
|
452
802
|
});
|
|
453
|
-
var dbCreateCommand = new
|
|
803
|
+
var dbCreateCommand = new import_commander2.Command("create").description("Provision a new database").argument("<name>", "Database name").option("--tier <tier>", "Database tier: standard or performance (default: standard)", "standard").option("--storage <gb>", "Storage in GB (default: 10)", "10").option("--env <env>", "Environment (default: production)", "production").action(async (name, opts) => {
|
|
454
804
|
requireAuth();
|
|
455
805
|
const storageGb = Number.parseInt(opts.storage, 10);
|
|
456
806
|
if (Number.isNaN(storageGb) || storageGb < 5) {
|
|
457
|
-
console.log(
|
|
807
|
+
console.log(import_chalk2.default.red("Storage must be at least 5 GB."));
|
|
458
808
|
process.exit(1);
|
|
459
809
|
}
|
|
460
810
|
const tier = opts.tier === "pro" ? "performance" : opts.tier;
|
|
461
|
-
const spinner = (0,
|
|
462
|
-
`Provisioning database ${
|
|
811
|
+
const spinner = (0, import_ora2.default)(
|
|
812
|
+
`Provisioning database ${import_chalk2.default.bold(name)} (${tier}, ${storageGb} GB)...`
|
|
463
813
|
).start();
|
|
464
814
|
try {
|
|
465
815
|
const db = await api.createDatabase({ name, env: opts.env, tier, storageGb });
|
|
466
816
|
spinner.text = `Waiting for database to become ready...`;
|
|
467
817
|
const ready = await pollUntilReady(db.id, spinner);
|
|
468
|
-
spinner.succeed(
|
|
818
|
+
spinner.succeed(import_chalk2.default.green(`\u2713 Database ${import_chalk2.default.bold(ready.name)} is ready`));
|
|
469
819
|
printDatabase(ready);
|
|
470
820
|
} catch (err) {
|
|
471
|
-
spinner.fail(
|
|
821
|
+
spinner.fail(import_chalk2.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
472
822
|
process.exit(1);
|
|
473
823
|
}
|
|
474
824
|
});
|
|
475
|
-
var dbGetCommand = new
|
|
825
|
+
var dbGetCommand = new import_commander2.Command("get").description("Get database details including connection string").argument("<id>", "Database ID").action(async (id) => {
|
|
476
826
|
requireAuth();
|
|
477
|
-
const spinner = (0,
|
|
827
|
+
const spinner = (0, import_ora2.default)("Fetching database...").start();
|
|
478
828
|
try {
|
|
479
829
|
const db = await api.getDatabase(id);
|
|
480
830
|
spinner.stop();
|
|
481
831
|
printDatabase(db);
|
|
482
832
|
} catch (err) {
|
|
483
|
-
spinner.fail(
|
|
833
|
+
spinner.fail(import_chalk2.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
484
834
|
process.exit(1);
|
|
485
835
|
}
|
|
486
836
|
});
|
|
487
|
-
var dbDeleteCommand = new
|
|
837
|
+
var dbDeleteCommand = new import_commander2.Command("delete").description("Delete a database (irreversible)").argument("<id>", "Database ID").option("--force", "Skip confirmation prompt").action(async (id, opts) => {
|
|
488
838
|
requireAuth();
|
|
489
839
|
if (!opts.force) {
|
|
490
840
|
console.log("");
|
|
491
|
-
console.log(
|
|
492
|
-
console.log(
|
|
841
|
+
console.log(import_chalk2.default.red(` \u26A0 You are about to PERMANENTLY delete database ${import_chalk2.default.bold(id)}.`));
|
|
842
|
+
console.log(import_chalk2.default.red(" This deletes all data, backups, and PVCs. IRREVERSIBLE."));
|
|
493
843
|
console.log("");
|
|
494
844
|
const answer = await prompt(" Type the database ID to confirm");
|
|
495
845
|
if (answer !== id) {
|
|
496
|
-
console.log(
|
|
846
|
+
console.log(import_chalk2.default.red("Confirmation did not match. Aborted."));
|
|
497
847
|
process.exit(1);
|
|
498
848
|
}
|
|
499
849
|
}
|
|
500
|
-
const spinner = (0,
|
|
850
|
+
const spinner = (0, import_ora2.default)(`Deleting database ${import_chalk2.default.bold(id)}...`).start();
|
|
501
851
|
try {
|
|
502
852
|
await api.deleteDatabase(id);
|
|
503
|
-
spinner.succeed(
|
|
853
|
+
spinner.succeed(import_chalk2.default.green(`\u2713 Deleted database ${import_chalk2.default.bold(id)}`));
|
|
504
854
|
} catch (err) {
|
|
505
|
-
spinner.fail(
|
|
855
|
+
spinner.fail(import_chalk2.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
506
856
|
process.exit(1);
|
|
507
857
|
}
|
|
508
858
|
});
|
|
509
|
-
var dbBranchCommand = new
|
|
859
|
+
var dbBranchCommand = new import_commander2.Command("branch").description("Branch a database for staging or preview").argument("<id>", "Source database ID").option("--name <name>", "Branch database name").option("--env <env>", "Target environment (default: staging)", "staging").action(async (id, opts) => {
|
|
510
860
|
requireAuth();
|
|
511
861
|
const branchName = opts.name ?? `${opts.env}-branch`;
|
|
512
|
-
const spinner = (0,
|
|
513
|
-
`Branching database ${
|
|
862
|
+
const spinner = (0, import_ora2.default)(
|
|
863
|
+
`Branching database ${import_chalk2.default.bold(id)} \u2192 ${import_chalk2.default.bold(branchName)}...`
|
|
514
864
|
).start();
|
|
515
865
|
try {
|
|
516
866
|
const db = await api.branchDatabase(id, { name: branchName, env: opts.env, fast: true });
|
|
517
867
|
spinner.text = `Waiting for branch to become ready...`;
|
|
518
868
|
const ready = await pollUntilReady(db.id, spinner);
|
|
519
|
-
spinner.succeed(
|
|
869
|
+
spinner.succeed(import_chalk2.default.green(`\u2713 Branch ${import_chalk2.default.bold(ready.name)} is ready`));
|
|
520
870
|
printDatabase(ready);
|
|
521
871
|
} catch (err) {
|
|
522
|
-
spinner.fail(
|
|
872
|
+
spinner.fail(import_chalk2.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
523
873
|
process.exit(1);
|
|
524
874
|
}
|
|
525
875
|
});
|
|
526
|
-
var dbCommand = new
|
|
876
|
+
var dbCommand = new import_commander2.Command("db").description("Manage PostgreSQL databases").addCommand(dbListCommand).addCommand(dbCreateCommand).addCommand(dbGetCommand).addCommand(dbDeleteCommand).addCommand(dbBranchCommand);
|
|
527
877
|
|
|
528
878
|
// src/commands/deploy.ts
|
|
529
|
-
var
|
|
530
|
-
var
|
|
531
|
-
var
|
|
879
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
880
|
+
var import_commander3 = require("commander");
|
|
881
|
+
var import_ora3 = __toESM(require("ora"));
|
|
532
882
|
|
|
533
883
|
// src/utils/logs.ts
|
|
534
|
-
var
|
|
884
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
885
|
+
var { version: version2 } = package_default;
|
|
535
886
|
function formatLogEntry(raw) {
|
|
536
887
|
let entry;
|
|
537
888
|
try {
|
|
538
889
|
entry = JSON.parse(raw);
|
|
539
890
|
} catch {
|
|
540
|
-
return ` ${
|
|
891
|
+
return ` ${import_chalk3.default.white(raw)}`;
|
|
541
892
|
}
|
|
542
893
|
const message = entry.content ?? entry.message ?? entry.msg ?? raw;
|
|
543
894
|
const level = (entry.level ?? "info").toLowerCase();
|
|
544
895
|
const timestamp = entry.timestamp ?? entry.ts;
|
|
545
|
-
const prefix = timestamp ? `${
|
|
896
|
+
const prefix = timestamp ? `${import_chalk3.default.dim(`[${new Date(timestamp).toLocaleTimeString()}]`)} ` : "";
|
|
546
897
|
const status = entry.status?.toLowerCase();
|
|
547
898
|
const effectiveLevel = status === "error" || status === "failed" ? "error" : status === "success" || status === "done" ? "success" : level;
|
|
548
899
|
switch (effectiveLevel) {
|
|
549
900
|
case "error":
|
|
550
|
-
return ` ${prefix}${
|
|
901
|
+
return ` ${prefix}${import_chalk3.default.red("\u2717")} ${import_chalk3.default.red(message)}`;
|
|
551
902
|
case "warn":
|
|
552
|
-
return ` ${prefix}${
|
|
903
|
+
return ` ${prefix}${import_chalk3.default.yellow("\u26A0")} ${import_chalk3.default.yellow(message)}`;
|
|
553
904
|
case "success":
|
|
554
|
-
return ` ${prefix}${
|
|
905
|
+
return ` ${prefix}${import_chalk3.default.green("\u2713")} ${import_chalk3.default.green(message)}`;
|
|
555
906
|
case "debug":
|
|
556
|
-
return ` ${prefix}${
|
|
907
|
+
return ` ${prefix}${import_chalk3.default.dim(message)}`;
|
|
557
908
|
default:
|
|
558
|
-
return ` ${prefix}${
|
|
909
|
+
return ` ${prefix}${import_chalk3.default.white(message)}`;
|
|
559
910
|
}
|
|
560
911
|
}
|
|
561
912
|
function streamLogs(url, token, opts = {}) {
|
|
562
913
|
return new Promise((resolve) => {
|
|
563
914
|
const EventSource = require("eventsource");
|
|
564
915
|
const headers = {
|
|
565
|
-
"User-Agent":
|
|
916
|
+
"User-Agent": `sylphx-cli/${version2}`,
|
|
566
917
|
Accept: "text/event-stream"
|
|
567
918
|
};
|
|
568
919
|
if (token) {
|
|
@@ -615,7 +966,7 @@ function streamLogs(url, token, opts = {}) {
|
|
|
615
966
|
es.addEventListener("error", (event) => {
|
|
616
967
|
const errorEvent = event;
|
|
617
968
|
if (errorEvent.message) {
|
|
618
|
-
console.log(
|
|
969
|
+
console.log(import_chalk3.default.red(` Stream error: ${errorEvent.message}`));
|
|
619
970
|
}
|
|
620
971
|
if (opts.exitOnDone) {
|
|
621
972
|
finish(succeeded);
|
|
@@ -631,40 +982,40 @@ function streamLogs(url, token, opts = {}) {
|
|
|
631
982
|
}
|
|
632
983
|
|
|
633
984
|
// src/commands/deploy.ts
|
|
634
|
-
var deployCommand = new
|
|
985
|
+
var deployCommand = new import_commander3.Command("deploy").description("Trigger a deployment for the linked app").option("--env <env>", "Environment to deploy to (e.g. production, staging)").action(async (opts) => {
|
|
635
986
|
const token = config.getToken();
|
|
636
987
|
if (!token) {
|
|
637
|
-
console.log(
|
|
988
|
+
console.log(import_chalk4.default.red("Not authenticated. Run `sylphx login` first."));
|
|
638
989
|
process.exit(1);
|
|
639
990
|
}
|
|
640
991
|
const linked = config.getLinkedApp();
|
|
641
992
|
if (!linked) {
|
|
642
|
-
console.log(
|
|
993
|
+
console.log(import_chalk4.default.red("No app linked. Run `sylphx link` first."));
|
|
643
994
|
process.exit(1);
|
|
644
995
|
}
|
|
645
996
|
const env = opts.env ?? linked.defaultEnv;
|
|
646
997
|
console.log("");
|
|
647
|
-
console.log(
|
|
998
|
+
console.log(import_chalk4.default.bold(` Deploying ${import_chalk4.default.cyan(linked.appId)} \u2192 ${import_chalk4.default.white(env)}`));
|
|
648
999
|
console.log("");
|
|
649
|
-
const spinner = (0,
|
|
1000
|
+
const spinner = (0, import_ora3.default)("Triggering deployment...").start();
|
|
650
1001
|
let deploymentId;
|
|
651
1002
|
try {
|
|
652
1003
|
const result = await api.triggerDeploy(linked.appId, env);
|
|
653
|
-
spinner.succeed(
|
|
1004
|
+
spinner.succeed(import_chalk4.default.green(`Deployment triggered: ${result.status}`));
|
|
654
1005
|
deploymentId = result.deploymentId;
|
|
655
1006
|
if (deploymentId) {
|
|
656
|
-
console.log(
|
|
1007
|
+
console.log(import_chalk4.default.dim(` Deployment ID: ${deploymentId}`));
|
|
657
1008
|
}
|
|
658
1009
|
if (result.environment) {
|
|
659
|
-
console.log(
|
|
1010
|
+
console.log(import_chalk4.default.dim(` Environment: ${result.environment}`));
|
|
660
1011
|
}
|
|
661
1012
|
} catch (err) {
|
|
662
|
-
spinner.fail(
|
|
1013
|
+
spinner.fail(import_chalk4.default.red(`Deploy failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
663
1014
|
process.exit(1);
|
|
664
1015
|
}
|
|
665
1016
|
console.log("");
|
|
666
|
-
console.log(
|
|
667
|
-
console.log(
|
|
1017
|
+
console.log(import_chalk4.default.bold(" Build Logs"));
|
|
1018
|
+
console.log(import_chalk4.default.dim(` ${"\u2500".repeat(50)}`));
|
|
668
1019
|
console.log("");
|
|
669
1020
|
const logStream = createLogStream(linked.appId, env);
|
|
670
1021
|
const success = await streamLogs(logStream.url, logStream.token, {
|
|
@@ -673,140 +1024,387 @@ var deployCommand = new import_commander2.Command("deploy").description("Trigger
|
|
|
673
1024
|
});
|
|
674
1025
|
if (!success) {
|
|
675
1026
|
console.log("");
|
|
676
|
-
console.log(
|
|
1027
|
+
console.log(import_chalk4.default.red(" Deployment failed."));
|
|
677
1028
|
process.exit(1);
|
|
678
1029
|
}
|
|
679
1030
|
console.log("");
|
|
680
|
-
console.log(
|
|
1031
|
+
console.log(import_chalk4.default.green.bold(" \u2713 Deployment successful!"));
|
|
681
1032
|
console.log("");
|
|
682
1033
|
});
|
|
683
1034
|
|
|
684
1035
|
// src/commands/domains.ts
|
|
685
|
-
var
|
|
686
|
-
var
|
|
687
|
-
var
|
|
688
|
-
function
|
|
1036
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
1037
|
+
var import_commander4 = require("commander");
|
|
1038
|
+
var import_ora4 = __toESM(require("ora"));
|
|
1039
|
+
function requireLinkedApp2() {
|
|
689
1040
|
const token = config.getToken();
|
|
690
1041
|
if (!token) {
|
|
691
|
-
console.
|
|
1042
|
+
console.error(import_chalk5.default.red("Not authenticated. Run `sylphx login` first."));
|
|
692
1043
|
process.exit(1);
|
|
693
1044
|
}
|
|
694
1045
|
const linked = config.getLinkedApp();
|
|
695
1046
|
if (!linked) {
|
|
696
|
-
console.
|
|
1047
|
+
console.error(import_chalk5.default.red("No project linked. Run `sylphx link` first."));
|
|
697
1048
|
process.exit(1);
|
|
698
1049
|
}
|
|
699
1050
|
return linked;
|
|
700
1051
|
}
|
|
701
|
-
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
1052
|
+
function extractApex(hostname) {
|
|
1053
|
+
const parts = hostname.split(".");
|
|
1054
|
+
return parts.length > 2 ? parts.slice(-2).join(".") : hostname;
|
|
1055
|
+
}
|
|
1056
|
+
function findDomainByApex(domains, apex) {
|
|
1057
|
+
return domains.find((d) => d.apexDomain === apex);
|
|
1058
|
+
}
|
|
1059
|
+
function findHostname(domain, hostname) {
|
|
1060
|
+
return domain.hostnames.find((h) => h.hostname === hostname);
|
|
1061
|
+
}
|
|
1062
|
+
function dnsStatusBadge(status) {
|
|
1063
|
+
switch (status) {
|
|
1064
|
+
case "active":
|
|
1065
|
+
case "verified":
|
|
1066
|
+
return import_chalk5.default.green("\u2713 active");
|
|
1067
|
+
case "verifying":
|
|
1068
|
+
return import_chalk5.default.yellow("\u27F3 verifying");
|
|
1069
|
+
case "pending":
|
|
1070
|
+
return import_chalk5.default.dim("\u25CB pending");
|
|
1071
|
+
case "error":
|
|
1072
|
+
case "failed":
|
|
1073
|
+
return import_chalk5.default.red("\u2717 failed");
|
|
1074
|
+
default:
|
|
1075
|
+
return import_chalk5.default.dim(status);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function printDnsRecord(record) {
|
|
1079
|
+
const statusBadge = record.status ? ` ${dnsStatusBadge(record.status)}` : "";
|
|
1080
|
+
console.log(import_chalk5.default.dim(` ${record.type.padEnd(5)} ${import_chalk5.default.cyan(record.name)}`));
|
|
1081
|
+
console.log(import_chalk5.default.dim(` \u2192 ${record.value}${statusBadge}`));
|
|
1082
|
+
if (record.description) console.log(import_chalk5.default.dim(` ${import_chalk5.default.italic(record.description)}`));
|
|
1083
|
+
}
|
|
1084
|
+
function printHostname(h, indent = " ") {
|
|
1085
|
+
const primary = h.isPrimary ? import_chalk5.default.green(" [primary]") : "";
|
|
1086
|
+
const redirect = h.redirectTo ? import_chalk5.default.dim(` \u2192 ${h.redirectTo}`) : "";
|
|
1087
|
+
console.log(
|
|
1088
|
+
`${indent}${import_chalk5.default.bold(h.hostname)}${primary}${redirect} ${dnsStatusBadge(h.dnsStatus)}`
|
|
1089
|
+
);
|
|
1090
|
+
if (h.dnsStatus !== "active") {
|
|
1091
|
+
console.log(
|
|
1092
|
+
`${indent} ${import_chalk5.default.dim(h.instruction.type)} record: ${import_chalk5.default.cyan(h.instruction.name)} \u2192 ${h.instruction.value}`
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
if (h.lastCheckError) {
|
|
1096
|
+
console.log(`${indent} ${import_chalk5.default.red(h.lastCheckError)}`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function printEmailBinding(email, indent = " ") {
|
|
1100
|
+
console.log(
|
|
1101
|
+
`${indent}Email: ${dnsStatusBadge(email.dnsStatus)}${email.stalwartLoaded ? import_chalk5.default.dim(" (DKIM loaded)") : ""}`
|
|
1102
|
+
);
|
|
1103
|
+
if (email.defaultFromEmail) {
|
|
1104
|
+
console.log(
|
|
1105
|
+
`${indent} From: ${email.defaultFromEmail}${email.defaultFromName ? ` <${email.defaultFromName}>` : ""}`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
if (email.dnsStatus !== "verified" && email.records.length > 0) {
|
|
1109
|
+
console.log(`${indent} ${import_chalk5.default.dim("DNS records to add:")}`);
|
|
1110
|
+
for (const rec of email.records) {
|
|
1111
|
+
printDnsRecord({ ...rec, status: rec.status ?? void 0 });
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
function printDomainResult(d) {
|
|
1116
|
+
console.log("");
|
|
1117
|
+
console.log(` ${import_chalk5.default.bold.white(d.apexDomain)}`);
|
|
1118
|
+
if (d.hostnames.length === 0) {
|
|
1119
|
+
console.log(import_chalk5.default.dim(" No hostname bindings"));
|
|
1120
|
+
} else {
|
|
1121
|
+
for (const h of d.hostnames) {
|
|
1122
|
+
printHostname(h, " ");
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
if (d.email) {
|
|
1126
|
+
printEmailBinding(d.email, " ");
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
var domainsListCommand = new import_commander4.Command("list").description("List all domains and their status").option("--env <env>", "Environment (production, staging, development)", "production").action(async (opts) => {
|
|
1130
|
+
const linked = requireLinkedApp2();
|
|
1131
|
+
const spinner = (0, import_ora4.default)("Fetching domains...").start();
|
|
705
1132
|
try {
|
|
706
|
-
const domains = await api.listDomains(linked.appId, env);
|
|
1133
|
+
const domains = await api.listDomains(linked.appId, opts.env);
|
|
707
1134
|
spinner.stop();
|
|
708
1135
|
if (domains.length === 0) {
|
|
709
|
-
console.log(
|
|
1136
|
+
console.log(import_chalk5.default.dim("\n No domains configured. Run `sylphx domains add <hostname>`\n"));
|
|
710
1137
|
return;
|
|
711
1138
|
}
|
|
712
1139
|
console.log("");
|
|
713
|
-
console.log(
|
|
714
|
-
console.log(
|
|
715
|
-
console.log("");
|
|
1140
|
+
console.log(import_chalk5.default.bold(` Domains [${import_chalk5.default.white(opts.env)}]`));
|
|
1141
|
+
console.log(import_chalk5.default.dim(` ${"\u2500".repeat(50)}`));
|
|
716
1142
|
for (const d of domains) {
|
|
717
|
-
|
|
718
|
-
const ssl = d.ssl ? import_chalk4.default.green(" \u{1F512}") : import_chalk4.default.yellow(" \u26A0 no SSL");
|
|
719
|
-
console.log(` ${import_chalk4.default.cyan(d.domain)}${status}${ssl}`);
|
|
1143
|
+
printDomainResult(d);
|
|
720
1144
|
}
|
|
721
1145
|
console.log("");
|
|
722
1146
|
} catch (err) {
|
|
723
|
-
spinner.fail(
|
|
1147
|
+
spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
724
1148
|
process.exit(1);
|
|
725
1149
|
}
|
|
726
1150
|
});
|
|
727
|
-
var domainsAddCommand = new
|
|
728
|
-
const linked =
|
|
729
|
-
const
|
|
730
|
-
const spinner = (0,
|
|
1151
|
+
var domainsAddCommand = new import_commander4.Command("add").description("Add a hostname binding (auto-registers apex domain if needed)").argument("<hostname>", "Hostname to add (e.g. app.example.com or example.com)").option("--env <env>", "Environment", "production").option("--primary", "Set as primary domain", false).option("--redirect <url>", "Redirect to this URL instead of serving traffic").action(async (hostname, opts) => {
|
|
1152
|
+
const linked = requireLinkedApp2();
|
|
1153
|
+
const apex = extractApex(hostname);
|
|
1154
|
+
const spinner = (0, import_ora4.default)(`Setting up ${hostname}...`).start();
|
|
731
1155
|
try {
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
if (
|
|
735
|
-
|
|
1156
|
+
const domains = await api.listDomains(linked.appId, opts.env);
|
|
1157
|
+
let domain = findDomainByApex(domains, apex);
|
|
1158
|
+
if (!domain) {
|
|
1159
|
+
spinner.text = `Registering apex domain ${apex}...`;
|
|
1160
|
+
domain = await api.createDomain(linked.appId, apex, opts.env);
|
|
736
1161
|
}
|
|
1162
|
+
spinner.text = `Adding hostname binding ${hostname}...`;
|
|
1163
|
+
const h = await api.addHostname(linked.appId, domain.id, hostname, {
|
|
1164
|
+
isPrimary: opts.primary,
|
|
1165
|
+
redirectTo: opts.redirect,
|
|
1166
|
+
envType: opts.env
|
|
1167
|
+
});
|
|
1168
|
+
spinner.succeed(import_chalk5.default.green(`Added ${import_chalk5.default.bold(hostname)}`));
|
|
1169
|
+
console.log("");
|
|
1170
|
+
console.log(import_chalk5.default.bold(" DNS Record to add:"));
|
|
1171
|
+
console.log("");
|
|
1172
|
+
console.log(
|
|
1173
|
+
` ${import_chalk5.default.dim(h.instruction.type.padEnd(5))} ${import_chalk5.default.cyan(h.instruction.name)}`
|
|
1174
|
+
);
|
|
1175
|
+
console.log(` ${import_chalk5.default.dim("value:")} ${h.instruction.value}`);
|
|
1176
|
+
console.log(` ${import_chalk5.default.dim("TTL:")} ${h.instruction.ttl}`);
|
|
1177
|
+
console.log("");
|
|
1178
|
+
console.log(import_chalk5.default.dim(` Status: ${dnsStatusBadge(h.dnsStatus)}`));
|
|
1179
|
+
console.log(import_chalk5.default.dim(` Run \`sylphx domains verify ${hostname}\` after updating DNS.`));
|
|
1180
|
+
console.log("");
|
|
737
1181
|
} catch (err) {
|
|
738
|
-
spinner.fail(
|
|
1182
|
+
spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
739
1183
|
process.exit(1);
|
|
740
1184
|
}
|
|
741
1185
|
});
|
|
742
|
-
var domainsRmCommand = new
|
|
743
|
-
const linked =
|
|
744
|
-
const
|
|
745
|
-
const spinner = (0,
|
|
1186
|
+
var domainsRmCommand = new import_commander4.Command("rm").description("Remove a hostname binding").argument("<hostname>", "Hostname to remove").option("--env <env>", "Environment", "production").action(async (hostname, opts) => {
|
|
1187
|
+
const linked = requireLinkedApp2();
|
|
1188
|
+
const apex = extractApex(hostname);
|
|
1189
|
+
const spinner = (0, import_ora4.default)(`Removing ${hostname}...`).start();
|
|
746
1190
|
try {
|
|
747
|
-
await api.
|
|
748
|
-
|
|
1191
|
+
const domains = await api.listDomains(linked.appId, opts.env);
|
|
1192
|
+
const domain = findDomainByApex(domains, apex);
|
|
1193
|
+
if (!domain) {
|
|
1194
|
+
spinner.fail(import_chalk5.default.red(`Domain ${apex} not found`));
|
|
1195
|
+
process.exit(1);
|
|
1196
|
+
}
|
|
1197
|
+
const h = findHostname(domain, hostname);
|
|
1198
|
+
if (!h) {
|
|
1199
|
+
spinner.fail(import_chalk5.default.red(`Hostname ${hostname} not found`));
|
|
1200
|
+
process.exit(1);
|
|
1201
|
+
}
|
|
1202
|
+
await api.removeHostname(linked.appId, domain.id, h.id);
|
|
1203
|
+
spinner.succeed(import_chalk5.default.green(`Removed ${import_chalk5.default.bold(hostname)}`));
|
|
749
1204
|
} catch (err) {
|
|
750
|
-
spinner.fail(
|
|
1205
|
+
spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
751
1206
|
process.exit(1);
|
|
752
1207
|
}
|
|
753
1208
|
});
|
|
754
|
-
var
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
1209
|
+
var domainsVerifyCommand = new import_commander4.Command("verify").description("Check DNS propagation for a hostname").argument("<hostname>", "Hostname to check").option("--env <env>", "Environment", "production").action(async (hostname, opts) => {
|
|
1210
|
+
const linked = requireLinkedApp2();
|
|
1211
|
+
const apex = extractApex(hostname);
|
|
1212
|
+
const spinner = (0, import_ora4.default)(`Checking DNS for ${hostname}...`).start();
|
|
1213
|
+
try {
|
|
1214
|
+
const domains = await api.listDomains(linked.appId, opts.env);
|
|
1215
|
+
const domain = findDomainByApex(domains, apex);
|
|
1216
|
+
if (!domain) {
|
|
1217
|
+
spinner.fail(
|
|
1218
|
+
import_chalk5.default.red(`Domain ${apex} not found. Run \`sylphx domains add ${hostname}\` first.`)
|
|
1219
|
+
);
|
|
1220
|
+
process.exit(1);
|
|
1221
|
+
}
|
|
1222
|
+
const h = findHostname(domain, hostname);
|
|
1223
|
+
if (!h) {
|
|
1224
|
+
spinner.fail(
|
|
1225
|
+
import_chalk5.default.red(
|
|
1226
|
+
`Hostname ${hostname} not registered. Run \`sylphx domains add ${hostname}\` first.`
|
|
1227
|
+
)
|
|
1228
|
+
);
|
|
1229
|
+
process.exit(1);
|
|
1230
|
+
}
|
|
1231
|
+
const updated = await api.checkHostname(linked.appId, domain.id, h.id);
|
|
1232
|
+
spinner.stop();
|
|
1233
|
+
if (updated.dnsStatus === "active") {
|
|
1234
|
+
console.log(import_chalk5.default.green(`
|
|
1235
|
+
\u2713 ${hostname} is active
|
|
1236
|
+
`));
|
|
1237
|
+
} else {
|
|
1238
|
+
console.log(import_chalk5.default.yellow(`
|
|
1239
|
+
\u26A0 ${hostname} \u2014 ${dnsStatusBadge(updated.dnsStatus)}`));
|
|
1240
|
+
if (updated.lastCheckError) console.log(import_chalk5.default.dim(` ${updated.lastCheckError}`));
|
|
1241
|
+
console.log("");
|
|
1242
|
+
console.log(import_chalk5.default.bold(" DNS record required:"));
|
|
1243
|
+
console.log(
|
|
1244
|
+
` ${updated.instruction.type} ${import_chalk5.default.cyan(updated.instruction.name)} \u2192 ${updated.instruction.value}`
|
|
1245
|
+
);
|
|
1246
|
+
console.log("");
|
|
1247
|
+
}
|
|
1248
|
+
} catch (err) {
|
|
1249
|
+
spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1250
|
+
process.exit(1);
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
var emailEnableCommand = new import_commander4.Command("enable").description("Enable DKIM email sending for a domain").argument("<apex-domain>", "Apex domain to enable email for (e.g. example.com)").option("--from <email>", "Default from address (e.g. support@example.com)").option("--name <name>", 'Default sender display name (e.g. "Acme Support")').option("--env <env>", "Environment", "production").action(async (apexDomain, opts) => {
|
|
1254
|
+
const linked = requireLinkedApp2();
|
|
1255
|
+
const spinner = (0, import_ora4.default)(`Enabling email for ${apexDomain}...`).start();
|
|
1256
|
+
try {
|
|
1257
|
+
const domains = await api.listDomains(linked.appId, opts.env);
|
|
1258
|
+
let domain = findDomainByApex(domains, apexDomain);
|
|
1259
|
+
if (!domain) {
|
|
1260
|
+
spinner.text = `Registering ${apexDomain}...`;
|
|
1261
|
+
domain = await api.createDomain(linked.appId, apexDomain, opts.env);
|
|
1262
|
+
}
|
|
1263
|
+
if (domain.email) {
|
|
1264
|
+
spinner.warn(
|
|
1265
|
+
import_chalk5.default.yellow(
|
|
1266
|
+
`Email already enabled for ${apexDomain}. Use \`sylphx domains email verify\` to check status.`
|
|
1267
|
+
)
|
|
1268
|
+
);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const binding = await api.enableEmail(linked.appId, domain.id, {
|
|
1272
|
+
defaultFromEmail: opts.from,
|
|
1273
|
+
defaultFromName: opts.name
|
|
1274
|
+
});
|
|
1275
|
+
spinner.succeed(import_chalk5.default.green(`Email enabled for ${import_chalk5.default.bold(apexDomain)}`));
|
|
1276
|
+
console.log("");
|
|
1277
|
+
console.log(import_chalk5.default.bold(" DNS Records to add:"));
|
|
1278
|
+
console.log("");
|
|
1279
|
+
for (const rec of binding.records) {
|
|
1280
|
+
console.log(` ${import_chalk5.default.dim(rec.type.padEnd(5))} ${import_chalk5.default.cyan(rec.name)}`);
|
|
1281
|
+
console.log(` ${import_chalk5.default.dim("value:")} ${rec.value}`);
|
|
1282
|
+
if (rec.priority !== void 0) console.log(` ${import_chalk5.default.dim("priority:")} ${rec.priority}`);
|
|
1283
|
+
console.log(` ${import_chalk5.default.dim("TTL:")} ${rec.ttl}`);
|
|
1284
|
+
if (rec.description) console.log(` ${import_chalk5.default.dim(rec.description)}`);
|
|
1285
|
+
console.log("");
|
|
1286
|
+
}
|
|
1287
|
+
console.log(
|
|
1288
|
+
import_chalk5.default.dim(` Run \`sylphx domains email verify ${apexDomain}\` after updating DNS.`)
|
|
1289
|
+
);
|
|
1290
|
+
console.log("");
|
|
1291
|
+
} catch (err) {
|
|
1292
|
+
spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1293
|
+
process.exit(1);
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
var emailVerifyCommand = new import_commander4.Command("verify").description("Check DKIM DNS propagation").argument("<apex-domain>", "Apex domain to verify").option("--env <env>", "Environment", "production").action(async (apexDomain, opts) => {
|
|
1297
|
+
const linked = requireLinkedApp2();
|
|
1298
|
+
const spinner = (0, import_ora4.default)(`Checking DKIM DNS for ${apexDomain}...`).start();
|
|
1299
|
+
try {
|
|
1300
|
+
const domains = await api.listDomains(linked.appId, opts.env);
|
|
1301
|
+
const domain = findDomainByApex(domains, apexDomain);
|
|
1302
|
+
if (!domain?.email) {
|
|
1303
|
+
spinner.fail(
|
|
1304
|
+
import_chalk5.default.red(
|
|
1305
|
+
`Email not enabled for ${apexDomain}. Run \`sylphx domains email enable ${apexDomain}\` first.`
|
|
1306
|
+
)
|
|
1307
|
+
);
|
|
1308
|
+
process.exit(1);
|
|
1309
|
+
}
|
|
1310
|
+
const binding = await api.checkEmail(linked.appId, domain.id);
|
|
1311
|
+
spinner.stop();
|
|
1312
|
+
if (binding.dnsStatus === "verified") {
|
|
1313
|
+
console.log(import_chalk5.default.green(`
|
|
1314
|
+
\u2713 ${apexDomain} email verified \u2014 DKIM active
|
|
1315
|
+
`));
|
|
1316
|
+
} else {
|
|
1317
|
+
console.log(import_chalk5.default.yellow(`
|
|
1318
|
+
${apexDomain} \u2014 ${dnsStatusBadge(binding.dnsStatus)}`));
|
|
1319
|
+
if (!binding.stalwartLoaded)
|
|
1320
|
+
console.log(import_chalk5.default.dim(" (DKIM not yet loaded into mail server)"));
|
|
1321
|
+
if (binding.verificationError) console.log(import_chalk5.default.red(` ${binding.verificationError}`));
|
|
1322
|
+
console.log("");
|
|
1323
|
+
console.log(import_chalk5.default.bold(" DNS records still pending:"));
|
|
1324
|
+
for (const rec of binding.records.filter((r) => r.status !== "verified")) {
|
|
1325
|
+
printDnsRecord({ ...rec, status: rec.status ?? void 0 });
|
|
1326
|
+
}
|
|
1327
|
+
console.log("");
|
|
1328
|
+
}
|
|
1329
|
+
} catch (err) {
|
|
1330
|
+
spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1331
|
+
process.exit(1);
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
var emailDisableCommand = new import_commander4.Command("disable").description("Disable email sending for a domain").argument("<apex-domain>", "Apex domain").option("--env <env>", "Environment", "production").action(async (apexDomain, opts) => {
|
|
1335
|
+
const linked = requireLinkedApp2();
|
|
1336
|
+
const spinner = (0, import_ora4.default)(`Disabling email for ${apexDomain}...`).start();
|
|
1337
|
+
try {
|
|
1338
|
+
const domains = await api.listDomains(linked.appId, opts.env);
|
|
1339
|
+
const domain = findDomainByApex(domains, apexDomain);
|
|
1340
|
+
if (!domain?.email) {
|
|
1341
|
+
spinner.fail(import_chalk5.default.red(`Email not enabled for ${apexDomain}`));
|
|
1342
|
+
process.exit(1);
|
|
1343
|
+
}
|
|
1344
|
+
await api.disableEmail(linked.appId, domain.id);
|
|
1345
|
+
spinner.succeed(import_chalk5.default.green(`Email disabled for ${import_chalk5.default.bold(apexDomain)}`));
|
|
1346
|
+
} catch (err) {
|
|
1347
|
+
spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1348
|
+
process.exit(1);
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
var domainsEmailCommand = new import_commander4.Command("email").description("Manage email sending for a domain").addCommand(emailEnableCommand).addCommand(emailVerifyCommand).addCommand(emailDisableCommand);
|
|
1352
|
+
var domainsCommand = new import_commander4.Command("domains").description("Manage custom domains").addCommand(domainsListCommand).addCommand(domainsAddCommand).addCommand(domainsRmCommand).addCommand(domainsVerifyCommand).addCommand(domainsEmailCommand);
|
|
1353
|
+
|
|
1354
|
+
// src/commands/env.ts
|
|
1355
|
+
var import_node_fs = __toESM(require("fs"));
|
|
1356
|
+
var import_node_path2 = __toESM(require("path"));
|
|
1357
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
1358
|
+
var import_commander5 = require("commander");
|
|
1359
|
+
var import_ora5 = __toESM(require("ora"));
|
|
1360
|
+
function requireLinkedApp3() {
|
|
763
1361
|
const token = config.getToken();
|
|
764
1362
|
if (!token) {
|
|
765
|
-
console.log(
|
|
1363
|
+
console.log(import_chalk6.default.red("Not authenticated. Run `sylphx login` first."));
|
|
766
1364
|
process.exit(1);
|
|
767
1365
|
}
|
|
768
1366
|
const linked = config.getLinkedApp();
|
|
769
1367
|
if (!linked) {
|
|
770
|
-
console.log(
|
|
1368
|
+
console.log(import_chalk6.default.red("No app linked. Run `sylphx link` first."));
|
|
771
1369
|
process.exit(1);
|
|
772
1370
|
}
|
|
773
1371
|
return linked;
|
|
774
1372
|
}
|
|
775
|
-
var envListCommand = new
|
|
776
|
-
const linked =
|
|
1373
|
+
var envListCommand = new import_commander5.Command("list").description("List environment variables").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
|
|
1374
|
+
const linked = requireLinkedApp3();
|
|
777
1375
|
const env = opts.env ?? linked.defaultEnv;
|
|
778
|
-
const spinner = (0,
|
|
1376
|
+
const spinner = (0, import_ora5.default)(`Fetching env vars [${env}]...`).start();
|
|
779
1377
|
try {
|
|
780
1378
|
const vars = await api.listEnvVars(linked.appId, env);
|
|
781
1379
|
spinner.stop();
|
|
782
1380
|
if (vars.length === 0) {
|
|
783
|
-
console.log(
|
|
1381
|
+
console.log(import_chalk6.default.dim("\n No environment variables set.\n"));
|
|
784
1382
|
return;
|
|
785
1383
|
}
|
|
786
1384
|
console.log("");
|
|
787
|
-
console.log(
|
|
788
|
-
console.log(
|
|
1385
|
+
console.log(import_chalk6.default.bold(` Environment Variables [${import_chalk6.default.white(env)}]`));
|
|
1386
|
+
console.log(import_chalk6.default.dim(` ${"\u2500".repeat(50)}`));
|
|
789
1387
|
console.log("");
|
|
790
1388
|
const maxKeyLen = Math.max(...vars.map((v) => v.key.length));
|
|
791
1389
|
for (const v of vars) {
|
|
792
|
-
const key =
|
|
1390
|
+
const key = import_chalk6.default.cyan(v.key.padEnd(maxKeyLen));
|
|
793
1391
|
const isSecret = v.isSecret ?? v.secret;
|
|
794
|
-
const value = isSecret ?
|
|
1392
|
+
const value = isSecret ? import_chalk6.default.dim("****** (secret)") : import_chalk6.default.white(v.value);
|
|
795
1393
|
console.log(` ${key} ${value}`);
|
|
796
1394
|
}
|
|
797
1395
|
console.log("");
|
|
798
1396
|
} catch (err) {
|
|
799
|
-
spinner.fail(
|
|
1397
|
+
spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
800
1398
|
process.exit(1);
|
|
801
1399
|
}
|
|
802
1400
|
});
|
|
803
|
-
var envSetCommand = new
|
|
804
|
-
const linked =
|
|
1401
|
+
var envSetCommand = new import_commander5.Command("set").description("Set an environment variable (KEY=VALUE)").argument("<assignment>", "Key=Value assignment, e.g. DATABASE_URL=postgres://...").option("--env <env>", "Environment (e.g. production, staging)").option("--secret", "Mark as secret").action(async (assignment, opts) => {
|
|
1402
|
+
const linked = requireLinkedApp3();
|
|
805
1403
|
const env = opts.env ?? linked.defaultEnv;
|
|
806
1404
|
const eqIdx = assignment.indexOf("=");
|
|
807
1405
|
if (eqIdx < 1) {
|
|
808
1406
|
console.log(
|
|
809
|
-
|
|
1407
|
+
import_chalk6.default.red(
|
|
810
1408
|
"Invalid format. Use KEY=VALUE (e.g. sylphx env set DATABASE_URL=postgres://...)"
|
|
811
1409
|
)
|
|
812
1410
|
);
|
|
@@ -815,40 +1413,40 @@ var envSetCommand = new import_commander4.Command("set").description("Set an env
|
|
|
815
1413
|
const key = assignment.slice(0, eqIdx).trim();
|
|
816
1414
|
const value = assignment.slice(eqIdx + 1);
|
|
817
1415
|
if (!key) {
|
|
818
|
-
console.log(
|
|
1416
|
+
console.log(import_chalk6.default.red("Key cannot be empty."));
|
|
819
1417
|
process.exit(1);
|
|
820
1418
|
}
|
|
821
|
-
const spinner = (0,
|
|
1419
|
+
const spinner = (0, import_ora5.default)(`Setting ${key} [${env}]...`).start();
|
|
822
1420
|
try {
|
|
823
1421
|
await api.setEnvVar(linked.appId, key, value, env, opts.secret);
|
|
824
|
-
spinner.succeed(
|
|
1422
|
+
spinner.succeed(import_chalk6.default.green(`\u2713 Set ${import_chalk6.default.bold(key)} in ${env}`));
|
|
825
1423
|
} catch (err) {
|
|
826
|
-
spinner.fail(
|
|
1424
|
+
spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
827
1425
|
process.exit(1);
|
|
828
1426
|
}
|
|
829
1427
|
});
|
|
830
|
-
var envRmCommand = new
|
|
831
|
-
const linked =
|
|
1428
|
+
var envRmCommand = new import_commander5.Command("rm").description("Remove an environment variable").argument("<key>", "Variable name to remove").option("--env <env>", "Environment (e.g. production, staging)").action(async (key, opts) => {
|
|
1429
|
+
const linked = requireLinkedApp3();
|
|
832
1430
|
const env = opts.env ?? linked.defaultEnv;
|
|
833
|
-
const spinner = (0,
|
|
1431
|
+
const spinner = (0, import_ora5.default)(`Removing ${key} [${env}]...`).start();
|
|
834
1432
|
try {
|
|
835
1433
|
await api.deleteEnvVar(linked.appId, key, env);
|
|
836
|
-
spinner.succeed(
|
|
1434
|
+
spinner.succeed(import_chalk6.default.green(`\u2713 Removed ${import_chalk6.default.bold(key)} from ${env}`));
|
|
837
1435
|
} catch (err) {
|
|
838
|
-
spinner.fail(
|
|
1436
|
+
spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
839
1437
|
process.exit(1);
|
|
840
1438
|
}
|
|
841
1439
|
});
|
|
842
|
-
var envPullCommand = new
|
|
843
|
-
const linked =
|
|
1440
|
+
var envPullCommand = new import_commander5.Command("pull").description("Download env vars from the platform to a local .env file").option("--env <env>", "Environment (e.g. production, staging)").option("--file <file>", ".env file path", ".env").action(async (opts) => {
|
|
1441
|
+
const linked = requireLinkedApp3();
|
|
844
1442
|
const env = opts.env ?? linked.defaultEnv;
|
|
845
1443
|
const filePath = import_node_path2.default.resolve(opts.file);
|
|
846
|
-
const spinner = (0,
|
|
1444
|
+
const spinner = (0, import_ora5.default)(`Pulling env vars [${env}]...`).start();
|
|
847
1445
|
try {
|
|
848
1446
|
const vars = await api.listEnvVars(linked.appId, env);
|
|
849
1447
|
spinner.stop();
|
|
850
1448
|
if (vars.length === 0) {
|
|
851
|
-
console.log(
|
|
1449
|
+
console.log(import_chalk6.default.dim("\n No environment variables to pull.\n"));
|
|
852
1450
|
return;
|
|
853
1451
|
}
|
|
854
1452
|
const lines = [
|
|
@@ -871,19 +1469,19 @@ var envPullCommand = new import_commander4.Command("pull").description("Download
|
|
|
871
1469
|
}
|
|
872
1470
|
lines.push("");
|
|
873
1471
|
import_node_fs.default.writeFileSync(filePath, lines.join("\n"), "utf-8");
|
|
874
|
-
console.log(
|
|
875
|
-
console.log(
|
|
1472
|
+
console.log(import_chalk6.default.green(`\u2713 Wrote ${vars.length} variable(s) to ${import_chalk6.default.bold(opts.file)}`));
|
|
1473
|
+
console.log(import_chalk6.default.dim(` Secrets are shown as ${import_chalk6.default.white("KEY=")} (value omitted).`));
|
|
876
1474
|
} catch (err) {
|
|
877
|
-
spinner.fail(
|
|
1475
|
+
spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
878
1476
|
process.exit(1);
|
|
879
1477
|
}
|
|
880
1478
|
});
|
|
881
|
-
var envPushCommand = new
|
|
882
|
-
const linked =
|
|
1479
|
+
var envPushCommand = new import_commander5.Command("push").description("Push a local .env file to the platform").option("--env <env>", "Environment (e.g. production, staging)").option("--file <file>", ".env file path", ".env").action(async (opts) => {
|
|
1480
|
+
const linked = requireLinkedApp3();
|
|
883
1481
|
const env = opts.env ?? linked.defaultEnv;
|
|
884
1482
|
const filePath = import_node_path2.default.resolve(opts.file);
|
|
885
1483
|
if (!import_node_fs.default.existsSync(filePath)) {
|
|
886
|
-
console.log(
|
|
1484
|
+
console.log(import_chalk6.default.red(`File not found: ${opts.file}`));
|
|
887
1485
|
process.exit(1);
|
|
888
1486
|
}
|
|
889
1487
|
const content = import_node_fs.default.readFileSync(filePath, "utf-8");
|
|
@@ -903,29 +1501,29 @@ var envPushCommand = new import_commander4.Command("push").description("Push a l
|
|
|
903
1501
|
vars.push({ key, value, secret: false });
|
|
904
1502
|
}
|
|
905
1503
|
if (vars.length === 0) {
|
|
906
|
-
console.log(
|
|
1504
|
+
console.log(import_chalk6.default.yellow(" No variables to push (all lines empty or comments)."));
|
|
907
1505
|
return;
|
|
908
1506
|
}
|
|
909
|
-
const spinner = (0,
|
|
1507
|
+
const spinner = (0, import_ora5.default)(`Pushing ${vars.length} variable(s) to [${env}]...`).start();
|
|
910
1508
|
try {
|
|
911
1509
|
await api.setEnvVars(linked.appId, vars, env);
|
|
912
|
-
spinner.succeed(
|
|
913
|
-
console.log(
|
|
1510
|
+
spinner.succeed(import_chalk6.default.green(`\u2713 Pushed ${vars.length} variable(s) to ${import_chalk6.default.bold(env)}`));
|
|
1511
|
+
console.log(import_chalk6.default.dim(` Run ${import_chalk6.default.cyan("sylphx deploy")} to apply the new variables.`));
|
|
914
1512
|
} catch (err) {
|
|
915
|
-
spinner.fail(
|
|
1513
|
+
spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
916
1514
|
process.exit(1);
|
|
917
1515
|
}
|
|
918
1516
|
});
|
|
919
|
-
var envCommand = new
|
|
1517
|
+
var envCommand = new import_commander5.Command("env").description("Manage environment variables").addCommand(envListCommand).addCommand(envSetCommand).addCommand(envRmCommand).addCommand(envPullCommand).addCommand(envPushCommand);
|
|
920
1518
|
|
|
921
1519
|
// src/commands/init.ts
|
|
922
1520
|
var import_node_child_process = require("child_process");
|
|
923
1521
|
var import_node_fs2 = __toESM(require("fs"));
|
|
924
1522
|
var import_node_path3 = __toESM(require("path"));
|
|
925
1523
|
var import_node_readline2 = __toESM(require("readline"));
|
|
926
|
-
var
|
|
927
|
-
var
|
|
928
|
-
var
|
|
1524
|
+
var import_chalk7 = __toESM(require("chalk"));
|
|
1525
|
+
var import_commander6 = require("commander");
|
|
1526
|
+
var import_ora6 = __toESM(require("ora"));
|
|
929
1527
|
function prompt2(question, defaultValue) {
|
|
930
1528
|
return new Promise((resolve) => {
|
|
931
1529
|
const rl = import_node_readline2.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -960,10 +1558,10 @@ function detectGitRemote() {
|
|
|
960
1558
|
return void 0;
|
|
961
1559
|
}
|
|
962
1560
|
}
|
|
963
|
-
var initCommand = new
|
|
1561
|
+
var initCommand = new import_commander6.Command("init").description("Create and link a new Sylphx project in one step").argument("[name]", "Project name (detected from package.json if omitted)").option("--git", "Auto-detect git remote URL and set as gitRepository").option("--port <port>", "Application port").action(async (nameArg, opts) => {
|
|
964
1562
|
const token = config.getToken();
|
|
965
1563
|
if (!token) {
|
|
966
|
-
console.log(
|
|
1564
|
+
console.log(import_chalk7.default.red("Not authenticated. Run `sylphx login` first."));
|
|
967
1565
|
process.exit(1);
|
|
968
1566
|
}
|
|
969
1567
|
const pkg = readPackageJson();
|
|
@@ -971,51 +1569,51 @@ var initCommand = new import_commander5.Command("init").description("Create and
|
|
|
971
1569
|
let projectName;
|
|
972
1570
|
if (detectedName) {
|
|
973
1571
|
projectName = detectedName;
|
|
974
|
-
console.log(
|
|
1572
|
+
console.log(import_chalk7.default.dim(` Project name: ${import_chalk7.default.white(projectName)}`));
|
|
975
1573
|
} else {
|
|
976
1574
|
projectName = await prompt2(" Project name");
|
|
977
1575
|
if (!projectName) {
|
|
978
|
-
console.log(
|
|
1576
|
+
console.log(import_chalk7.default.red("Project name is required."));
|
|
979
1577
|
process.exit(1);
|
|
980
1578
|
}
|
|
981
1579
|
}
|
|
982
1580
|
const slug = slugify(projectName);
|
|
983
1581
|
let orgId = config.getDefaultOrg();
|
|
984
1582
|
if (!orgId) {
|
|
985
|
-
const spinner2 = (0,
|
|
1583
|
+
const spinner2 = (0, import_ora6.default)("Fetching your organizations...").start();
|
|
986
1584
|
let orgs = [];
|
|
987
1585
|
try {
|
|
988
1586
|
const me = await api.whoami();
|
|
989
1587
|
orgs = me.orgs;
|
|
990
1588
|
spinner2.stop();
|
|
991
1589
|
} catch (err) {
|
|
992
|
-
spinner2.fail(
|
|
1590
|
+
spinner2.fail(import_chalk7.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
993
1591
|
process.exit(1);
|
|
994
1592
|
}
|
|
995
1593
|
if (orgs.length === 0) {
|
|
996
|
-
console.log(
|
|
1594
|
+
console.log(import_chalk7.default.red("No organizations found. Create one at https://app.sylphx.com."));
|
|
997
1595
|
process.exit(1);
|
|
998
1596
|
}
|
|
999
1597
|
if (orgs.length === 1 && orgs[0]) {
|
|
1000
1598
|
orgId = orgs[0].slug;
|
|
1001
|
-
console.log(
|
|
1599
|
+
console.log(import_chalk7.default.dim(` Using org: ${import_chalk7.default.white(orgId)}`));
|
|
1002
1600
|
} else {
|
|
1003
|
-
console.log(
|
|
1601
|
+
console.log(import_chalk7.default.bold("\n Your organizations:\n"));
|
|
1004
1602
|
orgs.forEach((org, i) => {
|
|
1005
|
-
console.log(` ${
|
|
1603
|
+
console.log(` ${import_chalk7.default.cyan(String(i + 1))}. ${org.slug} ${import_chalk7.default.dim(`(${org.name})`)}`);
|
|
1006
1604
|
});
|
|
1007
1605
|
console.log("");
|
|
1008
1606
|
const choice = await prompt2(" Select org (number)");
|
|
1009
1607
|
const idx = Number.parseInt(choice, 10) - 1;
|
|
1010
1608
|
if (Number.isNaN(idx) || idx < 0 || idx >= orgs.length || !orgs[idx]) {
|
|
1011
|
-
console.log(
|
|
1609
|
+
console.log(import_chalk7.default.red("Invalid selection."));
|
|
1012
1610
|
process.exit(1);
|
|
1013
1611
|
}
|
|
1014
1612
|
orgId = orgs[idx]?.slug;
|
|
1015
1613
|
}
|
|
1016
1614
|
}
|
|
1017
1615
|
if (!orgId) {
|
|
1018
|
-
console.log(
|
|
1616
|
+
console.log(import_chalk7.default.red("Could not determine org."));
|
|
1019
1617
|
process.exit(1);
|
|
1020
1618
|
}
|
|
1021
1619
|
let gitRepository;
|
|
@@ -1026,9 +1624,9 @@ var initCommand = new import_commander5.Command("init").description("Create and
|
|
|
1026
1624
|
if (sshMatch?.[1]) gitRepository = sshMatch[1];
|
|
1027
1625
|
const httpsMatch = gitRepository.match(/github\.com\/(.+?)(?:\.git)?$/);
|
|
1028
1626
|
if (httpsMatch?.[1]) gitRepository = httpsMatch[1];
|
|
1029
|
-
console.log(
|
|
1627
|
+
console.log(import_chalk7.default.dim(` Git repo: ${import_chalk7.default.white(gitRepository)}`));
|
|
1030
1628
|
} else {
|
|
1031
|
-
console.log(
|
|
1629
|
+
console.log(import_chalk7.default.yellow(" \u26A0 No git remote found (--git flag ignored)."));
|
|
1032
1630
|
}
|
|
1033
1631
|
}
|
|
1034
1632
|
let appPort;
|
|
@@ -1036,10 +1634,10 @@ var initCommand = new import_commander5.Command("init").description("Create and
|
|
|
1036
1634
|
appPort = Number.parseInt(opts.port, 10);
|
|
1037
1635
|
} else if (pkg) {
|
|
1038
1636
|
appPort = detectPortFromPackage(pkg);
|
|
1039
|
-
if (appPort) console.log(
|
|
1637
|
+
if (appPort) console.log(import_chalk7.default.dim(` Detected port: ${import_chalk7.default.white(String(appPort))}`));
|
|
1040
1638
|
}
|
|
1041
1639
|
console.log("");
|
|
1042
|
-
const spinner = (0,
|
|
1640
|
+
const spinner = (0, import_ora6.default)(`Creating project ${import_chalk7.default.bold(projectName)}...`).start();
|
|
1043
1641
|
try {
|
|
1044
1642
|
const project = await api.createProject({
|
|
1045
1643
|
name: projectName,
|
|
@@ -1049,17 +1647,17 @@ var initCommand = new import_commander5.Command("init").description("Create and
|
|
|
1049
1647
|
});
|
|
1050
1648
|
config.linkApp({ appId: project.id, orgId, defaultEnv: "production" });
|
|
1051
1649
|
config.setDefaultOrg(orgId);
|
|
1052
|
-
spinner.succeed(
|
|
1650
|
+
spinner.succeed(import_chalk7.default.green(`\u2713 Created and linked project ${import_chalk7.default.bold(project.name)}`));
|
|
1053
1651
|
console.log("");
|
|
1054
|
-
console.log(
|
|
1055
|
-
console.log(
|
|
1056
|
-
console.log(
|
|
1057
|
-
console.log(
|
|
1652
|
+
console.log(import_chalk7.default.dim(` Project ID : ${project.id}`));
|
|
1653
|
+
console.log(import_chalk7.default.dim(` Slug : ${project.slug}`));
|
|
1654
|
+
console.log(import_chalk7.default.dim(` Org : ${orgId}`));
|
|
1655
|
+
console.log(import_chalk7.default.dim(` Directory : ${process.cwd()}`));
|
|
1058
1656
|
console.log("");
|
|
1059
|
-
console.log(
|
|
1657
|
+
console.log(import_chalk7.default.dim(` ${import_chalk7.default.cyan("sylphx deploy")} \u2014 deploy to production when ready`));
|
|
1060
1658
|
console.log("");
|
|
1061
1659
|
} catch (err) {
|
|
1062
|
-
spinner.fail(
|
|
1660
|
+
spinner.fail(import_chalk7.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1063
1661
|
process.exit(1);
|
|
1064
1662
|
}
|
|
1065
1663
|
});
|
|
@@ -1068,9 +1666,9 @@ var initCommand = new import_commander5.Command("init").description("Create and
|
|
|
1068
1666
|
var import_node_fs3 = __toESM(require("fs"));
|
|
1069
1667
|
var import_node_path4 = __toESM(require("path"));
|
|
1070
1668
|
var import_node_readline3 = __toESM(require("readline"));
|
|
1071
|
-
var
|
|
1072
|
-
var
|
|
1073
|
-
var
|
|
1669
|
+
var import_chalk8 = __toESM(require("chalk"));
|
|
1670
|
+
var import_commander7 = require("commander");
|
|
1671
|
+
var import_ora7 = __toESM(require("ora"));
|
|
1074
1672
|
function readPackageName() {
|
|
1075
1673
|
try {
|
|
1076
1674
|
const pkgPath = import_node_path4.default.join(process.cwd(), "package.json");
|
|
@@ -1092,86 +1690,86 @@ function prompt3(question) {
|
|
|
1092
1690
|
});
|
|
1093
1691
|
});
|
|
1094
1692
|
}
|
|
1095
|
-
var linkCommand = new
|
|
1693
|
+
var linkCommand = new import_commander7.Command("link").description("Link current directory to a Sylphx app").option("--org <org>", "Organization slug").action(async (opts) => {
|
|
1096
1694
|
const token = config.getToken();
|
|
1097
1695
|
if (!token) {
|
|
1098
|
-
console.log(
|
|
1696
|
+
console.log(import_chalk8.default.red("Not authenticated. Run `sylphx login` first."));
|
|
1099
1697
|
process.exit(1);
|
|
1100
1698
|
}
|
|
1101
1699
|
const pkgName = readPackageName();
|
|
1102
1700
|
if (pkgName) {
|
|
1103
|
-
console.log(
|
|
1701
|
+
console.log(import_chalk8.default.dim(` Detected package: ${import_chalk8.default.white(pkgName)}`));
|
|
1104
1702
|
}
|
|
1105
1703
|
let orgId = opts.org ?? config.getDefaultOrg();
|
|
1106
1704
|
if (!orgId) {
|
|
1107
|
-
const spinner = (0,
|
|
1705
|
+
const spinner = (0, import_ora7.default)("Fetching your organizations...").start();
|
|
1108
1706
|
let orgs = [];
|
|
1109
1707
|
try {
|
|
1110
1708
|
const me = await api.whoami();
|
|
1111
1709
|
orgs = me.orgs;
|
|
1112
1710
|
spinner.stop();
|
|
1113
1711
|
} catch (err) {
|
|
1114
|
-
spinner.fail(
|
|
1712
|
+
spinner.fail(import_chalk8.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1115
1713
|
process.exit(1);
|
|
1116
1714
|
}
|
|
1117
1715
|
if (orgs.length === 0) {
|
|
1118
|
-
console.log(
|
|
1716
|
+
console.log(import_chalk8.default.red("No organizations found."));
|
|
1119
1717
|
process.exit(1);
|
|
1120
1718
|
}
|
|
1121
1719
|
if (orgs.length === 1 && orgs[0]) {
|
|
1122
1720
|
orgId = orgs[0].slug;
|
|
1123
|
-
console.log(
|
|
1721
|
+
console.log(import_chalk8.default.dim(` Using org: ${import_chalk8.default.white(orgId)}`));
|
|
1124
1722
|
} else {
|
|
1125
|
-
console.log(
|
|
1723
|
+
console.log(import_chalk8.default.bold("\n Your organizations:\n"));
|
|
1126
1724
|
orgs.forEach((org, i) => {
|
|
1127
|
-
console.log(` ${
|
|
1725
|
+
console.log(` ${import_chalk8.default.cyan(String(i + 1))}. ${org.slug} ${import_chalk8.default.dim(`(${org.name})`)}`);
|
|
1128
1726
|
});
|
|
1129
1727
|
console.log("");
|
|
1130
1728
|
const choice = await prompt3(" Select org (number): ");
|
|
1131
1729
|
const idx = Number.parseInt(choice, 10) - 1;
|
|
1132
1730
|
if (Number.isNaN(idx) || idx < 0 || idx >= orgs.length || !orgs[idx]) {
|
|
1133
|
-
console.log(
|
|
1731
|
+
console.log(import_chalk8.default.red("Invalid selection."));
|
|
1134
1732
|
process.exit(1);
|
|
1135
1733
|
}
|
|
1136
1734
|
orgId = orgs[idx]?.slug;
|
|
1137
1735
|
}
|
|
1138
1736
|
}
|
|
1139
|
-
const spinner2 = (0,
|
|
1737
|
+
const spinner2 = (0, import_ora7.default)("Fetching projects...").start();
|
|
1140
1738
|
let apps = [];
|
|
1141
1739
|
try {
|
|
1142
1740
|
apps = await api.listProjects();
|
|
1143
1741
|
spinner2.stop();
|
|
1144
1742
|
} catch (err) {
|
|
1145
|
-
spinner2.fail(
|
|
1743
|
+
spinner2.fail(import_chalk8.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1146
1744
|
process.exit(1);
|
|
1147
1745
|
}
|
|
1148
1746
|
if (apps.length === 0) {
|
|
1149
|
-
console.log(
|
|
1747
|
+
console.log(import_chalk8.default.yellow("No apps found for this org."));
|
|
1150
1748
|
process.exit(1);
|
|
1151
1749
|
}
|
|
1152
|
-
console.log(
|
|
1750
|
+
console.log(import_chalk8.default.bold("\n Available apps:\n"));
|
|
1153
1751
|
apps.forEach((app, i) => {
|
|
1154
|
-
console.log(` ${
|
|
1752
|
+
console.log(` ${import_chalk8.default.cyan(String(i + 1))}. ${app.slug} ${import_chalk8.default.dim(`(${app.name})`)}`);
|
|
1155
1753
|
});
|
|
1156
1754
|
console.log("");
|
|
1157
1755
|
const appChoice = await prompt3(" Select app (number): ");
|
|
1158
1756
|
const appIdx = Number.parseInt(appChoice, 10) - 1;
|
|
1159
1757
|
if (Number.isNaN(appIdx) || appIdx < 0 || appIdx >= apps.length || !apps[appIdx]) {
|
|
1160
|
-
console.log(
|
|
1758
|
+
console.log(import_chalk8.default.red("Invalid selection."));
|
|
1161
1759
|
process.exit(1);
|
|
1162
1760
|
}
|
|
1163
1761
|
const selectedApp = apps[appIdx];
|
|
1164
1762
|
if (!selectedApp) {
|
|
1165
|
-
console.log(
|
|
1763
|
+
console.log(import_chalk8.default.red("Invalid selection."));
|
|
1166
1764
|
process.exit(1);
|
|
1167
1765
|
}
|
|
1168
1766
|
let defaultEnv = "production";
|
|
1169
1767
|
const envs = selectedApp.environments ?? [];
|
|
1170
1768
|
if (envs.length > 1) {
|
|
1171
|
-
console.log(
|
|
1769
|
+
console.log(import_chalk8.default.bold("\n Available environments:\n"));
|
|
1172
1770
|
envs.forEach((env, i) => {
|
|
1173
1771
|
console.log(
|
|
1174
|
-
` ${
|
|
1772
|
+
` ${import_chalk8.default.cyan(String(i + 1))}. ${env.envType ?? env.slug ?? env.name} ${import_chalk8.default.dim(`(${env.name})`)}`
|
|
1175
1773
|
);
|
|
1176
1774
|
});
|
|
1177
1775
|
console.log("");
|
|
@@ -1194,18 +1792,19 @@ var linkCommand = new import_commander6.Command("link").description("Link curren
|
|
|
1194
1792
|
config.setDefaultOrg(orgId);
|
|
1195
1793
|
}
|
|
1196
1794
|
console.log("");
|
|
1197
|
-
console.log(
|
|
1795
|
+
console.log(import_chalk8.default.green(`\u2713 Linked to ${import_chalk8.default.bold(selectedApp.slug)}`));
|
|
1198
1796
|
console.log(
|
|
1199
|
-
|
|
1797
|
+
import_chalk8.default.dim(` App ID: ${selectedApp.id} | Env: ${defaultEnv} | Dir: ${process.cwd()}`)
|
|
1200
1798
|
);
|
|
1201
1799
|
console.log("");
|
|
1202
1800
|
});
|
|
1203
1801
|
|
|
1204
1802
|
// src/commands/login.ts
|
|
1205
1803
|
var import_node_crypto = __toESM(require("crypto"));
|
|
1206
|
-
var
|
|
1207
|
-
var
|
|
1208
|
-
var
|
|
1804
|
+
var import_chalk9 = __toESM(require("chalk"));
|
|
1805
|
+
var import_commander8 = require("commander");
|
|
1806
|
+
var import_ora8 = __toESM(require("ora"));
|
|
1807
|
+
var { version: version3 } = package_default;
|
|
1209
1808
|
var BASE_URL2 = process.env.SYLPHX_API_URL ?? "https://sylphx.com";
|
|
1210
1809
|
var POLL_INTERVAL_MS2 = 2e3;
|
|
1211
1810
|
var POLL_TIMEOUT_MS2 = 5 * 60 * 1e3;
|
|
@@ -1214,7 +1813,7 @@ async function initCode(code) {
|
|
|
1214
1813
|
method: "POST",
|
|
1215
1814
|
headers: {
|
|
1216
1815
|
"Content-Type": "application/json",
|
|
1217
|
-
"User-Agent":
|
|
1816
|
+
"User-Agent": `sylphx-cli/${version3}`
|
|
1218
1817
|
},
|
|
1219
1818
|
body: JSON.stringify({ code })
|
|
1220
1819
|
});
|
|
@@ -1222,22 +1821,17 @@ async function initCode(code) {
|
|
|
1222
1821
|
throw new Error(`Failed to initialize auth code (HTTP ${res.status})`);
|
|
1223
1822
|
}
|
|
1224
1823
|
}
|
|
1225
|
-
var loginCommand = new
|
|
1226
|
-
"--token <token>",
|
|
1227
|
-
"Authenticate with a pre-existing API token (for CI/CD)"
|
|
1228
|
-
).action(async (opts) => {
|
|
1824
|
+
var loginCommand = new import_commander8.Command("login").description("Authenticate with the Sylphx platform").option("--token <token>", "Authenticate with a pre-existing API token (for CI/CD)").action(async (opts) => {
|
|
1229
1825
|
if (opts.token) {
|
|
1230
|
-
const spinner2 = (0,
|
|
1826
|
+
const spinner2 = (0, import_ora8.default)("Validating token...").start();
|
|
1231
1827
|
try {
|
|
1232
1828
|
config.setToken(opts.token);
|
|
1233
1829
|
const me = await api.whoami();
|
|
1234
1830
|
config.setToken(opts.token);
|
|
1235
|
-
spinner2.succeed(
|
|
1236
|
-
import_chalk8.default.green(`Authenticated as ${import_chalk8.default.bold(me.user.email)}`)
|
|
1237
|
-
);
|
|
1831
|
+
spinner2.succeed(import_chalk9.default.green(`Authenticated as ${import_chalk9.default.bold(me.user.email)}`));
|
|
1238
1832
|
} catch (err) {
|
|
1239
1833
|
config.clearToken();
|
|
1240
|
-
spinner2.fail(
|
|
1834
|
+
spinner2.fail(import_chalk9.default.red("Invalid token"));
|
|
1241
1835
|
process.exit(1);
|
|
1242
1836
|
}
|
|
1243
1837
|
return;
|
|
@@ -1245,18 +1839,18 @@ var loginCommand = new import_commander7.Command("login").description("Authentic
|
|
|
1245
1839
|
const code = import_node_crypto.default.randomBytes(16).toString("hex").slice(0, 20).toUpperCase();
|
|
1246
1840
|
const authUrl = `${BASE_URL2}/cli-auth?code=${code}`;
|
|
1247
1841
|
console.log("");
|
|
1248
|
-
console.log(
|
|
1842
|
+
console.log(import_chalk9.default.bold(" Sylphx CLI Authentication"));
|
|
1249
1843
|
console.log("");
|
|
1250
1844
|
console.log(" Opening browser to:");
|
|
1251
|
-
console.log(` ${
|
|
1845
|
+
console.log(` ${import_chalk9.default.cyan(authUrl)}`);
|
|
1252
1846
|
console.log("");
|
|
1253
|
-
console.log(` One-time code: ${
|
|
1847
|
+
console.log(` One-time code: ${import_chalk9.default.yellow.bold(code)}`);
|
|
1254
1848
|
console.log("");
|
|
1255
1849
|
try {
|
|
1256
1850
|
await initCode(code);
|
|
1257
1851
|
} catch (err) {
|
|
1258
1852
|
console.log(
|
|
1259
|
-
|
|
1853
|
+
import_chalk9.default.yellow(
|
|
1260
1854
|
` Warning: Could not register code: ${err instanceof Error ? err.message : String(err)}`
|
|
1261
1855
|
)
|
|
1262
1856
|
);
|
|
@@ -1265,13 +1859,9 @@ var loginCommand = new import_commander7.Command("login").description("Authentic
|
|
|
1265
1859
|
const { default: open } = await import("open");
|
|
1266
1860
|
await open(authUrl);
|
|
1267
1861
|
} catch {
|
|
1268
|
-
console.log(
|
|
1269
|
-
import_chalk8.default.dim(
|
|
1270
|
-
" Could not open browser automatically. Please visit the URL above."
|
|
1271
|
-
)
|
|
1272
|
-
);
|
|
1862
|
+
console.log(import_chalk9.default.dim(" Could not open browser automatically. Please visit the URL above."));
|
|
1273
1863
|
}
|
|
1274
|
-
const spinner = (0,
|
|
1864
|
+
const spinner = (0, import_ora8.default)("Waiting for browser authorization...").start();
|
|
1275
1865
|
const deadline = Date.now() + POLL_TIMEOUT_MS2;
|
|
1276
1866
|
while (Date.now() < deadline) {
|
|
1277
1867
|
await sleep(POLL_INTERVAL_MS2);
|
|
@@ -1281,28 +1871,24 @@ var loginCommand = new import_commander7.Command("login").description("Authentic
|
|
|
1281
1871
|
config.setToken(result.token);
|
|
1282
1872
|
try {
|
|
1283
1873
|
const me = await api.whoami();
|
|
1284
|
-
spinner.succeed(
|
|
1285
|
-
import_chalk8.default.green(`Authenticated as ${import_chalk8.default.bold(me.user.email)}`)
|
|
1286
|
-
);
|
|
1874
|
+
spinner.succeed(import_chalk9.default.green(`Authenticated as ${import_chalk9.default.bold(me.user.email)}`));
|
|
1287
1875
|
} catch {
|
|
1288
|
-
spinner.succeed(
|
|
1876
|
+
spinner.succeed(import_chalk9.default.green("Authenticated successfully"));
|
|
1289
1877
|
}
|
|
1290
1878
|
console.log("");
|
|
1291
|
-
console.log(
|
|
1292
|
-
` Token stored at: ${import_chalk8.default.dim(config.getConfigPath())}`
|
|
1293
|
-
);
|
|
1879
|
+
console.log(` Token stored at: ${import_chalk9.default.dim(config.getConfigPath())}`);
|
|
1294
1880
|
console.log("");
|
|
1295
1881
|
return;
|
|
1296
1882
|
}
|
|
1297
1883
|
} catch (err) {
|
|
1298
1884
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1299
1885
|
if (msg.includes("404") || msg.includes("expired")) {
|
|
1300
|
-
spinner.fail(
|
|
1886
|
+
spinner.fail(import_chalk9.default.red("Code expired or invalid. Please try again."));
|
|
1301
1887
|
process.exit(1);
|
|
1302
1888
|
}
|
|
1303
1889
|
}
|
|
1304
1890
|
}
|
|
1305
|
-
spinner.fail(
|
|
1891
|
+
spinner.fail(import_chalk9.default.red("Authentication timed out. Please try again."));
|
|
1306
1892
|
process.exit(1);
|
|
1307
1893
|
});
|
|
1308
1894
|
function sleep(ms) {
|
|
@@ -1310,43 +1896,43 @@ function sleep(ms) {
|
|
|
1310
1896
|
}
|
|
1311
1897
|
|
|
1312
1898
|
// src/commands/logout.ts
|
|
1313
|
-
var
|
|
1314
|
-
var
|
|
1315
|
-
var logoutCommand = new
|
|
1899
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
1900
|
+
var import_commander9 = require("commander");
|
|
1901
|
+
var logoutCommand = new import_commander9.Command("logout").description("Clear stored credentials").action(() => {
|
|
1316
1902
|
const token = config.getToken();
|
|
1317
1903
|
if (!token) {
|
|
1318
|
-
console.log(
|
|
1904
|
+
console.log(import_chalk10.default.yellow("You are not currently logged in."));
|
|
1319
1905
|
return;
|
|
1320
1906
|
}
|
|
1321
1907
|
config.clearToken();
|
|
1322
|
-
console.log(
|
|
1908
|
+
console.log(import_chalk10.default.green("\u2713 Logged out successfully."));
|
|
1323
1909
|
console.log(
|
|
1324
|
-
|
|
1910
|
+
import_chalk10.default.dim(` Credentials cleared from ${config.getConfigPath()}`)
|
|
1325
1911
|
);
|
|
1326
1912
|
});
|
|
1327
1913
|
|
|
1328
1914
|
// src/commands/logs.ts
|
|
1329
|
-
var
|
|
1330
|
-
var
|
|
1331
|
-
var logsCommand = new
|
|
1915
|
+
var import_chalk11 = __toESM(require("chalk"));
|
|
1916
|
+
var import_commander10 = require("commander");
|
|
1917
|
+
var logsCommand = new import_commander10.Command("logs").description("Stream build/runtime logs").option("--env <env>", "Environment (e.g. production, staging)").option("-f, --follow", "Follow log output (stream continuously)").action(async (opts) => {
|
|
1332
1918
|
const token = config.getToken();
|
|
1333
1919
|
if (!token) {
|
|
1334
|
-
console.log(
|
|
1920
|
+
console.log(import_chalk11.default.red("Not authenticated. Run `sylphx login` first."));
|
|
1335
1921
|
process.exit(1);
|
|
1336
1922
|
}
|
|
1337
1923
|
const linked = config.getLinkedApp();
|
|
1338
1924
|
if (!linked) {
|
|
1339
|
-
console.log(
|
|
1925
|
+
console.log(import_chalk11.default.red("No app linked. Run `sylphx link` first."));
|
|
1340
1926
|
process.exit(1);
|
|
1341
1927
|
}
|
|
1342
1928
|
const env = opts.env ?? linked.defaultEnv;
|
|
1343
1929
|
console.log("");
|
|
1344
1930
|
console.log(
|
|
1345
|
-
|
|
1346
|
-
` Logs for ${
|
|
1931
|
+
import_chalk11.default.bold(
|
|
1932
|
+
` Logs for ${import_chalk11.default.cyan(linked.appId)} [${import_chalk11.default.white(env)}]${opts.follow ? import_chalk11.default.dim(" (following...)") : ""}`
|
|
1347
1933
|
)
|
|
1348
1934
|
);
|
|
1349
|
-
console.log(
|
|
1935
|
+
console.log(import_chalk11.default.dim(` ${"\u2500".repeat(50)}`));
|
|
1350
1936
|
console.log("");
|
|
1351
1937
|
const logStream = createLogStream(linked.appId, env);
|
|
1352
1938
|
await streamLogs(logStream.url, logStream.token, {
|
|
@@ -1359,59 +1945,60 @@ var logsCommand = new import_commander9.Command("logs").description("Stream buil
|
|
|
1359
1945
|
});
|
|
1360
1946
|
|
|
1361
1947
|
// src/commands/open.ts
|
|
1362
|
-
var
|
|
1363
|
-
var
|
|
1364
|
-
var
|
|
1365
|
-
var openCommand = new
|
|
1948
|
+
var import_chalk12 = __toESM(require("chalk"));
|
|
1949
|
+
var import_commander11 = require("commander");
|
|
1950
|
+
var import_ora9 = __toESM(require("ora"));
|
|
1951
|
+
var openCommand = new import_commander11.Command("open").description("Open the app in your browser").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
|
|
1366
1952
|
const token = config.getToken();
|
|
1367
1953
|
if (!token) {
|
|
1368
|
-
console.log(
|
|
1954
|
+
console.log(import_chalk12.default.red("Not authenticated. Run `sylphx login` first."));
|
|
1369
1955
|
process.exit(1);
|
|
1370
1956
|
}
|
|
1371
1957
|
const linked = config.getLinkedApp();
|
|
1372
1958
|
if (!linked) {
|
|
1373
|
-
console.log(
|
|
1959
|
+
console.log(import_chalk12.default.red("No app linked. Run `sylphx link` first."));
|
|
1374
1960
|
process.exit(1);
|
|
1375
1961
|
}
|
|
1376
|
-
const spinner = (0,
|
|
1962
|
+
const spinner = (0, import_ora9.default)("Fetching app URL...").start();
|
|
1377
1963
|
let url;
|
|
1378
1964
|
try {
|
|
1379
1965
|
const status = await api.getDeploymentStatus(linked.appId);
|
|
1380
1966
|
spinner.stop();
|
|
1381
|
-
|
|
1967
|
+
const envType = opts.env ?? linked.defaultEnv ?? "production";
|
|
1968
|
+
const envMatch = status.environments?.find((e) => e.envType === envType) ?? status.environments?.find((e) => e.envType === "production") ?? status.environments?.[0];
|
|
1969
|
+
const resolvedUrl = status.url ?? envMatch?.url ?? null;
|
|
1970
|
+
if (!resolvedUrl) {
|
|
1382
1971
|
url = `https://sylphx.com/console/apps/${linked.appId}`;
|
|
1383
|
-
console.log(
|
|
1972
|
+
console.log(import_chalk12.default.yellow(" No URL found, opening console instead."));
|
|
1384
1973
|
} else {
|
|
1385
|
-
url =
|
|
1974
|
+
url = resolvedUrl;
|
|
1386
1975
|
}
|
|
1387
1976
|
} catch {
|
|
1388
1977
|
spinner.stop();
|
|
1389
1978
|
url = `https://sylphx.com/console/apps/${linked.appId}`;
|
|
1390
1979
|
}
|
|
1391
|
-
console.log(` Opening ${
|
|
1980
|
+
console.log(` Opening ${import_chalk12.default.cyan(url)}`);
|
|
1392
1981
|
try {
|
|
1393
1982
|
const { default: open } = await import("open");
|
|
1394
1983
|
await open(url);
|
|
1395
1984
|
} catch (err) {
|
|
1396
1985
|
console.log(
|
|
1397
|
-
|
|
1398
|
-
`Failed to open browser: ${err instanceof Error ? err.message : String(err)}`
|
|
1399
|
-
)
|
|
1986
|
+
import_chalk12.default.red(`Failed to open browser: ${err instanceof Error ? err.message : String(err)}`)
|
|
1400
1987
|
);
|
|
1401
|
-
console.log(
|
|
1988
|
+
console.log(import_chalk12.default.dim(` URL: ${url}`));
|
|
1402
1989
|
process.exit(1);
|
|
1403
1990
|
}
|
|
1404
1991
|
});
|
|
1405
1992
|
|
|
1406
1993
|
// src/commands/projects.ts
|
|
1407
1994
|
var import_node_readline4 = __toESM(require("readline"));
|
|
1408
|
-
var
|
|
1409
|
-
var
|
|
1410
|
-
var
|
|
1995
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
1996
|
+
var import_commander12 = require("commander");
|
|
1997
|
+
var import_ora10 = __toESM(require("ora"));
|
|
1411
1998
|
function requireAuth2() {
|
|
1412
1999
|
const token = config.getToken();
|
|
1413
2000
|
if (!token) {
|
|
1414
|
-
console.log(
|
|
2001
|
+
console.log(import_chalk13.default.red("Not authenticated. Run `sylphx login` first."));
|
|
1415
2002
|
process.exit(1);
|
|
1416
2003
|
}
|
|
1417
2004
|
return token;
|
|
@@ -1428,41 +2015,41 @@ function prompt4(question) {
|
|
|
1428
2015
|
});
|
|
1429
2016
|
});
|
|
1430
2017
|
}
|
|
1431
|
-
var projectsListCommand = new
|
|
2018
|
+
var projectsListCommand = new import_commander12.Command("list").description("List all projects").action(async () => {
|
|
1432
2019
|
requireAuth2();
|
|
1433
|
-
const spinner = (0,
|
|
2020
|
+
const spinner = (0, import_ora10.default)("Fetching projects...").start();
|
|
1434
2021
|
try {
|
|
1435
2022
|
const projects = await api.listProjects();
|
|
1436
2023
|
spinner.stop();
|
|
1437
2024
|
if (projects.length === 0) {
|
|
1438
|
-
console.log(
|
|
2025
|
+
console.log(import_chalk13.default.dim("\n No projects found.\n"));
|
|
1439
2026
|
return;
|
|
1440
2027
|
}
|
|
1441
2028
|
const colSlug = Math.max(4, ...projects.map((p) => p.slug.length));
|
|
1442
2029
|
const colName = Math.max(4, ...projects.map((p) => p.name.length));
|
|
1443
2030
|
console.log("");
|
|
1444
|
-
console.log(
|
|
1445
|
-
console.log(
|
|
2031
|
+
console.log(import_chalk13.default.bold(" Projects"));
|
|
2032
|
+
console.log(import_chalk13.default.dim(` ${"\u2500".repeat(colSlug + colName + 30)}`));
|
|
1446
2033
|
console.log(
|
|
1447
|
-
|
|
2034
|
+
import_chalk13.default.dim(` ${"SLUG".padEnd(colSlug)} ${"NAME".padEnd(colName)} ${"ENVIRONMENTS"}`)
|
|
1448
2035
|
);
|
|
1449
|
-
console.log(
|
|
2036
|
+
console.log(import_chalk13.default.dim(` ${"\u2500".repeat(colSlug + colName + 30)}`));
|
|
1450
2037
|
for (const p of projects) {
|
|
1451
|
-
const envNames = p.environments && p.environments.length > 0 ? p.environments.map((e) => e.name || e.envType || e.id).join(", ") :
|
|
2038
|
+
const envNames = p.environments && p.environments.length > 0 ? p.environments.map((e) => e.name || e.envType || e.id).join(", ") : import_chalk13.default.dim("\u2014");
|
|
1452
2039
|
console.log(
|
|
1453
|
-
` ${
|
|
2040
|
+
` ${import_chalk13.default.cyan(p.slug.padEnd(colSlug))} ${p.name.padEnd(colName)} ${envNames}`
|
|
1454
2041
|
);
|
|
1455
2042
|
}
|
|
1456
2043
|
console.log("");
|
|
1457
2044
|
} catch (err) {
|
|
1458
|
-
spinner.fail(
|
|
2045
|
+
spinner.fail(import_chalk13.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1459
2046
|
process.exit(1);
|
|
1460
2047
|
}
|
|
1461
2048
|
});
|
|
1462
|
-
var projectsCreateCommand = new
|
|
2049
|
+
var projectsCreateCommand = new import_commander12.Command("create").description("Create a new project (without linking the current directory)").argument("<name>", "Project name").option("--git <repo>", "Git repository (e.g. owner/repo)").option("--port <port>", "Application port").action(async (name, opts) => {
|
|
1463
2050
|
requireAuth2();
|
|
1464
2051
|
const slug = slugify2(name);
|
|
1465
|
-
const spinner = (0,
|
|
2052
|
+
const spinner = (0, import_ora10.default)(`Creating project ${import_chalk13.default.bold(name)}...`).start();
|
|
1466
2053
|
try {
|
|
1467
2054
|
const project = await api.createProject({
|
|
1468
2055
|
name,
|
|
@@ -1470,75 +2057,296 @@ var projectsCreateCommand = new import_commander11.Command("create").description
|
|
|
1470
2057
|
...opts.git ? { gitRepository: opts.git } : {},
|
|
1471
2058
|
...opts.port ? { port: Number.parseInt(opts.port, 10) } : {}
|
|
1472
2059
|
});
|
|
1473
|
-
spinner.succeed(
|
|
2060
|
+
spinner.succeed(import_chalk13.default.green(`\u2713 Created project ${import_chalk13.default.bold(project.name)}`));
|
|
1474
2061
|
console.log("");
|
|
1475
|
-
console.log(
|
|
1476
|
-
console.log(
|
|
2062
|
+
console.log(import_chalk13.default.dim(` ID : ${project.id}`));
|
|
2063
|
+
console.log(import_chalk13.default.dim(` Slug : ${project.slug}`));
|
|
1477
2064
|
console.log("");
|
|
1478
2065
|
console.log(
|
|
1479
|
-
|
|
2066
|
+
import_chalk13.default.dim(` Run ${import_chalk13.default.cyan("sylphx link")} to link this project to a directory.`)
|
|
1480
2067
|
);
|
|
1481
2068
|
console.log("");
|
|
1482
2069
|
} catch (err) {
|
|
1483
|
-
spinner.fail(
|
|
2070
|
+
spinner.fail(import_chalk13.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1484
2071
|
process.exit(1);
|
|
1485
2072
|
}
|
|
1486
2073
|
});
|
|
1487
|
-
var projectsDeleteCommand = new
|
|
2074
|
+
var projectsDeleteCommand = new import_commander12.Command("delete").description("Delete a project").argument("<id>", "Project ID or slug").option("--force", "Skip confirmation prompt").action(async (id, opts) => {
|
|
1488
2075
|
requireAuth2();
|
|
1489
2076
|
if (!opts.force) {
|
|
1490
2077
|
console.log("");
|
|
1491
2078
|
console.log(
|
|
1492
|
-
|
|
2079
|
+
import_chalk13.default.yellow(` \u26A0 You are about to permanently delete project ${import_chalk13.default.bold(id)}.`)
|
|
1493
2080
|
);
|
|
1494
|
-
console.log(
|
|
2081
|
+
console.log(import_chalk13.default.yellow(" This action cannot be undone."));
|
|
1495
2082
|
console.log("");
|
|
1496
2083
|
const answer = await prompt4(" Type the project ID/slug to confirm");
|
|
1497
2084
|
if (answer !== id) {
|
|
1498
|
-
console.log(
|
|
2085
|
+
console.log(import_chalk13.default.red("Confirmation did not match. Aborted."));
|
|
1499
2086
|
process.exit(1);
|
|
1500
2087
|
}
|
|
1501
2088
|
}
|
|
1502
|
-
const spinner = (0,
|
|
2089
|
+
const spinner = (0, import_ora10.default)(`Deleting project ${import_chalk13.default.bold(id)}...`).start();
|
|
1503
2090
|
try {
|
|
1504
2091
|
await api.deleteProject(id);
|
|
1505
|
-
spinner.succeed(
|
|
2092
|
+
spinner.succeed(import_chalk13.default.green(`\u2713 Deleted project ${import_chalk13.default.bold(id)}`));
|
|
1506
2093
|
} catch (err) {
|
|
1507
|
-
spinner.fail(
|
|
2094
|
+
spinner.fail(import_chalk13.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1508
2095
|
process.exit(1);
|
|
1509
2096
|
}
|
|
1510
2097
|
});
|
|
1511
|
-
var projectsCommand = new
|
|
2098
|
+
var projectsCommand = new import_commander12.Command("projects").description("Manage Sylphx projects").addCommand(projectsListCommand).addCommand(projectsCreateCommand).addCommand(projectsDeleteCommand);
|
|
1512
2099
|
|
|
1513
|
-
// src/commands/
|
|
1514
|
-
var
|
|
1515
|
-
var
|
|
1516
|
-
var
|
|
1517
|
-
var
|
|
2100
|
+
// src/commands/promote.ts
|
|
2101
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
2102
|
+
var import_commander13 = require("commander");
|
|
2103
|
+
var import_ora11 = __toESM(require("ora"));
|
|
2104
|
+
var promoteCommand = new import_commander13.Command("promote").description("Promote an environment (e.g. staging \u2192 production)").option("--from <env>", "Source environment (default: staging)", "staging").option("--to <env>", "Target environment (default: production)", "production").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
1518
2105
|
const token = config.getToken();
|
|
1519
2106
|
if (!token) {
|
|
1520
|
-
console.log(
|
|
2107
|
+
console.log(import_chalk14.default.red("Not authenticated. Run `sylphx login` first."));
|
|
1521
2108
|
process.exit(1);
|
|
1522
2109
|
}
|
|
1523
2110
|
const linked = config.getLinkedApp();
|
|
1524
2111
|
if (!linked) {
|
|
1525
|
-
console.log(
|
|
2112
|
+
console.log(import_chalk14.default.red("No app linked. Run `sylphx link` first."));
|
|
2113
|
+
process.exit(1);
|
|
2114
|
+
}
|
|
2115
|
+
if (opts.from === opts.to) {
|
|
2116
|
+
console.log(import_chalk14.default.red("Source and target environments must be different."));
|
|
1526
2117
|
process.exit(1);
|
|
1527
2118
|
}
|
|
1528
|
-
const env = opts.env ?? linked.defaultEnv;
|
|
1529
2119
|
if (!opts.force) {
|
|
1530
|
-
const
|
|
1531
|
-
const rl =
|
|
1532
|
-
|
|
1533
|
-
|
|
2120
|
+
const readline8 = await import("readline");
|
|
2121
|
+
const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
|
|
2122
|
+
await new Promise((resolve) => {
|
|
2123
|
+
rl.question(
|
|
2124
|
+
import_chalk14.default.yellow(
|
|
2125
|
+
` Promote ${import_chalk14.default.bold(linked.appId)}: ${import_chalk14.default.cyan(opts.from)} \u2192 ${import_chalk14.default.green(opts.to)}? (y/N) `
|
|
2126
|
+
),
|
|
2127
|
+
(answer) => {
|
|
2128
|
+
rl.close();
|
|
2129
|
+
if (answer.toLowerCase() !== "y") {
|
|
2130
|
+
console.log(import_chalk14.default.dim(" Cancelled."));
|
|
2131
|
+
process.exit(0);
|
|
2132
|
+
}
|
|
2133
|
+
resolve();
|
|
2134
|
+
}
|
|
2135
|
+
);
|
|
1534
2136
|
});
|
|
1535
|
-
|
|
2137
|
+
}
|
|
2138
|
+
const spinner = (0, import_ora11.default)(`Resolving environments...`).start();
|
|
2139
|
+
try {
|
|
2140
|
+
const envs = await api.listEnvironments(linked.appId);
|
|
2141
|
+
const sourceEnv = envs.find(
|
|
2142
|
+
(e) => (e.envType ?? e.name ?? "").toLowerCase() === opts.from.toLowerCase()
|
|
2143
|
+
);
|
|
2144
|
+
const targetEnv = envs.find(
|
|
2145
|
+
(e) => (e.envType ?? e.name ?? "").toLowerCase() === opts.to.toLowerCase()
|
|
2146
|
+
);
|
|
2147
|
+
if (!sourceEnv) {
|
|
2148
|
+
spinner.fail(import_chalk14.default.red(`Source environment '${opts.from}' not found.`));
|
|
2149
|
+
console.log(import_chalk14.default.dim(` Available: ${envs.map((e) => e.envType ?? e.name).join(", ")}`));
|
|
2150
|
+
process.exit(1);
|
|
2151
|
+
}
|
|
2152
|
+
if (!targetEnv) {
|
|
2153
|
+
spinner.fail(import_chalk14.default.red(`Target environment '${opts.to}' not found.`));
|
|
2154
|
+
console.log(import_chalk14.default.dim(` Available: ${envs.map((e) => e.envType ?? e.name).join(", ")}`));
|
|
2155
|
+
process.exit(1);
|
|
2156
|
+
}
|
|
2157
|
+
spinner.text = `Promoting ${opts.from} \u2192 ${opts.to}...`;
|
|
2158
|
+
const result = await api.promoteEnvironment(linked.appId, targetEnv.id, sourceEnv.id);
|
|
2159
|
+
if (result.promoted === 0) {
|
|
2160
|
+
spinner.warn(
|
|
2161
|
+
import_chalk14.default.yellow(
|
|
2162
|
+
`No services promoted from ${import_chalk14.default.bold(opts.from)} \u2014 nothing to push (source may have no deployed image yet)`
|
|
2163
|
+
)
|
|
2164
|
+
);
|
|
2165
|
+
} else {
|
|
2166
|
+
spinner.succeed(
|
|
2167
|
+
import_chalk14.default.green(
|
|
2168
|
+
`\u2713 Promoted ${import_chalk14.default.bold(opts.from)} \u2192 ${import_chalk14.default.bold(opts.to)} (${result.promoted} service${result.promoted !== 1 ? "s" : ""})`
|
|
2169
|
+
)
|
|
2170
|
+
);
|
|
2171
|
+
console.log(
|
|
2172
|
+
import_chalk14.default.dim(`
|
|
2173
|
+
Deployments queued. Run ${import_chalk14.default.cyan("sylphx status")} to monitor.`)
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
} catch (err) {
|
|
2177
|
+
spinner.fail(import_chalk14.default.red(`Promote failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
2178
|
+
process.exit(1);
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2181
|
+
|
|
2182
|
+
// src/commands/resources.ts
|
|
2183
|
+
var import_chalk15 = __toESM(require("chalk"));
|
|
2184
|
+
var import_commander14 = require("commander");
|
|
2185
|
+
var import_ora12 = __toESM(require("ora"));
|
|
2186
|
+
function requireLinkedApp4() {
|
|
2187
|
+
const linked = config.getLinkedApp();
|
|
2188
|
+
if (!linked) {
|
|
2189
|
+
console.log(import_chalk15.default.red("No app linked. Run `sylphx link` first."));
|
|
2190
|
+
process.exit(1);
|
|
2191
|
+
}
|
|
2192
|
+
return linked;
|
|
2193
|
+
}
|
|
2194
|
+
function requireToken2() {
|
|
2195
|
+
const token = config.getToken();
|
|
2196
|
+
if (!token) {
|
|
2197
|
+
console.log(import_chalk15.default.red("Not authenticated. Run `sylphx login` first."));
|
|
2198
|
+
process.exit(1);
|
|
2199
|
+
}
|
|
2200
|
+
return token;
|
|
2201
|
+
}
|
|
2202
|
+
var listCmd2 = new import_commander14.Command("list").description("List managed resources (databases, volumes, storage)").option("--kind <kind>", "Filter by kind: database, volume, storage").action(async (opts) => {
|
|
2203
|
+
requireToken2();
|
|
2204
|
+
const spinner = (0, import_ora12.default)("Fetching resources...").start();
|
|
2205
|
+
try {
|
|
2206
|
+
const resources = await api.listDatabases();
|
|
2207
|
+
spinner.stop();
|
|
2208
|
+
if (resources.length === 0) {
|
|
2209
|
+
console.log(import_chalk15.default.dim(` No resources found.`));
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
console.log();
|
|
2213
|
+
console.log(
|
|
2214
|
+
` ${import_chalk15.default.bold("NAME".padEnd(24))} ${"KIND".padEnd(12)} ${"STATUS".padEnd(14)} ID`
|
|
2215
|
+
);
|
|
2216
|
+
console.log(` ${"\u2500".repeat(72)}`);
|
|
2217
|
+
for (const r of resources) {
|
|
2218
|
+
const statusColor2 = r.status === "ready" ? import_chalk15.default.green((r.status ?? "unknown").padEnd(14)) : import_chalk15.default.yellow((r.status ?? "unknown").padEnd(14));
|
|
2219
|
+
console.log(
|
|
2220
|
+
` ${import_chalk15.default.cyan(r.name.padEnd(24))} ${"database".padEnd(12)} ${statusColor2} ${import_chalk15.default.dim(r.id)}`
|
|
2221
|
+
);
|
|
2222
|
+
}
|
|
2223
|
+
console.log();
|
|
2224
|
+
} catch (err) {
|
|
2225
|
+
spinner.fail(import_chalk15.default.red(err instanceof Error ? err.message : String(err)));
|
|
2226
|
+
process.exit(1);
|
|
2227
|
+
}
|
|
2228
|
+
});
|
|
2229
|
+
var getCmd2 = new import_commander14.Command("get").description("Get details of a resource").argument("<id>", "Resource ID").action(async (id) => {
|
|
2230
|
+
requireToken2();
|
|
2231
|
+
const spinner = (0, import_ora12.default)(`Fetching resource ${id}...`).start();
|
|
2232
|
+
try {
|
|
2233
|
+
const r = await api.getDatabase(id);
|
|
2234
|
+
spinner.stop();
|
|
2235
|
+
console.log();
|
|
2236
|
+
console.log(` ${import_chalk15.default.bold("Name:")} ${import_chalk15.default.cyan(r.name)}`);
|
|
2237
|
+
console.log(` ${import_chalk15.default.bold("ID:")} ${r.id}`);
|
|
2238
|
+
console.log(
|
|
2239
|
+
` ${import_chalk15.default.bold("Status:")} ${r.status === "ready" ? import_chalk15.default.green(r.status) : import_chalk15.default.yellow(r.status ?? "unknown")}`
|
|
2240
|
+
);
|
|
2241
|
+
if (r.tier) console.log(` ${import_chalk15.default.bold("Tier:")} ${r.tier}`);
|
|
2242
|
+
if (r.host) console.log(` ${import_chalk15.default.bold("Host:")} ${r.host}${r.port ? `:${r.port}` : ""}`);
|
|
2243
|
+
if (r.connectionString) {
|
|
2244
|
+
console.log(` ${import_chalk15.default.bold("DSN:")} ${import_chalk15.default.dim(r.connectionString)}`);
|
|
2245
|
+
}
|
|
2246
|
+
if (r.env) console.log(` ${import_chalk15.default.bold("Env:")} ${r.env}`);
|
|
2247
|
+
console.log();
|
|
2248
|
+
} catch (err) {
|
|
2249
|
+
spinner.fail(import_chalk15.default.red(err instanceof Error ? err.message : String(err)));
|
|
2250
|
+
process.exit(1);
|
|
2251
|
+
}
|
|
2252
|
+
});
|
|
2253
|
+
var bindingsCmd = new import_commander14.Command("bindings").description("List project bindings for a resource").argument("<id>", "Resource ID").action(async (id) => {
|
|
2254
|
+
requireToken2();
|
|
2255
|
+
const spinner = (0, import_ora12.default)(`Fetching bindings for ${id}...`).start();
|
|
2256
|
+
try {
|
|
2257
|
+
const bindings = await api.listResourceBindings(id);
|
|
2258
|
+
spinner.stop();
|
|
2259
|
+
if (bindings.length === 0) {
|
|
2260
|
+
console.log(import_chalk15.default.dim(" No bindings. Use `sylphx resources bind <id>` to link."));
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
console.log();
|
|
2264
|
+
console.log(
|
|
2265
|
+
` ${import_chalk15.default.bold("BINDING ID".padEnd(26))} ${"PROJECT".padEnd(24)} ${"ENV".padEnd(12)} ROLE`
|
|
2266
|
+
);
|
|
2267
|
+
console.log(` ${"\u2500".repeat(80)}`);
|
|
2268
|
+
for (const b of bindings) {
|
|
2269
|
+
const project = b.projectName ?? b.projectSlug ?? "\u2014";
|
|
2270
|
+
const env = b.envName ?? "\u2014";
|
|
2271
|
+
console.log(
|
|
2272
|
+
` ${import_chalk15.default.dim(b.id.padEnd(26))} ${project.padEnd(24)} ${env.padEnd(12)} ${b.role}`
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
console.log();
|
|
2276
|
+
} catch (err) {
|
|
2277
|
+
spinner.fail(import_chalk15.default.red(err instanceof Error ? err.message : String(err)));
|
|
2278
|
+
process.exit(1);
|
|
2279
|
+
}
|
|
2280
|
+
});
|
|
2281
|
+
var bindCmd = new import_commander14.Command("bind").description("Bind a resource to this project environment").argument("<id>", "Resource ID (e.g. res_xxx)").option("--env <env>", "Environment (default: production)", "production").option(
|
|
2282
|
+
"--role <role>",
|
|
2283
|
+
"Binding role: primary, replica, analytics, backup (default: primary)",
|
|
2284
|
+
"primary"
|
|
2285
|
+
).action(async (id, opts) => {
|
|
2286
|
+
requireToken2();
|
|
2287
|
+
const linked = requireLinkedApp4();
|
|
2288
|
+
const spinner = (0, import_ora12.default)(`Binding resource ${id} to ${linked.appId} [${opts.env}]...`).start();
|
|
2289
|
+
try {
|
|
2290
|
+
const binding = await api.bindResource(id, {
|
|
2291
|
+
projectId: linked.appId,
|
|
2292
|
+
envType: opts.env,
|
|
2293
|
+
role: opts.role
|
|
2294
|
+
});
|
|
2295
|
+
spinner.succeed(import_chalk15.default.green(`\u2713 Resource bound (${binding.role})`));
|
|
2296
|
+
console.log();
|
|
2297
|
+
console.log(` ${import_chalk15.default.dim("Binding ID:")} ${binding.id}`);
|
|
2298
|
+
console.log(` ${import_chalk15.default.dim("Env:")} ${binding.envName ?? opts.env}`);
|
|
2299
|
+
console.log(
|
|
2300
|
+
import_chalk15.default.dim(
|
|
2301
|
+
`
|
|
2302
|
+
Run ${import_chalk15.default.cyan("sylphx deploy")} to apply the connection vars to your deployment.`
|
|
2303
|
+
)
|
|
2304
|
+
);
|
|
2305
|
+
} catch (err) {
|
|
2306
|
+
spinner.fail(import_chalk15.default.red(err instanceof Error ? err.message : String(err)));
|
|
2307
|
+
process.exit(1);
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
var unbindCmd = new import_commander14.Command("unbind").description("Remove a resource binding").argument("<id>", "Resource ID").argument("<binding-id>", "Binding ID (from `sylphx resources bindings <id>`)").action(async (id, bindingId) => {
|
|
2311
|
+
requireToken2();
|
|
2312
|
+
const spinner = (0, import_ora12.default)(`Removing binding ${bindingId}...`).start();
|
|
2313
|
+
try {
|
|
2314
|
+
await api.unbindResource(id, bindingId);
|
|
2315
|
+
spinner.succeed(import_chalk15.default.green(`\u2713 Binding removed`));
|
|
2316
|
+
} catch (err) {
|
|
2317
|
+
spinner.fail(import_chalk15.default.red(err instanceof Error ? err.message : String(err)));
|
|
2318
|
+
process.exit(1);
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
var resourcesCommand = new import_commander14.Command("resources").description("Manage resources and project bindings").addCommand(listCmd2).addCommand(getCmd2).addCommand(bindingsCmd).addCommand(bindCmd).addCommand(unbindCmd).action(() => resourcesCommand.help());
|
|
2322
|
+
|
|
2323
|
+
// src/commands/rollback.ts
|
|
2324
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
2325
|
+
var import_commander15 = require("commander");
|
|
2326
|
+
var import_ora13 = __toESM(require("ora"));
|
|
2327
|
+
var rollbackCommand = new import_commander15.Command("rollback").description("Rollback to the previous deployment").option("--env <env>", "Environment to rollback (e.g. production, staging)").option("--deployment <id>", "Rollback to a specific deployment ID").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
2328
|
+
const token = config.getToken();
|
|
2329
|
+
if (!token) {
|
|
2330
|
+
console.log(import_chalk16.default.red("Not authenticated. Run `sylphx login` first."));
|
|
2331
|
+
process.exit(1);
|
|
2332
|
+
}
|
|
2333
|
+
const linked = config.getLinkedApp();
|
|
2334
|
+
if (!linked) {
|
|
2335
|
+
console.log(import_chalk16.default.red("No app linked. Run `sylphx link` first."));
|
|
2336
|
+
process.exit(1);
|
|
2337
|
+
}
|
|
2338
|
+
const envType = opts.env ?? linked.defaultEnv ?? "production";
|
|
2339
|
+
if (!opts.force) {
|
|
2340
|
+
const readline8 = await import("readline");
|
|
2341
|
+
const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
|
|
2342
|
+
await new Promise((resolve) => {
|
|
2343
|
+
const label = opts.deployment ? `deployment ${import_chalk16.default.bold(opts.deployment)}` : `previous deployment`;
|
|
1536
2344
|
rl.question(
|
|
1537
|
-
|
|
2345
|
+
import_chalk16.default.yellow(` Rollback ${import_chalk16.default.bold(linked.appId)} [${envType}] to ${label}? (y/N) `),
|
|
1538
2346
|
(answer) => {
|
|
1539
2347
|
rl.close();
|
|
1540
2348
|
if (answer.toLowerCase() !== "y") {
|
|
1541
|
-
console.log(
|
|
2349
|
+
console.log(import_chalk16.default.dim(" Cancelled."));
|
|
1542
2350
|
process.exit(0);
|
|
1543
2351
|
}
|
|
1544
2352
|
resolve();
|
|
@@ -1546,145 +2354,523 @@ var rollbackCommand = new import_commander12.Command("rollback").description("Ro
|
|
|
1546
2354
|
);
|
|
1547
2355
|
});
|
|
1548
2356
|
}
|
|
1549
|
-
const spinner = (0,
|
|
2357
|
+
const spinner = (0, import_ora13.default)(`Rolling back ${linked.appId} [${envType}]...`).start();
|
|
1550
2358
|
try {
|
|
1551
|
-
|
|
1552
|
-
|
|
2359
|
+
let rollbackBody;
|
|
2360
|
+
if (opts.deployment) {
|
|
2361
|
+
rollbackBody = { deploymentRecordId: opts.deployment };
|
|
2362
|
+
} else {
|
|
2363
|
+
const status = await api.getDeploymentStatus(linked.appId);
|
|
2364
|
+
const envEntry = status.environments?.find(
|
|
2365
|
+
(e) => (e.envType ?? "").toLowerCase() === envType.toLowerCase()
|
|
2366
|
+
);
|
|
2367
|
+
if (!envEntry?.envId) {
|
|
2368
|
+
spinner.fail(
|
|
2369
|
+
import_chalk16.default.red(
|
|
2370
|
+
`Could not find environment '${envType}'. Run \`sylphx status\` to see available environments.`
|
|
2371
|
+
)
|
|
2372
|
+
);
|
|
2373
|
+
process.exit(1);
|
|
2374
|
+
}
|
|
2375
|
+
rollbackBody = { envId: envEntry.envId };
|
|
2376
|
+
}
|
|
2377
|
+
const result = await api.rollback(linked.appId, rollbackBody);
|
|
2378
|
+
spinner.succeed(import_chalk16.default.green(`\u2713 Rollback initiated`));
|
|
1553
2379
|
if (result.deploymentId) {
|
|
1554
|
-
console.log(
|
|
2380
|
+
console.log(import_chalk16.default.dim(` Deployment ID: ${result.deploymentId}`));
|
|
2381
|
+
}
|
|
2382
|
+
if (result.status) {
|
|
2383
|
+
console.log(import_chalk16.default.dim(` Status: ${result.status}`));
|
|
1555
2384
|
}
|
|
1556
2385
|
if (result.message) {
|
|
1557
|
-
console.log(
|
|
2386
|
+
console.log(import_chalk16.default.dim(` ${result.message}`));
|
|
1558
2387
|
}
|
|
2388
|
+
console.log(import_chalk16.default.dim(`
|
|
2389
|
+
Run ${import_chalk16.default.cyan("sylphx status")} to monitor progress.`));
|
|
1559
2390
|
} catch (err) {
|
|
1560
2391
|
spinner.fail(
|
|
1561
|
-
|
|
2392
|
+
import_chalk16.default.red(`Rollback failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
1562
2393
|
);
|
|
1563
2394
|
process.exit(1);
|
|
1564
2395
|
}
|
|
1565
2396
|
});
|
|
1566
2397
|
|
|
2398
|
+
// src/commands/services.ts
|
|
2399
|
+
var import_node_readline5 = __toESM(require("readline"));
|
|
2400
|
+
var import_chalk17 = __toESM(require("chalk"));
|
|
2401
|
+
var import_commander16 = require("commander");
|
|
2402
|
+
var import_ora14 = __toESM(require("ora"));
|
|
2403
|
+
function requireLinkedApp5() {
|
|
2404
|
+
const linked = config.getLinkedApp();
|
|
2405
|
+
if (!linked) {
|
|
2406
|
+
console.log(import_chalk17.default.red("No app linked. Run `sylphx link` first."));
|
|
2407
|
+
process.exit(1);
|
|
2408
|
+
}
|
|
2409
|
+
return linked;
|
|
2410
|
+
}
|
|
2411
|
+
function requireToken3() {
|
|
2412
|
+
const token = config.getToken();
|
|
2413
|
+
if (!token) {
|
|
2414
|
+
console.log(import_chalk17.default.red("Not authenticated. Run `sylphx login` first."));
|
|
2415
|
+
process.exit(1);
|
|
2416
|
+
}
|
|
2417
|
+
return token;
|
|
2418
|
+
}
|
|
2419
|
+
var listCmd3 = new import_commander16.Command("list").description("List all services in this project").action(async () => {
|
|
2420
|
+
requireToken3();
|
|
2421
|
+
const linked = requireLinkedApp5();
|
|
2422
|
+
const spinner = (0, import_ora14.default)("Fetching services...").start();
|
|
2423
|
+
try {
|
|
2424
|
+
const services = await api.listServices(linked.appId);
|
|
2425
|
+
spinner.stop();
|
|
2426
|
+
if (services.length === 0) {
|
|
2427
|
+
console.log(import_chalk17.default.dim(" No services defined."));
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
console.log();
|
|
2431
|
+
console.log(` ${import_chalk17.default.bold("NAME".padEnd(24))} ${"REPO".padEnd(36)} ENVS`);
|
|
2432
|
+
console.log(` ${"\u2500".repeat(70)}`);
|
|
2433
|
+
for (const svc of services) {
|
|
2434
|
+
const repo = svc.githubRepo ?? import_chalk17.default.dim("\u2014");
|
|
2435
|
+
const envs = svc.environmentCount ?? 0;
|
|
2436
|
+
console.log(` ${import_chalk17.default.cyan(svc.name.padEnd(24))} ${repo.padEnd(36)} ${envs}`);
|
|
2437
|
+
}
|
|
2438
|
+
console.log();
|
|
2439
|
+
} catch (err) {
|
|
2440
|
+
spinner.fail(import_chalk17.default.red(err instanceof Error ? err.message : String(err)));
|
|
2441
|
+
process.exit(1);
|
|
2442
|
+
}
|
|
2443
|
+
});
|
|
2444
|
+
var getCmd3 = new import_commander16.Command("get").description("Show details of a service").argument("<name>", "Service name").action(async (name) => {
|
|
2445
|
+
requireToken3();
|
|
2446
|
+
const linked = requireLinkedApp5();
|
|
2447
|
+
const spinner = (0, import_ora14.default)(`Fetching service '${name}'...`).start();
|
|
2448
|
+
try {
|
|
2449
|
+
const svc = await api.getService(linked.appId, name);
|
|
2450
|
+
spinner.stop();
|
|
2451
|
+
console.log();
|
|
2452
|
+
console.log(` ${import_chalk17.default.bold("Name:")} ${import_chalk17.default.cyan(svc.name)}`);
|
|
2453
|
+
console.log(` ${import_chalk17.default.bold("ID:")} ${svc.id}`);
|
|
2454
|
+
if (svc.githubRepo) console.log(` ${import_chalk17.default.bold("Repo:")} ${svc.githubRepo}`);
|
|
2455
|
+
if (svc.githubBranch) console.log(` ${import_chalk17.default.bold("Branch:")} ${svc.githubBranch}`);
|
|
2456
|
+
if (svc.dockerfilePath)
|
|
2457
|
+
console.log(` ${import_chalk17.default.bold("Dockerfile:")} ${svc.dockerfilePath}`);
|
|
2458
|
+
if (svc.port) console.log(` ${import_chalk17.default.bold("Port:")} ${svc.port}`);
|
|
2459
|
+
if (svc.publicNetworking !== void 0 && svc.publicNetworking !== null)
|
|
2460
|
+
console.log(
|
|
2461
|
+
` ${import_chalk17.default.bold("Public:")} ${svc.publicNetworking ? import_chalk17.default.green("yes") : import_chalk17.default.dim("no")}`
|
|
2462
|
+
);
|
|
2463
|
+
console.log();
|
|
2464
|
+
} catch (err) {
|
|
2465
|
+
spinner.fail(import_chalk17.default.red(err instanceof Error ? err.message : String(err)));
|
|
2466
|
+
process.exit(1);
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
var deployCmd = new import_commander16.Command("deploy").description("Deploy a specific service").argument("<name>", "Service name").option("--env <env>", "Environment (default: production)", "production").action(async (name, opts) => {
|
|
2470
|
+
requireToken3();
|
|
2471
|
+
const linked = requireLinkedApp5();
|
|
2472
|
+
const spinner = (0, import_ora14.default)(`Deploying service '${name}' [${opts.env}]...`).start();
|
|
2473
|
+
try {
|
|
2474
|
+
const result = await api.deployService(linked.appId, name, opts.env);
|
|
2475
|
+
spinner.succeed(import_chalk17.default.green(`\u2713 Deploy triggered for service '${name}'`));
|
|
2476
|
+
if (result.deploymentId) {
|
|
2477
|
+
console.log(import_chalk17.default.dim(` Deployment ID: ${result.deploymentId}`));
|
|
2478
|
+
}
|
|
2479
|
+
console.log(import_chalk17.default.dim(`
|
|
2480
|
+
Run ${import_chalk17.default.cyan("sylphx status")} to monitor.`));
|
|
2481
|
+
} catch (err) {
|
|
2482
|
+
spinner.fail(import_chalk17.default.red(err instanceof Error ? err.message : String(err)));
|
|
2483
|
+
process.exit(1);
|
|
2484
|
+
}
|
|
2485
|
+
});
|
|
2486
|
+
var rmCmd2 = new import_commander16.Command("rm").description("Delete a service").argument("<name>", "Service name").option("--force", "Skip confirmation prompt").action(async (name, opts) => {
|
|
2487
|
+
requireToken3();
|
|
2488
|
+
const linked = requireLinkedApp5();
|
|
2489
|
+
if (!opts.force) {
|
|
2490
|
+
const rl = import_node_readline5.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
2491
|
+
await new Promise((resolve) => {
|
|
2492
|
+
rl.question(import_chalk17.default.yellow(` Delete service ${import_chalk17.default.bold(name)}? (y/N) `), (answer) => {
|
|
2493
|
+
rl.close();
|
|
2494
|
+
if (answer.toLowerCase() !== "y") {
|
|
2495
|
+
console.log(import_chalk17.default.dim(" Cancelled."));
|
|
2496
|
+
process.exit(0);
|
|
2497
|
+
}
|
|
2498
|
+
resolve();
|
|
2499
|
+
});
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
const spinner = (0, import_ora14.default)(`Deleting service '${name}'...`).start();
|
|
2503
|
+
try {
|
|
2504
|
+
await api.deleteService(linked.appId, name);
|
|
2505
|
+
spinner.succeed(import_chalk17.default.green(`\u2713 Service '${name}' deleted`));
|
|
2506
|
+
} catch (err) {
|
|
2507
|
+
spinner.fail(import_chalk17.default.red(err instanceof Error ? err.message : String(err)));
|
|
2508
|
+
process.exit(1);
|
|
2509
|
+
}
|
|
2510
|
+
});
|
|
2511
|
+
var servicesCommand = new import_commander16.Command("services").description("Manage project services").addCommand(listCmd3).addCommand(getCmd3).addCommand(deployCmd).addCommand(rmCmd2).action(() => servicesCommand.help());
|
|
2512
|
+
|
|
1567
2513
|
// src/commands/status.ts
|
|
1568
|
-
var
|
|
1569
|
-
var
|
|
1570
|
-
var
|
|
2514
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
2515
|
+
var import_commander17 = require("commander");
|
|
2516
|
+
var import_ora15 = __toESM(require("ora"));
|
|
1571
2517
|
function statusColor(status) {
|
|
1572
2518
|
const s = status.toLowerCase();
|
|
1573
2519
|
if (s === "running" || s === "active" || s === "healthy" || s === "success") {
|
|
1574
|
-
return
|
|
2520
|
+
return import_chalk18.default.green(status);
|
|
1575
2521
|
}
|
|
1576
2522
|
if (s === "deploying" || s === "building" || s === "pending") {
|
|
1577
|
-
return
|
|
2523
|
+
return import_chalk18.default.yellow(status);
|
|
1578
2524
|
}
|
|
1579
2525
|
if (s === "error" || s === "failed" || s === "stopped") {
|
|
1580
|
-
return
|
|
2526
|
+
return import_chalk18.default.red(status);
|
|
1581
2527
|
}
|
|
1582
|
-
return
|
|
2528
|
+
return import_chalk18.default.white(status);
|
|
1583
2529
|
}
|
|
1584
|
-
var statusCommand = new
|
|
2530
|
+
var statusCommand = new import_commander17.Command("status").description("Show deployment status").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
|
|
1585
2531
|
const token = config.getToken();
|
|
1586
2532
|
if (!token) {
|
|
1587
|
-
console.log(
|
|
2533
|
+
console.log(import_chalk18.default.red("Not authenticated. Run `sylphx login` first."));
|
|
1588
2534
|
process.exit(1);
|
|
1589
2535
|
}
|
|
1590
2536
|
const linked = config.getLinkedApp();
|
|
1591
2537
|
if (!linked) {
|
|
1592
|
-
console.log(
|
|
2538
|
+
console.log(import_chalk18.default.red("No app linked. Run `sylphx link` first."));
|
|
1593
2539
|
process.exit(1);
|
|
1594
2540
|
}
|
|
1595
2541
|
const env = opts.env ?? linked.defaultEnv;
|
|
1596
|
-
const spinner = (0,
|
|
2542
|
+
const spinner = (0, import_ora15.default)(`Fetching status [${env}]...`).start();
|
|
1597
2543
|
try {
|
|
1598
2544
|
const status = await api.getDeploymentStatus(linked.appId);
|
|
1599
2545
|
spinner.stop();
|
|
1600
2546
|
console.log("");
|
|
1601
|
-
console.log(
|
|
1602
|
-
console.log(
|
|
2547
|
+
console.log(import_chalk18.default.bold(" Deployment Status"));
|
|
2548
|
+
console.log(import_chalk18.default.dim(` ${"\u2500".repeat(40)}`));
|
|
1603
2549
|
console.log("");
|
|
1604
|
-
console.log(` App: ${
|
|
1605
|
-
console.log(` Environment: ${
|
|
2550
|
+
console.log(` App: ${import_chalk18.default.cyan(linked.appId)}`);
|
|
2551
|
+
console.log(` Environment: ${import_chalk18.default.white(env)}`);
|
|
1606
2552
|
console.log(` Status: ${statusColor(status.status)}`);
|
|
1607
2553
|
if (status.deployedAt) {
|
|
1608
2554
|
const date = new Date(status.deployedAt);
|
|
1609
|
-
console.log(` Deployed: ${
|
|
2555
|
+
console.log(` Deployed: ${import_chalk18.default.white(date.toLocaleString())}`);
|
|
1610
2556
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
2557
|
+
const envMatch = status.environments?.find((e) => e.envType === env) ?? status.environments?.find((e) => e.envType === "production") ?? status.environments?.[0];
|
|
2558
|
+
const displayUrl = status.url ?? envMatch?.url ?? null;
|
|
2559
|
+
if (displayUrl) {
|
|
2560
|
+
console.log(` URL: ${import_chalk18.default.cyan(displayUrl)}`);
|
|
2561
|
+
}
|
|
2562
|
+
if (envMatch?.lastDeployedAt) {
|
|
2563
|
+
console.log(
|
|
2564
|
+
` Deployed: ${import_chalk18.default.white(new Date(envMatch.lastDeployedAt).toLocaleString())}`
|
|
2565
|
+
);
|
|
1613
2566
|
}
|
|
1614
2567
|
if (status.replicas) {
|
|
1615
2568
|
const { ready, desired } = status.replicas;
|
|
1616
|
-
const replicaColor = ready === desired ?
|
|
2569
|
+
const replicaColor = ready === desired ? import_chalk18.default.green : import_chalk18.default.yellow;
|
|
1617
2570
|
console.log(` Replicas: ${replicaColor(`${ready}/${desired}`)}`);
|
|
1618
2571
|
}
|
|
1619
2572
|
console.log("");
|
|
1620
2573
|
} catch (err) {
|
|
1621
|
-
spinner.fail(
|
|
2574
|
+
spinner.fail(import_chalk18.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
2575
|
+
process.exit(1);
|
|
2576
|
+
}
|
|
2577
|
+
});
|
|
2578
|
+
|
|
2579
|
+
// src/commands/storage.ts
|
|
2580
|
+
var import_node_readline6 = __toESM(require("readline"));
|
|
2581
|
+
var import_chalk19 = __toESM(require("chalk"));
|
|
2582
|
+
var import_commander18 = require("commander");
|
|
2583
|
+
var import_ora16 = __toESM(require("ora"));
|
|
2584
|
+
function requireLinkedApp6() {
|
|
2585
|
+
const linked = config.getLinkedApp();
|
|
2586
|
+
if (!linked) {
|
|
2587
|
+
console.log(import_chalk19.default.red("No app linked. Run `sylphx link` first."));
|
|
2588
|
+
process.exit(1);
|
|
2589
|
+
}
|
|
2590
|
+
return linked;
|
|
2591
|
+
}
|
|
2592
|
+
function requireToken4() {
|
|
2593
|
+
const token = config.getToken();
|
|
2594
|
+
if (!token) {
|
|
2595
|
+
console.log(import_chalk19.default.red("Not authenticated. Run `sylphx login` first."));
|
|
2596
|
+
process.exit(1);
|
|
2597
|
+
}
|
|
2598
|
+
return token;
|
|
2599
|
+
}
|
|
2600
|
+
var listCmd4 = new import_commander18.Command("list").description("List storage resources for this project").option("--env <env>", "Environment (default: production)", "production").action(async (opts) => {
|
|
2601
|
+
requireToken4();
|
|
2602
|
+
const linked = requireLinkedApp6();
|
|
2603
|
+
const spinner = (0, import_ora16.default)("Fetching storage resources...").start();
|
|
2604
|
+
try {
|
|
2605
|
+
const resources = await api.listStorage(linked.appId, opts.env);
|
|
2606
|
+
spinner.stop();
|
|
2607
|
+
if (resources.length === 0) {
|
|
2608
|
+
console.log(import_chalk19.default.dim(" No storage resources provisioned."));
|
|
2609
|
+
console.log(
|
|
2610
|
+
import_chalk19.default.dim(`
|
|
2611
|
+
Run ${import_chalk19.default.cyan("sylphx storage create")} to provision storage.`)
|
|
2612
|
+
);
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
console.log();
|
|
2616
|
+
for (const r of resources) {
|
|
2617
|
+
const label = [r.role ?? "primary", r.envType].filter(Boolean).join(" \xB7 ");
|
|
2618
|
+
console.log(` ${import_chalk19.default.cyan(label)} ${import_chalk19.default.dim(r.id)}`);
|
|
2619
|
+
if (r.publicUrl) {
|
|
2620
|
+
console.log(` ${import_chalk19.default.dim("URL:")} ${import_chalk19.default.cyan(r.publicUrl)}`);
|
|
2621
|
+
}
|
|
2622
|
+
if (r.bucket) {
|
|
2623
|
+
console.log(` ${import_chalk19.default.dim("Bucket:")} ${r.bucket}`);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
console.log();
|
|
2627
|
+
} catch (err) {
|
|
2628
|
+
spinner.fail(import_chalk19.default.red(err instanceof Error ? err.message : String(err)));
|
|
1622
2629
|
process.exit(1);
|
|
1623
2630
|
}
|
|
1624
2631
|
});
|
|
2632
|
+
var createCmd = new import_commander18.Command("create").description("Provision blob storage for this project").argument("[name]", "Storage resource name (default: primary)", "primary").option("--env <env>", "Environment (default: production)", "production").action(async (name, opts) => {
|
|
2633
|
+
requireToken4();
|
|
2634
|
+
const linked = requireLinkedApp6();
|
|
2635
|
+
const spinner = (0, import_ora16.default)(`Provisioning storage '${name}' [${opts.env}]...`).start();
|
|
2636
|
+
try {
|
|
2637
|
+
const resource = await api.createStorage(linked.appId, { name, envType: opts.env });
|
|
2638
|
+
spinner.succeed(import_chalk19.default.green(`\u2713 Storage '${name}' provisioned`));
|
|
2639
|
+
console.log();
|
|
2640
|
+
console.log(` ${import_chalk19.default.dim("ID:")} ${resource.id}`);
|
|
2641
|
+
if (resource.publicUrl) {
|
|
2642
|
+
console.log(` ${import_chalk19.default.dim("Public URL:")} ${import_chalk19.default.cyan(resource.publicUrl)}`);
|
|
2643
|
+
}
|
|
2644
|
+
if (resource.envVars && Object.keys(resource.envVars).length > 0) {
|
|
2645
|
+
console.log();
|
|
2646
|
+
console.log(import_chalk19.default.bold(" Env vars to add to your project:"));
|
|
2647
|
+
for (const [k, v] of Object.entries(resource.envVars)) {
|
|
2648
|
+
console.log(` ${import_chalk19.default.cyan(k)}=${v}`);
|
|
2649
|
+
}
|
|
2650
|
+
console.log();
|
|
2651
|
+
console.log(
|
|
2652
|
+
import_chalk19.default.dim(
|
|
2653
|
+
` Run ${import_chalk19.default.cyan("sylphx env set KEY=VALUE")} to add these to your project.`
|
|
2654
|
+
)
|
|
2655
|
+
);
|
|
2656
|
+
}
|
|
2657
|
+
} catch (err) {
|
|
2658
|
+
spinner.fail(import_chalk19.default.red(err instanceof Error ? err.message : String(err)));
|
|
2659
|
+
process.exit(1);
|
|
2660
|
+
}
|
|
2661
|
+
});
|
|
2662
|
+
var rmCmd3 = new import_commander18.Command("rm").description("Delete a storage resource").argument("<id>", "Storage resource ID").option("--force", "Skip confirmation prompt").action(async (id, opts) => {
|
|
2663
|
+
requireToken4();
|
|
2664
|
+
const linked = requireLinkedApp6();
|
|
2665
|
+
if (!opts.force) {
|
|
2666
|
+
const rl = import_node_readline6.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
2667
|
+
await new Promise((resolve) => {
|
|
2668
|
+
rl.question(
|
|
2669
|
+
import_chalk19.default.yellow(` \u26A0\uFE0F Delete storage ${import_chalk19.default.bold(id)}? This is irreversible. (y/N) `),
|
|
2670
|
+
(answer) => {
|
|
2671
|
+
rl.close();
|
|
2672
|
+
if (answer.toLowerCase() !== "y") {
|
|
2673
|
+
console.log(import_chalk19.default.dim(" Cancelled."));
|
|
2674
|
+
process.exit(0);
|
|
2675
|
+
}
|
|
2676
|
+
resolve();
|
|
2677
|
+
}
|
|
2678
|
+
);
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
const spinner = (0, import_ora16.default)(`Deleting storage ${id}...`).start();
|
|
2682
|
+
try {
|
|
2683
|
+
await api.deleteStorage(linked.appId, id);
|
|
2684
|
+
spinner.succeed(import_chalk19.default.green(`\u2713 Storage ${id} deleted`));
|
|
2685
|
+
} catch (err) {
|
|
2686
|
+
spinner.fail(import_chalk19.default.red(err instanceof Error ? err.message : String(err)));
|
|
2687
|
+
process.exit(1);
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
var storageCommand = new import_commander18.Command("storage").description("Manage blob storage resources").addCommand(listCmd4).addCommand(createCmd).addCommand(rmCmd3).action(() => storageCommand.help());
|
|
1625
2691
|
|
|
1626
2692
|
// src/commands/unlink.ts
|
|
1627
|
-
var
|
|
1628
|
-
var
|
|
1629
|
-
var unlinkCommand = new
|
|
2693
|
+
var import_chalk20 = __toESM(require("chalk"));
|
|
2694
|
+
var import_commander19 = require("commander");
|
|
2695
|
+
var unlinkCommand = new import_commander19.Command("unlink").description("Unlink current directory from its linked app").action(() => {
|
|
1630
2696
|
const linked = config.getLinkedApp();
|
|
1631
2697
|
if (!linked) {
|
|
1632
|
-
console.log(
|
|
2698
|
+
console.log(import_chalk20.default.yellow(" This directory is not linked to any app."));
|
|
1633
2699
|
process.exit(0);
|
|
1634
2700
|
}
|
|
1635
2701
|
const appId = linked.appId;
|
|
1636
2702
|
config.unlinkApp();
|
|
1637
2703
|
console.log("");
|
|
1638
|
-
console.log(
|
|
1639
|
-
console.log(
|
|
2704
|
+
console.log(import_chalk20.default.green(`\u2713 Unlinked from ${import_chalk20.default.bold(appId)}`));
|
|
2705
|
+
console.log(import_chalk20.default.dim(` Directory: ${process.cwd()}`));
|
|
1640
2706
|
console.log("");
|
|
1641
2707
|
});
|
|
1642
2708
|
|
|
2709
|
+
// src/commands/volumes.ts
|
|
2710
|
+
var import_node_readline7 = __toESM(require("readline"));
|
|
2711
|
+
var import_chalk21 = __toESM(require("chalk"));
|
|
2712
|
+
var import_commander20 = require("commander");
|
|
2713
|
+
var import_ora17 = __toESM(require("ora"));
|
|
2714
|
+
function requireLinkedApp7() {
|
|
2715
|
+
const linked = config.getLinkedApp();
|
|
2716
|
+
if (!linked) {
|
|
2717
|
+
console.log(import_chalk21.default.red("No app linked. Run `sylphx link` first."));
|
|
2718
|
+
process.exit(1);
|
|
2719
|
+
}
|
|
2720
|
+
return linked;
|
|
2721
|
+
}
|
|
2722
|
+
function requireToken5() {
|
|
2723
|
+
const token = config.getToken();
|
|
2724
|
+
if (!token) {
|
|
2725
|
+
console.log(import_chalk21.default.red("Not authenticated. Run `sylphx login` first."));
|
|
2726
|
+
process.exit(1);
|
|
2727
|
+
}
|
|
2728
|
+
return token;
|
|
2729
|
+
}
|
|
2730
|
+
var listCmd5 = new import_commander20.Command("list").description("List persistent volumes for this project").action(async () => {
|
|
2731
|
+
requireToken5();
|
|
2732
|
+
const linked = requireLinkedApp7();
|
|
2733
|
+
const spinner = (0, import_ora17.default)("Fetching volumes...").start();
|
|
2734
|
+
try {
|
|
2735
|
+
const volumes = await api.listVolumes(linked.appId);
|
|
2736
|
+
spinner.stop();
|
|
2737
|
+
if (volumes.length === 0) {
|
|
2738
|
+
console.log(import_chalk21.default.dim(" No volumes provisioned."));
|
|
2739
|
+
console.log(
|
|
2740
|
+
import_chalk21.default.dim(`
|
|
2741
|
+
Run ${import_chalk21.default.cyan("sylphx volumes create <name>")} to provision.`)
|
|
2742
|
+
);
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2745
|
+
console.log();
|
|
2746
|
+
console.log(
|
|
2747
|
+
` ${import_chalk21.default.bold("NAME".padEnd(24))} ${"SIZE".padEnd(8)} ${"STATUS".padEnd(14)} ID`
|
|
2748
|
+
);
|
|
2749
|
+
console.log(` ${"\u2500".repeat(70)}`);
|
|
2750
|
+
for (const v of volumes) {
|
|
2751
|
+
const statusColor2 = v.status === "ready" ? import_chalk21.default.green((v.status ?? "unknown").padEnd(14)) : import_chalk21.default.yellow((v.status ?? "unknown").padEnd(14));
|
|
2752
|
+
const size = v.sizeGb ? `${v.sizeGb}Gi`.padEnd(8) : "\u2014".padEnd(8);
|
|
2753
|
+
console.log(
|
|
2754
|
+
` ${import_chalk21.default.cyan(v.name.padEnd(24))} ${size} ${statusColor2} ${import_chalk21.default.dim(v.id)}`
|
|
2755
|
+
);
|
|
2756
|
+
}
|
|
2757
|
+
console.log();
|
|
2758
|
+
} catch (err) {
|
|
2759
|
+
spinner.fail(import_chalk21.default.red(err instanceof Error ? err.message : String(err)));
|
|
2760
|
+
process.exit(1);
|
|
2761
|
+
}
|
|
2762
|
+
});
|
|
2763
|
+
var createCmd2 = new import_commander20.Command("create").description("Provision a persistent volume").argument("<name>", "Volume name").option("--size <gb>", "Size in GiB (default: 10)", "10").option(
|
|
2764
|
+
"--access-mode <mode>",
|
|
2765
|
+
"ReadWriteOnce or ReadWriteMany (default: ReadWriteOnce)",
|
|
2766
|
+
"ReadWriteOnce"
|
|
2767
|
+
).option("--mount <path>", "Default mount path (optional)").action(async (name, opts) => {
|
|
2768
|
+
requireToken5();
|
|
2769
|
+
const linked = requireLinkedApp7();
|
|
2770
|
+
const sizeGb = Number.parseInt(opts.size, 10);
|
|
2771
|
+
if (Number.isNaN(sizeGb) || sizeGb < 1) {
|
|
2772
|
+
console.log(import_chalk21.default.red(" --size must be a number >= 1"));
|
|
2773
|
+
process.exit(1);
|
|
2774
|
+
}
|
|
2775
|
+
if (!["ReadWriteOnce", "ReadWriteMany"].includes(opts.accessMode)) {
|
|
2776
|
+
console.log(import_chalk21.default.red(" --access-mode must be ReadWriteOnce or ReadWriteMany"));
|
|
2777
|
+
process.exit(1);
|
|
2778
|
+
}
|
|
2779
|
+
const spinner = (0, import_ora17.default)(`Provisioning volume '${name}' (${sizeGb}Gi)...`).start();
|
|
2780
|
+
try {
|
|
2781
|
+
const result = await api.createVolume(linked.appId, {
|
|
2782
|
+
name,
|
|
2783
|
+
sizeGb,
|
|
2784
|
+
accessMode: opts.accessMode,
|
|
2785
|
+
mountPath: opts.mount
|
|
2786
|
+
});
|
|
2787
|
+
const vol = result.volume;
|
|
2788
|
+
spinner.succeed(import_chalk21.default.green(`\u2713 Volume '${vol.name}' provisioned`));
|
|
2789
|
+
console.log();
|
|
2790
|
+
console.log(` ${import_chalk21.default.dim("ID:")} ${vol.id}`);
|
|
2791
|
+
console.log(` ${import_chalk21.default.dim("Size:")} ${vol.sizeGb}Gi`);
|
|
2792
|
+
console.log(` ${import_chalk21.default.dim("Access:")} ${vol.accessMode ?? "ReadWriteOnce"}`);
|
|
2793
|
+
console.log();
|
|
2794
|
+
} catch (err) {
|
|
2795
|
+
spinner.fail(import_chalk21.default.red(err instanceof Error ? err.message : String(err)));
|
|
2796
|
+
process.exit(1);
|
|
2797
|
+
}
|
|
2798
|
+
});
|
|
2799
|
+
var rmCmd4 = new import_commander20.Command("rm").description("Delete a persistent volume").argument("<id>", "Volume ID").option("--force", "Skip confirmation prompt").action(async (id, opts) => {
|
|
2800
|
+
requireToken5();
|
|
2801
|
+
const linked = requireLinkedApp7();
|
|
2802
|
+
if (!opts.force) {
|
|
2803
|
+
const rl = import_node_readline7.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
2804
|
+
await new Promise((resolve) => {
|
|
2805
|
+
rl.question(
|
|
2806
|
+
import_chalk21.default.yellow(` \u26A0\uFE0F Delete volume ${import_chalk21.default.bold(id)}? This will destroy all data. (y/N) `),
|
|
2807
|
+
(answer) => {
|
|
2808
|
+
rl.close();
|
|
2809
|
+
if (answer.toLowerCase() !== "y") {
|
|
2810
|
+
console.log(import_chalk21.default.dim(" Cancelled."));
|
|
2811
|
+
process.exit(0);
|
|
2812
|
+
}
|
|
2813
|
+
resolve();
|
|
2814
|
+
}
|
|
2815
|
+
);
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
const spinner = (0, import_ora17.default)(`Deleting volume ${id}...`).start();
|
|
2819
|
+
try {
|
|
2820
|
+
await api.deleteVolume(linked.appId, id);
|
|
2821
|
+
spinner.succeed(import_chalk21.default.green(`\u2713 Volume ${id} deleted`));
|
|
2822
|
+
} catch (err) {
|
|
2823
|
+
spinner.fail(import_chalk21.default.red(err instanceof Error ? err.message : String(err)));
|
|
2824
|
+
process.exit(1);
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
var volumesCommand = new import_commander20.Command("volumes").description("Manage persistent volumes").addCommand(listCmd5).addCommand(createCmd2).addCommand(rmCmd4).action(() => volumesCommand.help());
|
|
2828
|
+
|
|
1643
2829
|
// src/commands/whoami.ts
|
|
1644
|
-
var
|
|
1645
|
-
var
|
|
1646
|
-
var
|
|
1647
|
-
var whoamiCommand = new
|
|
2830
|
+
var import_chalk22 = __toESM(require("chalk"));
|
|
2831
|
+
var import_commander21 = require("commander");
|
|
2832
|
+
var import_ora18 = __toESM(require("ora"));
|
|
2833
|
+
var whoamiCommand = new import_commander21.Command("whoami").description("Show current user, org, and linked app").action(async () => {
|
|
1648
2834
|
const token = config.getToken();
|
|
1649
2835
|
if (!token) {
|
|
1650
|
-
console.log(
|
|
2836
|
+
console.log(import_chalk22.default.red("Not authenticated. Run `sylphx login` first."));
|
|
1651
2837
|
process.exit(1);
|
|
1652
2838
|
}
|
|
1653
|
-
const spinner = (0,
|
|
2839
|
+
const spinner = (0, import_ora18.default)("Fetching account info...").start();
|
|
1654
2840
|
try {
|
|
1655
2841
|
const me = await api.whoami();
|
|
1656
2842
|
spinner.stop();
|
|
1657
2843
|
console.log("");
|
|
1658
|
-
console.log(
|
|
1659
|
-
console.log(` User: ${
|
|
1660
|
-
console.log(` Name: ${
|
|
1661
|
-
console.log(` ID: ${
|
|
2844
|
+
console.log(import_chalk22.default.bold(" Account"));
|
|
2845
|
+
console.log(` User: ${import_chalk22.default.cyan(me.user.email)}`);
|
|
2846
|
+
console.log(` Name: ${import_chalk22.default.white(me.user.name)}`);
|
|
2847
|
+
console.log(` ID: ${import_chalk22.default.dim(me.user.id)}`);
|
|
1662
2848
|
if (me.orgs.length > 0) {
|
|
1663
2849
|
console.log("");
|
|
1664
|
-
console.log(
|
|
2850
|
+
console.log(import_chalk22.default.bold(" Organizations"));
|
|
1665
2851
|
for (const org of me.orgs) {
|
|
1666
2852
|
console.log(
|
|
1667
|
-
` ${
|
|
2853
|
+
` ${import_chalk22.default.cyan(org.slug)} ${import_chalk22.default.dim(`(${org.name})`)}`
|
|
1668
2854
|
);
|
|
1669
2855
|
}
|
|
1670
2856
|
}
|
|
1671
2857
|
const linkedApp = config.getLinkedApp();
|
|
1672
2858
|
if (linkedApp) {
|
|
1673
2859
|
console.log("");
|
|
1674
|
-
console.log(
|
|
1675
|
-
console.log(` App: ${
|
|
1676
|
-
console.log(` Org: ${
|
|
1677
|
-
console.log(` Env: ${
|
|
2860
|
+
console.log(import_chalk22.default.bold(" Linked App"));
|
|
2861
|
+
console.log(` App: ${import_chalk22.default.cyan(linkedApp.appId)}`);
|
|
2862
|
+
console.log(` Org: ${import_chalk22.default.white(linkedApp.orgId)}`);
|
|
2863
|
+
console.log(` Env: ${import_chalk22.default.white(linkedApp.defaultEnv)}`);
|
|
1678
2864
|
} else {
|
|
1679
2865
|
console.log("");
|
|
1680
2866
|
console.log(
|
|
1681
|
-
|
|
2867
|
+
import_chalk22.default.dim(" No app linked. Run `sylphx link` to link one.")
|
|
1682
2868
|
);
|
|
1683
2869
|
}
|
|
1684
2870
|
console.log("");
|
|
1685
2871
|
} catch (err) {
|
|
1686
2872
|
spinner.fail(
|
|
1687
|
-
|
|
2873
|
+
import_chalk22.default.red(
|
|
1688
2874
|
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1689
2875
|
)
|
|
1690
2876
|
);
|
|
@@ -1693,44 +2879,49 @@ var whoamiCommand = new import_commander15.Command("whoami").description("Show c
|
|
|
1693
2879
|
});
|
|
1694
2880
|
|
|
1695
2881
|
// src/index.ts
|
|
1696
|
-
var { version } = package_default;
|
|
1697
|
-
var program = new
|
|
1698
|
-
program.name("sylphx").description(`${
|
|
2882
|
+
var { version: version4 } = package_default;
|
|
2883
|
+
var program = new import_commander22.Command();
|
|
2884
|
+
program.name("sylphx").description(`${import_chalk23.default.bold("Sylphx Platform CLI")} \u2014 deploy and manage your applications`).version(version4, "-v, --version", "Print version").helpOption("-h, --help", "Show help").addHelpText(
|
|
1699
2885
|
"after",
|
|
1700
2886
|
`
|
|
1701
|
-
${
|
|
1702
|
-
${
|
|
1703
|
-
${
|
|
1704
|
-
${
|
|
1705
|
-
${
|
|
1706
|
-
${
|
|
1707
|
-
${
|
|
1708
|
-
${
|
|
1709
|
-
${
|
|
1710
|
-
${
|
|
1711
|
-
${
|
|
1712
|
-
${
|
|
1713
|
-
${
|
|
1714
|
-
${
|
|
1715
|
-
${
|
|
1716
|
-
${
|
|
2887
|
+
${import_chalk23.default.bold("Examples:")}
|
|
2888
|
+
${import_chalk23.default.cyan("sylphx login")} Authenticate with Sylphx
|
|
2889
|
+
${import_chalk23.default.cyan("sylphx init my-app")} Create and link a new project
|
|
2890
|
+
${import_chalk23.default.cyan("sylphx link")} Link current directory to an app
|
|
2891
|
+
${import_chalk23.default.cyan("sylphx deploy")} Deploy to production
|
|
2892
|
+
${import_chalk23.default.cyan("sylphx deploy --env staging")} Deploy to staging
|
|
2893
|
+
${import_chalk23.default.cyan("sylphx promote --from staging")} Promote staging build \u2192 production
|
|
2894
|
+
${import_chalk23.default.cyan("sylphx rollback")} Rollback to previous deployment
|
|
2895
|
+
${import_chalk23.default.cyan("sylphx logs -f")} Stream live logs
|
|
2896
|
+
${import_chalk23.default.cyan("sylphx status")} Check deployment status
|
|
2897
|
+
${import_chalk23.default.cyan("sylphx env list")} List environment variables
|
|
2898
|
+
${import_chalk23.default.cyan("sylphx env set PORT=3000")} Set an env var
|
|
2899
|
+
${import_chalk23.default.cyan("sylphx config list")} List remote config keys
|
|
2900
|
+
${import_chalk23.default.cyan("sylphx config set KEY=value")} Set a remote config key
|
|
2901
|
+
${import_chalk23.default.cyan("sylphx db create my-db")} Provision a PostgreSQL database
|
|
2902
|
+
${import_chalk23.default.cyan("sylphx storage create")} Provision blob storage
|
|
2903
|
+
${import_chalk23.default.cyan("sylphx volumes create data --size 20")} Provision a persistent volume
|
|
2904
|
+
${import_chalk23.default.cyan("sylphx services list")} List project services
|
|
2905
|
+
${import_chalk23.default.cyan("sylphx services deploy web")} Deploy a specific service
|
|
2906
|
+
${import_chalk23.default.cyan("sylphx resources bind <id>")} Bind a resource to this project
|
|
2907
|
+
${import_chalk23.default.cyan("sylphx projects list")} List all projects
|
|
1717
2908
|
|
|
1718
|
-
${
|
|
1719
|
-
${
|
|
2909
|
+
${import_chalk23.default.bold("Documentation:")}
|
|
2910
|
+
${import_chalk23.default.underline("https://docs.sylphx.com/cli")}
|
|
1720
2911
|
`
|
|
1721
2912
|
);
|
|
1722
|
-
program.addCommand(loginCommand).addCommand(logoutCommand).addCommand(whoamiCommand).addCommand(initCommand).addCommand(linkCommand).addCommand(unlinkCommand).addCommand(deployCommand).addCommand(logsCommand).addCommand(envCommand).addCommand(
|
|
2913
|
+
program.addCommand(loginCommand).addCommand(logoutCommand).addCommand(whoamiCommand).addCommand(initCommand).addCommand(linkCommand).addCommand(unlinkCommand).addCommand(projectsCommand).addCommand(deployCommand).addCommand(promoteCommand).addCommand(rollbackCommand).addCommand(statusCommand).addCommand(logsCommand).addCommand(envCommand).addCommand(configCommand).addCommand(dbCommand).addCommand(storageCommand).addCommand(volumesCommand).addCommand(resourcesCommand).addCommand(servicesCommand).addCommand(domainsCommand).addCommand(openCommand);
|
|
1723
2914
|
program.on("command:*", (operands) => {
|
|
1724
|
-
console.error(
|
|
1725
|
-
Unknown command: ${
|
|
2915
|
+
console.error(import_chalk23.default.red(`
|
|
2916
|
+
Unknown command: ${import_chalk23.default.bold(operands[0] ?? "")}
|
|
1726
2917
|
`));
|
|
1727
|
-
console.log(` Run ${
|
|
2918
|
+
console.log(` Run ${import_chalk23.default.cyan("sylphx --help")} for usage.
|
|
1728
2919
|
`);
|
|
1729
2920
|
process.exit(1);
|
|
1730
2921
|
});
|
|
1731
2922
|
program.parseAsync(process.argv).catch((err) => {
|
|
1732
2923
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1733
|
-
console.error(
|
|
2924
|
+
console.error(import_chalk23.default.red(`
|
|
1734
2925
|
Error: ${msg}
|
|
1735
2926
|
`));
|
|
1736
2927
|
process.exit(1);
|