@kitecd/cli 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/doctor.js +181 -0
- package/dist/export.js +144 -0
- package/dist/ignore.js +38 -0
- package/dist/import.js +333 -0
- package/dist/index.js +213 -3
- package/dist/ops.js +338 -0
- package/dist/pack.js +15 -5
- package/dist/serve.js +27 -1
- package/dist/server/index.js +17231 -5610
- package/dist/upload.js +11 -3
- package/dist/verify.js +161 -0
- package/dist/web/assets/AuditLog-BO9BJdsk.js +1 -0
- package/dist/web/assets/ConfirmDialog-CikXU818.js +1 -0
- package/dist/web/assets/ConfirmDialog-D8avT8FJ.css +1 -0
- package/dist/web/assets/Dashboard-ho157_Gr.js +1 -0
- package/dist/web/assets/DefaultLayout-Y_852d55.js +1 -0
- package/dist/web/assets/DefaultLayout-lj5NNciV.css +1 -0
- package/dist/web/assets/FileExplorer-oxT6ZdHt.js +1 -0
- package/dist/web/assets/FolderPickerDialog-CDO-yrvE.css +1 -0
- package/dist/web/assets/FolderPickerDialog-CKohwBIP.js +1 -0
- package/dist/web/assets/LogBoard-DkvHTXcb.js +6 -0
- package/dist/web/assets/LogBoard-Dx8yNofc.css +1 -0
- package/dist/web/assets/LogTail-CuaBDDKf.js +9 -0
- package/dist/web/assets/Login-kcvq2T9U.js +1 -0
- package/dist/web/assets/Migration-C_M_Exzf.js +1 -0
- package/dist/web/assets/ProjectDetail-BTOfo71A.js +1 -0
- package/dist/web/assets/ProjectDetail-ia6-z1kZ.css +1 -0
- package/dist/web/assets/ProjectList-D_PoTDT9.js +1 -0
- package/dist/web/assets/ProjectList-YJlvRRNh.css +1 -0
- package/dist/web/assets/ProjectTagsEditor-zXN_5rwP.js +1 -0
- package/dist/web/assets/Settings-BL4hNZpU.js +1 -0
- package/dist/web/assets/Storage-B-pZjj08.js +1 -0
- package/dist/web/assets/Storage-CFbfZtA5.css +1 -0
- package/dist/web/assets/Terminal-Bh7bM6Bb.css +1 -0
- package/dist/web/assets/Terminal-fTZlKf2Y.js +7 -0
- package/dist/web/assets/{activity-DItEGOtI.js → activity-DZCOq6kQ.js} +1 -1
- package/dist/web/assets/archive-MB1JcYug.js +1 -0
- package/dist/web/assets/arrow-left-CsZYc3XK.js +1 -0
- package/dist/web/assets/{circle-alert-Bfrn_ovD.js → circle-alert-kmcvMchB.js} +1 -1
- package/dist/web/assets/clock-DIBzfDoY.js +1 -0
- package/dist/web/assets/constants-m1eFfRMw.js +1 -0
- package/dist/web/assets/copy-CNXWZJbC.js +1 -0
- package/dist/web/assets/createLucideIcon-BmsTm7z-.js +1 -0
- package/dist/web/assets/database-D6rC_24l.js +1 -0
- package/dist/web/assets/eye-off-BNUfzp8P.js +1 -0
- package/dist/web/assets/eye-vRDqRzR9.js +1 -0
- package/dist/web/assets/file-text-BxWt6is5.js +1 -0
- package/dist/web/assets/folder-CYPJgLMr.js +1 -0
- package/dist/web/assets/folder-open-DQz0XviI.js +1 -0
- package/dist/web/assets/hard-drive-DBiQxkNS.js +1 -0
- package/dist/web/assets/history-dkZbN_TB.js +1 -0
- package/dist/web/assets/house-D9JIOKIs.js +1 -0
- package/dist/web/assets/index-BZU6i5nw.js +2 -0
- package/dist/web/assets/index-BrlC5Hdt.css +1 -0
- package/dist/web/assets/loader-circle-OwtRa1dp.js +1 -0
- package/dist/web/assets/pencil-BTFuj0gA.js +1 -0
- package/dist/web/assets/plus-CcefsCv_.js +1 -0
- package/dist/web/assets/refresh-cw-OqaxwQhF.js +1 -0
- package/dist/web/assets/rotate-ccw-D8C0iiAw.js +1 -0
- package/dist/web/assets/save-D0NTjP8Q.js +1 -0
- package/dist/web/assets/scroll-text-6UwJwqap.js +1 -0
- package/dist/web/assets/{server-C33taHNn.js → server-DFHpqwVn.js} +1 -1
- package/dist/web/assets/square-terminal-C1oJyHEG.js +1 -0
- package/dist/web/assets/sun-Cg6PdQms.js +1 -0
- package/dist/web/assets/terminal-CUz5b_ol.js +1 -0
- package/dist/web/assets/trash-2-20ikd6fk.js +1 -0
- package/dist/web/assets/useIntervalRaf-CXWU2lqg.js +1 -0
- package/dist/web/index.html +3 -3
- package/package.json +11 -7
- package/scripts/postinstall.js +53 -0
- package/dist/web/assets/Dashboard-7wBCpwzp.js +0 -1
- package/dist/web/assets/DefaultLayout-Bj8fPWym.css +0 -1
- package/dist/web/assets/DefaultLayout-LaPhlICp.js +0 -1
- package/dist/web/assets/FileExplorer-DvzkxXvZ.js +0 -1
- package/dist/web/assets/LogBoard-BWAPesBz.js +0 -6
- package/dist/web/assets/LogBoard-DzW-cEqH.css +0 -1
- package/dist/web/assets/Login-BhNdUJs0.js +0 -1
- package/dist/web/assets/ProjectDetail-DZwMOEto.js +0 -1
- package/dist/web/assets/ProjectList-Czr-438J.js +0 -1
- package/dist/web/assets/Settings-OmPvcQbD.js +0 -1
- package/dist/web/assets/clock-BPXGSCIV.js +0 -1
- package/dist/web/assets/constants-iI5LEC2F.js +0 -1
- package/dist/web/assets/createLucideIcon-Cgv1AIRL.js +0 -1
- package/dist/web/assets/folder-open-jX-_Q7bA.js +0 -1
- package/dist/web/assets/index-C9LiRc31.css +0 -1
- package/dist/web/assets/index-eyx6wNyQ.js +0 -2
- package/dist/web/assets/project-BFuaDcvV.js +0 -1
- package/dist/web/assets/refresh-cw-DWmqwQRn.js +0 -1
- package/dist/web/assets/save-BkiMrL9q.js +0 -1
- package/dist/web/assets/settings-CrCWmNyB.js +0 -1
- package/dist/web/assets/square-terminal-C8toRwjx.js +0 -1
package/dist/ops.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import readline from 'readline/promises';
|
|
3
|
+
import { stdin as input, stdout as output } from 'process';
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
|
+
import { readGlobalConfig, readLocalEnv, resolveProjectConfig, envTokenKey, listProjectEnvs, } from './home.js';
|
|
6
|
+
export function resolveOpsAuth(opts = {}) {
|
|
7
|
+
const globalCfg = readGlobalConfig();
|
|
8
|
+
const localEnv = readLocalEnv();
|
|
9
|
+
const serverUrl = opts.server ||
|
|
10
|
+
process.env.KITE_SERVER_URL ||
|
|
11
|
+
localEnv.KITE_SERVER_URL ||
|
|
12
|
+
globalCfg.serverUrl;
|
|
13
|
+
if (!serverUrl) {
|
|
14
|
+
throw new Error('Server URL not configured. Use --server <url> or run `kite config:set serverUrl <url> --global`.');
|
|
15
|
+
}
|
|
16
|
+
if (opts.token) {
|
|
17
|
+
return { serverUrl, token: opts.token, source: 'cli flag' };
|
|
18
|
+
}
|
|
19
|
+
if (opts.requireAdmin) {
|
|
20
|
+
const adminTok = process.env.KITE_TOKEN || globalCfg.token;
|
|
21
|
+
if (!adminTok) {
|
|
22
|
+
throw new Error('Admin token required. Run `kite config:set token <admin> --global` or pass --token.');
|
|
23
|
+
}
|
|
24
|
+
return { serverUrl, token: adminTok, source: 'global admin token' };
|
|
25
|
+
}
|
|
26
|
+
const envTok = process.env.KITE_TOKEN || process.env.KITE_DEPLOY_TOKEN || localEnv.KITE_TOKEN || localEnv.KITE_DEPLOY_TOKEN;
|
|
27
|
+
if (envTok)
|
|
28
|
+
return { serverUrl, token: envTok, source: 'env var' };
|
|
29
|
+
const allEnvs = listProjectEnvs();
|
|
30
|
+
let resolved = null;
|
|
31
|
+
if (opts.env) {
|
|
32
|
+
resolved = resolveProjectConfig(opts.env);
|
|
33
|
+
}
|
|
34
|
+
else if (allEnvs.length === 1) {
|
|
35
|
+
resolved = allEnvs[0];
|
|
36
|
+
}
|
|
37
|
+
if (resolved?.config?.projectId) {
|
|
38
|
+
const key = envTokenKey(resolved.config.projectId, resolved.env);
|
|
39
|
+
const projTok = globalCfg.projectToken?.[key];
|
|
40
|
+
if (projTok)
|
|
41
|
+
return { serverUrl, token: projTok, source: `project token (${key})` };
|
|
42
|
+
}
|
|
43
|
+
if (globalCfg.token) {
|
|
44
|
+
return { serverUrl, token: globalCfg.token, source: 'global admin token' };
|
|
45
|
+
}
|
|
46
|
+
throw new Error('Token not found. Configure via `kite config:set token <value>` (project) or `kite config:set token <admin> --global`.');
|
|
47
|
+
}
|
|
48
|
+
async function readJson(res) {
|
|
49
|
+
const text = await res.text();
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(text);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { error: text };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function expectOk(res, label) {
|
|
58
|
+
if (res.status === 401)
|
|
59
|
+
throw new Error(`Unauthorized: invalid token (${label})`);
|
|
60
|
+
if (res.status === 404) {
|
|
61
|
+
const body = await readJson(res);
|
|
62
|
+
throw new Error(`Not found: ${body.error || label}`);
|
|
63
|
+
}
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
const body = await readJson(res);
|
|
66
|
+
throw new Error(`[${res.status}] ${body.error || label}`);
|
|
67
|
+
}
|
|
68
|
+
return readJson(res);
|
|
69
|
+
}
|
|
70
|
+
function statusColor(status) {
|
|
71
|
+
switch (status) {
|
|
72
|
+
case 'success': return chalk.green(status);
|
|
73
|
+
case 'failed': return chalk.red(status);
|
|
74
|
+
case 'running': return chalk.yellow(status);
|
|
75
|
+
case 'idle': return chalk.gray(status);
|
|
76
|
+
default: return chalk.gray(status || '-');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function padCell(text, width) {
|
|
80
|
+
const visible = text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
81
|
+
if (visible.length >= width)
|
|
82
|
+
return text;
|
|
83
|
+
return text + ' '.repeat(width - visible.length);
|
|
84
|
+
}
|
|
85
|
+
function printTable(rows, headers) {
|
|
86
|
+
const cols = headers.length;
|
|
87
|
+
const widths = new Array(cols).fill(0);
|
|
88
|
+
for (const r of [headers, ...rows]) {
|
|
89
|
+
for (let i = 0; i < cols; i++) {
|
|
90
|
+
const visible = (r[i] || '').replace(/\x1b\[[0-9;]*m/g, '');
|
|
91
|
+
if (visible.length > widths[i])
|
|
92
|
+
widths[i] = visible.length;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const headerLine = headers.map((h, i) => padCell(chalk.bold(h), widths[i])).join(' ');
|
|
96
|
+
console.log(headerLine);
|
|
97
|
+
console.log(chalk.gray(widths.map((w) => '-'.repeat(w)).join(' ')));
|
|
98
|
+
for (const r of rows) {
|
|
99
|
+
console.log(r.map((c, i) => padCell(c, widths[i])).join(' '));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export async function runList(opts) {
|
|
103
|
+
const auth = resolveOpsAuth({ ...opts, requireAdmin: true });
|
|
104
|
+
const res = await fetch(`${auth.serverUrl.replace(/\/$/, '')}/api/projects`, {
|
|
105
|
+
headers: { Authorization: `Bearer ${auth.token}` },
|
|
106
|
+
});
|
|
107
|
+
const projects = await expectOk(res, 'GET /api/projects');
|
|
108
|
+
let filtered = projects;
|
|
109
|
+
if (opts.env)
|
|
110
|
+
filtered = projects.filter((p) => (p.env || '') === opts.env);
|
|
111
|
+
if (opts.json) {
|
|
112
|
+
process.stdout.write(JSON.stringify(filtered, null, 2) + '\n');
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
if (filtered.length === 0) {
|
|
116
|
+
console.log(chalk.gray('No projects found.'));
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
const rows = filtered.map((p) => [
|
|
120
|
+
p.id,
|
|
121
|
+
p.name,
|
|
122
|
+
p.env || chalk.gray('-'),
|
|
123
|
+
statusColor(p.status),
|
|
124
|
+
chalk.gray(p.updatedAt ? new Date(p.updatedAt).toISOString().slice(0, 19).replace('T', ' ') : '-'),
|
|
125
|
+
]);
|
|
126
|
+
printTable(rows, ['ID', 'NAME', 'ENV', 'STATUS', 'UPDATED']);
|
|
127
|
+
console.log(chalk.gray(`\n${filtered.length} project(s)`));
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
export async function runStatus(projectIdArg, opts) {
|
|
131
|
+
const auth = resolveOpsAuth({ ...opts, requireAdmin: true });
|
|
132
|
+
const limit = Math.min(Math.max(Number(opts.limit) || 5, 1), 50);
|
|
133
|
+
let projectId = projectIdArg;
|
|
134
|
+
if (!projectId) {
|
|
135
|
+
const all = listProjectEnvs();
|
|
136
|
+
const resolved = opts.env ? resolveProjectConfig(opts.env) : (all.length === 1 ? all[0] : null);
|
|
137
|
+
if (resolved?.config?.projectId) {
|
|
138
|
+
projectId = resolved.config.projectId;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!projectId) {
|
|
142
|
+
throw new Error('projectId required. Pass <projectId> or run inside a project directory with kite.config*.json');
|
|
143
|
+
}
|
|
144
|
+
const base = auth.serverUrl.replace(/\/$/, '');
|
|
145
|
+
const [projectRes, logsRes] = await Promise.all([
|
|
146
|
+
fetch(`${base}/api/projects/${projectId}`, { headers: { Authorization: `Bearer ${auth.token}` } }),
|
|
147
|
+
fetch(`${base}/api/logs`, { headers: { Authorization: `Bearer ${auth.token}` } }),
|
|
148
|
+
]);
|
|
149
|
+
const project = await expectOk(projectRes, `GET /api/projects/${projectId}`);
|
|
150
|
+
const logs = await expectOk(logsRes, 'GET /api/logs');
|
|
151
|
+
const projectLogs = logs
|
|
152
|
+
.filter((l) => l.projectId === projectId)
|
|
153
|
+
.sort((a, b) => (b.startTime || '').localeCompare(a.startTime || ''))
|
|
154
|
+
.slice(0, limit);
|
|
155
|
+
if (opts.json) {
|
|
156
|
+
process.stdout.write(JSON.stringify({ project, deployments: projectLogs }, null, 2) + '\n');
|
|
157
|
+
return 0;
|
|
158
|
+
}
|
|
159
|
+
console.log(chalk.bold(`Project: ${project.name}`) + chalk.gray(` (${project.id})`));
|
|
160
|
+
console.log(chalk.gray(`Status: `) + statusColor(project.status));
|
|
161
|
+
console.log('');
|
|
162
|
+
if (projectLogs.length === 0) {
|
|
163
|
+
console.log(chalk.gray('No deployments yet.'));
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
const rows = projectLogs.map((d) => [
|
|
167
|
+
d.id.slice(0, 12),
|
|
168
|
+
statusColor(d.status),
|
|
169
|
+
d.triggerSource,
|
|
170
|
+
chalk.gray(d.startTime ? d.startTime.slice(0, 19).replace('T', ' ') : '-'),
|
|
171
|
+
d.duration || chalk.gray('-'),
|
|
172
|
+
d.rollbackOf ? chalk.gray(`← ${d.rollbackOf.slice(0, 8)}`) : '',
|
|
173
|
+
]);
|
|
174
|
+
printTable(rows, ['DEPLOY ID', 'STATUS', 'TRIGGER', 'STARTED', 'DURATION', 'ROLLBACK OF']);
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
const STATUS_EXIT = { success: 0, failed: 1, running: 0 };
|
|
178
|
+
export async function runLogs(deployId, opts) {
|
|
179
|
+
const auth = resolveOpsAuth({ ...opts, requireAdmin: true });
|
|
180
|
+
const base = auth.serverUrl.replace(/\/$/, '');
|
|
181
|
+
if (!opts.follow) {
|
|
182
|
+
const res = await fetch(`${base}/api/logs/${deployId}`, {
|
|
183
|
+
headers: { Authorization: `Bearer ${auth.token}` },
|
|
184
|
+
});
|
|
185
|
+
const log = await expectOk(res, `GET /api/logs/${deployId}`);
|
|
186
|
+
if (opts.json) {
|
|
187
|
+
process.stdout.write(JSON.stringify(log, null, 2) + '\n');
|
|
188
|
+
return STATUS_EXIT[log.status] ?? 1;
|
|
189
|
+
}
|
|
190
|
+
console.log(chalk.bold(`Deployment ${log.id.slice(0, 12)}`) + chalk.gray(` (${log.projectName})`));
|
|
191
|
+
console.log(chalk.gray('Status: ') + statusColor(log.status) + chalk.gray(` duration=${log.duration || '-'} trigger=${log.triggerSource}`));
|
|
192
|
+
console.log('');
|
|
193
|
+
if (log.output)
|
|
194
|
+
process.stdout.write(log.output);
|
|
195
|
+
if (!log.output?.endsWith('\n'))
|
|
196
|
+
process.stdout.write('\n');
|
|
197
|
+
return STATUS_EXIT[log.status] ?? 1;
|
|
198
|
+
}
|
|
199
|
+
const res = await fetch(`${base}/api/logs/${deployId}/stream`, {
|
|
200
|
+
headers: {
|
|
201
|
+
Authorization: `Bearer ${auth.token}`,
|
|
202
|
+
Accept: 'text/event-stream',
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
if (res.status === 401)
|
|
206
|
+
throw new Error('Unauthorized: invalid token');
|
|
207
|
+
if (res.status === 404)
|
|
208
|
+
throw new Error('Deployment not found');
|
|
209
|
+
if (!res.ok || !res.body)
|
|
210
|
+
throw new Error(`SSE failed: HTTP ${res.status}`);
|
|
211
|
+
const reader = res.body.getReader();
|
|
212
|
+
const decoder = new TextDecoder();
|
|
213
|
+
let buffer = '';
|
|
214
|
+
let lastStatus = null;
|
|
215
|
+
let printedOutputLen = 0;
|
|
216
|
+
while (true) {
|
|
217
|
+
const { done, value } = await reader.read();
|
|
218
|
+
if (done)
|
|
219
|
+
break;
|
|
220
|
+
buffer += decoder.decode(value, { stream: true });
|
|
221
|
+
const events = buffer.split('\n\n');
|
|
222
|
+
buffer = events.pop();
|
|
223
|
+
for (const block of events) {
|
|
224
|
+
if (!block.trim())
|
|
225
|
+
continue;
|
|
226
|
+
let event = 'message';
|
|
227
|
+
let data = '';
|
|
228
|
+
for (const line of block.split('\n')) {
|
|
229
|
+
if (line.startsWith('event: '))
|
|
230
|
+
event = line.slice(7).trim();
|
|
231
|
+
else if (line.startsWith('data: '))
|
|
232
|
+
data += line.slice(6);
|
|
233
|
+
}
|
|
234
|
+
let parsed = data;
|
|
235
|
+
try {
|
|
236
|
+
parsed = JSON.parse(data);
|
|
237
|
+
}
|
|
238
|
+
catch { }
|
|
239
|
+
if (event === 'log') {
|
|
240
|
+
// First log event from server contains the full historical output.
|
|
241
|
+
// Subsequent events are incremental lines.
|
|
242
|
+
if (printedOutputLen === 0 && typeof parsed === 'string' && parsed.includes('\n')) {
|
|
243
|
+
process.stdout.write(parsed);
|
|
244
|
+
if (!parsed.endsWith('\n'))
|
|
245
|
+
process.stdout.write('\n');
|
|
246
|
+
printedOutputLen = parsed.length;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
process.stdout.write(parsed + '\n');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (event === 'status') {
|
|
253
|
+
let payload = parsed;
|
|
254
|
+
if (typeof payload === 'string') {
|
|
255
|
+
try {
|
|
256
|
+
payload = JSON.parse(payload);
|
|
257
|
+
}
|
|
258
|
+
catch { }
|
|
259
|
+
}
|
|
260
|
+
lastStatus = payload?.status || null;
|
|
261
|
+
const dur = payload?.duration || '-';
|
|
262
|
+
console.log(chalk.gray(`\n[Kite Logs] Deployment finished: `) + statusColor(lastStatus) + chalk.gray(` duration=${dur}`));
|
|
263
|
+
try {
|
|
264
|
+
reader.cancel();
|
|
265
|
+
}
|
|
266
|
+
catch { }
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (lastStatus)
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
return STATUS_EXIT[lastStatus || ''] ?? 0;
|
|
274
|
+
}
|
|
275
|
+
export async function runRollback(projectIdArg, opts) {
|
|
276
|
+
const auth = resolveOpsAuth({ ...opts, requireAdmin: true });
|
|
277
|
+
const base = auth.serverUrl.replace(/\/$/, '');
|
|
278
|
+
let projectId = projectIdArg;
|
|
279
|
+
if (!projectId) {
|
|
280
|
+
const all = listProjectEnvs();
|
|
281
|
+
const resolved = opts.env ? resolveProjectConfig(opts.env) : (all.length === 1 ? all[0] : null);
|
|
282
|
+
if (resolved?.config?.projectId)
|
|
283
|
+
projectId = resolved.config.projectId;
|
|
284
|
+
}
|
|
285
|
+
if (!projectId)
|
|
286
|
+
throw new Error('projectId required for rollback.');
|
|
287
|
+
let targetDeployId = opts.to;
|
|
288
|
+
if (!targetDeployId) {
|
|
289
|
+
const logsRes = await fetch(`${base}/api/logs`, { headers: { Authorization: `Bearer ${auth.token}` } });
|
|
290
|
+
const logs = await expectOk(logsRes, 'GET /api/logs');
|
|
291
|
+
const candidates = logs
|
|
292
|
+
.filter((l) => l.projectId === projectId && l.status === 'success' && !!l.artifactPath && l.triggerSource !== 'rollback')
|
|
293
|
+
.sort((a, b) => (b.startTime || '').localeCompare(a.startTime || ''));
|
|
294
|
+
if (candidates.length === 0) {
|
|
295
|
+
throw new Error('No successful deployment with archived artifact found. Pass --to <deployId> explicitly.');
|
|
296
|
+
}
|
|
297
|
+
targetDeployId = candidates[0].id;
|
|
298
|
+
console.log(chalk.gray(`Selected latest success deploy: ${targetDeployId.slice(0, 12)} (${candidates[0].startTime.slice(0, 19).replace('T', ' ')})`));
|
|
299
|
+
}
|
|
300
|
+
if (!opts.yes) {
|
|
301
|
+
if (!process.stdin.isTTY) {
|
|
302
|
+
throw new Error('Refusing to rollback in non-TTY mode without --yes. Add --yes to confirm.');
|
|
303
|
+
}
|
|
304
|
+
const rl = readline.createInterface({ input, output });
|
|
305
|
+
const answer = await rl.question(chalk.yellow(`About to rollback project ${projectId} to deploy ${targetDeployId.slice(0, 12)}. Proceed? [y/N] `));
|
|
306
|
+
rl.close();
|
|
307
|
+
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
308
|
+
console.log(chalk.gray('Rollback cancelled.'));
|
|
309
|
+
return 1;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const traceId = randomUUID();
|
|
313
|
+
const res = await fetch(`${base}/api/deployments/${targetDeployId}/rollback`, {
|
|
314
|
+
method: 'POST',
|
|
315
|
+
headers: {
|
|
316
|
+
Authorization: `Bearer ${auth.token}`,
|
|
317
|
+
'X-Kite-Trace-Id': traceId,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
const body = await readJson(res);
|
|
321
|
+
if (res.status === 401)
|
|
322
|
+
throw new Error('Unauthorized: invalid token');
|
|
323
|
+
if (res.status === 404)
|
|
324
|
+
throw new Error(body?.error || 'Deployment or artifact not found');
|
|
325
|
+
if (!res.ok || !body?.success) {
|
|
326
|
+
throw new Error(body?.error || `Rollback failed: HTTP ${res.status}`);
|
|
327
|
+
}
|
|
328
|
+
if (opts.json) {
|
|
329
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
console.log(chalk.green(`Rollback succeeded.`));
|
|
333
|
+
console.log(chalk.gray(` new deployId : ${body.deployId}`));
|
|
334
|
+
console.log(chalk.gray(` rolled back : ${body.rollbackOf}`));
|
|
335
|
+
console.log(chalk.gray(` duration : ${body.duration}`));
|
|
336
|
+
console.log(chalk.gray(` traceId : ${body.traceId}`));
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
package/dist/pack.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import archiver from 'archiver';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
|
|
4
|
+
import { mergeIgnore } from './ignore.js';
|
|
5
5
|
/**
|
|
6
6
|
* 将路径列表折叠:如果一个目录下的所有文件都在列表中,则只展示目录
|
|
7
7
|
*/
|
|
@@ -78,9 +78,19 @@ function collapseEntries(rawEntries) {
|
|
|
78
78
|
* 将指定目录或文件打包为 zip 文件
|
|
79
79
|
* @param sourceDir 要打包的源目录
|
|
80
80
|
* @param destZip 目标 zip 文件的路径
|
|
81
|
-
* @param
|
|
81
|
+
* @param filesOrOptions 兼容旧签名 string[],或新版 PackOptions
|
|
82
|
+
*
|
|
83
|
+
* 忽略规则语义:
|
|
84
|
+
* - 显式 files 条目命中真实路径(archive.file / archive.directory)→ 不应用 ignore,
|
|
85
|
+
* 用户明确指定即表示需要。
|
|
86
|
+
* - glob 通配(archive.glob 含默认全量)→ 应用 mergeIgnore({ custom, ignoreBuiltin })。
|
|
82
87
|
*/
|
|
83
|
-
export async function packProject(sourceDir, destZip,
|
|
88
|
+
export async function packProject(sourceDir, destZip, filesOrOptions) {
|
|
89
|
+
const opts = Array.isArray(filesOrOptions)
|
|
90
|
+
? { files: filesOrOptions }
|
|
91
|
+
: (filesOrOptions || {});
|
|
92
|
+
const files = opts.files;
|
|
93
|
+
const ignoreList = mergeIgnore({ custom: opts.ignore, ignoreBuiltin: opts.ignoreBuiltin });
|
|
84
94
|
return new Promise((resolve, reject) => {
|
|
85
95
|
const output = fs.createWriteStream(destZip);
|
|
86
96
|
const archive = archiver('zip', {
|
|
@@ -125,12 +135,12 @@ export async function packProject(sourceDir, destZip, files) {
|
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
137
|
else {
|
|
128
|
-
archive.glob(pattern, { cwd: sourceDir, ignore:
|
|
138
|
+
archive.glob(pattern, { cwd: sourceDir, ignore: ignoreList });
|
|
129
139
|
}
|
|
130
140
|
});
|
|
131
141
|
}
|
|
132
142
|
else {
|
|
133
|
-
archive.glob('**/*', { cwd: sourceDir, ignore:
|
|
143
|
+
archive.glob('**/*', { cwd: sourceDir, ignore: ignoreList });
|
|
134
144
|
}
|
|
135
145
|
archive.finalize();
|
|
136
146
|
});
|
package/dist/serve.js
CHANGED
|
@@ -34,6 +34,17 @@ function detectRuntime(preferred) {
|
|
|
34
34
|
process.exit(1);
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
+
function isWeakAdminToken(token) {
|
|
38
|
+
if (typeof token !== 'string')
|
|
39
|
+
return { weak: true, reason: 'token 必须是字符串' };
|
|
40
|
+
if (token.length < 24)
|
|
41
|
+
return { weak: true, reason: '长度不足 24' };
|
|
42
|
+
if (!/[A-Za-z]/.test(token) || !/[0-9]/.test(token))
|
|
43
|
+
return { weak: true, reason: '需同时包含字母和数字' };
|
|
44
|
+
if (new Set(token).size < 8)
|
|
45
|
+
return { weak: true, reason: '字符多样性不足(去重 < 8)' };
|
|
46
|
+
return { weak: false };
|
|
47
|
+
}
|
|
37
48
|
function ensureAdminToken() {
|
|
38
49
|
const localEnv = readLocalEnv();
|
|
39
50
|
if (localEnv.KITE_DEPLOY_TOKEN) {
|
|
@@ -46,7 +57,12 @@ function ensureAdminToken() {
|
|
|
46
57
|
const content = fs.readFileSync(envPath, 'utf-8');
|
|
47
58
|
const match = content.match(/^ADMIN_TOKEN=(.+)$/m);
|
|
48
59
|
if (match) {
|
|
49
|
-
|
|
60
|
+
const existing = match[1].replace(/^['"]|['"]$/g, '');
|
|
61
|
+
const check = isWeakAdminToken(existing);
|
|
62
|
+
if (check.weak) {
|
|
63
|
+
console.warn(chalk.yellow(`[warn] 当前 ADMIN_TOKEN 强度不足(${check.reason}),建议执行 kite rotate-token 或在 .env.local 中替换为长度 ≥ 24 且包含字母和数字、去重字符 ≥ 8 的随机字符串。`));
|
|
64
|
+
}
|
|
65
|
+
return existing;
|
|
50
66
|
}
|
|
51
67
|
}
|
|
52
68
|
// Generate a new admin token
|
|
@@ -66,6 +82,14 @@ function buildServerEnv(options, adminToken) {
|
|
|
66
82
|
KITE_SERVER_VERSION: cliPkg.version || '1.0.0',
|
|
67
83
|
};
|
|
68
84
|
}
|
|
85
|
+
function isLocalHost(host) {
|
|
86
|
+
return host === '127.0.0.1' || host === 'localhost' || host === '::1';
|
|
87
|
+
}
|
|
88
|
+
function warnRemoteHost(host) {
|
|
89
|
+
if (!isLocalHost(host)) {
|
|
90
|
+
console.warn(chalk.yellow(`[warn] 当前监听 host=${host},将对外网络暴露 Kite 管理端。请确保已在前置代理(Nginx/Caddy)配置 TLS 与限速,否则建议使用 --host 127.0.0.1。`));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
69
93
|
function startForeground(options, env, runtime) {
|
|
70
94
|
const bundlePath = getServerBundlePath();
|
|
71
95
|
if (!fs.existsSync(bundlePath)) {
|
|
@@ -81,6 +105,7 @@ function startForeground(options, env, runtime) {
|
|
|
81
105
|
console.log(chalk.gray(` DB Dir: ${env.KITE_DB_DIR}`));
|
|
82
106
|
console.log(chalk.yellow(` Admin Token: ${env.ADMIN_TOKEN}`));
|
|
83
107
|
console.log();
|
|
108
|
+
warnRemoteHost(options.host);
|
|
84
109
|
const args = runtime.name === 'bun' ? ['run', bundlePath] : [bundlePath];
|
|
85
110
|
const child = spawn(runtime.name, args, {
|
|
86
111
|
stdio: 'inherit',
|
|
@@ -181,6 +206,7 @@ function startPm2(options, env, runtime) {
|
|
|
181
206
|
console.log(chalk.gray(' pm2 logs kite-server # View logs'));
|
|
182
207
|
console.log(chalk.gray(' pm2 status # Check status'));
|
|
183
208
|
console.log(chalk.gray(' kite serve --pm2 stop # Stop server'));
|
|
209
|
+
warnRemoteHost(options.host);
|
|
184
210
|
}
|
|
185
211
|
function stopPm2() {
|
|
186
212
|
const result = spawnSync('pm2', ['delete', 'kite-server'], { stdio: 'inherit' });
|