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