@kitecd/cli 1.0.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/bin/kite.js +2 -0
- package/dist/home.js +114 -0
- package/dist/index.js +603 -0
- package/dist/local-server.js +434 -0
- package/dist/local-store.js +142 -0
- package/dist/pack.js +137 -0
- package/dist/serve.js +208 -0
- package/dist/server/index.js +30043 -0
- package/dist/upload.js +84 -0
- package/dist/web/assets/Dashboard-pjIWWLub.js +1 -0
- package/dist/web/assets/DefaultLayout-Bj8fPWym.css +1 -0
- package/dist/web/assets/DefaultLayout-DelfwTTT.js +1 -0
- package/dist/web/assets/FileExplorer-xY5ejhhN.js +1 -0
- package/dist/web/assets/LogBoard-DzW-cEqH.css +1 -0
- package/dist/web/assets/LogBoard-tT61QjOx.js +6 -0
- package/dist/web/assets/Login-B4C149oC.js +1 -0
- package/dist/web/assets/ProjectDetail-Z8cZoqr5.js +1 -0
- package/dist/web/assets/ProjectList-9rbMuJeY.js +1 -0
- package/dist/web/assets/Settings-CtCNDUXY.js +1 -0
- package/dist/web/assets/activity-DItEGOtI.js +1 -0
- package/dist/web/assets/circle-alert-Bfrn_ovD.js +1 -0
- package/dist/web/assets/clock-BPXGSCIV.js +1 -0
- package/dist/web/assets/constants-C4Zrkm2g.js +1 -0
- package/dist/web/assets/createLucideIcon-Cgv1AIRL.js +1 -0
- package/dist/web/assets/folder-open-jX-_Q7bA.js +1 -0
- package/dist/web/assets/index-C615tnMi.js +2 -0
- package/dist/web/assets/index-C9LiRc31.css +1 -0
- package/dist/web/assets/project-BFuaDcvV.js +1 -0
- package/dist/web/assets/refresh-cw-DWmqwQRn.js +1 -0
- package/dist/web/assets/save-BkiMrL9q.js +1 -0
- package/dist/web/assets/server-C33taHNn.js +1 -0
- package/dist/web/assets/settings-CrCWmNyB.js +1 -0
- package/dist/web/assets/square-terminal-C8toRwjx.js +1 -0
- package/dist/web/favicon.svg +5 -0
- package/dist/web/icons.svg +24 -0
- package/dist/web/index.html +15 -0
- package/package.json +40 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import cac from 'cac';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import readline from 'readline/promises';
|
|
7
|
+
import { stdin as input, stdout as output } from 'process';
|
|
8
|
+
import { packProject } from './pack.js';
|
|
9
|
+
import { uploadZip } from './upload.js';
|
|
10
|
+
import { getConfigPath, getKiteHome, randomToken, readGlobalConfig, readLocalEnv, setGlobalConfig, writeGlobalConfig, writeLocalEnvValue, listProjectEnvs, resolveProjectConfig, envTokenKey } from './home.js';
|
|
11
|
+
import { LocalStore } from './local-store.js';
|
|
12
|
+
import { startServe } from './serve.js';
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
const cli = cac('kite');
|
|
15
|
+
// ==========================
|
|
16
|
+
// Config commands
|
|
17
|
+
// ==========================
|
|
18
|
+
cli.command('config:set <key> <value>', 'Set global configuration')
|
|
19
|
+
.option('--global', 'Set global fallback token instead of per-project token')
|
|
20
|
+
.option('--env <name>', 'Environment name (selects kite.config.<name>.json)')
|
|
21
|
+
.action((key, value, options) => {
|
|
22
|
+
if (key === 'token' && !options.global) {
|
|
23
|
+
const allEnvs = listProjectEnvs();
|
|
24
|
+
let resolved = null;
|
|
25
|
+
if (options.env) {
|
|
26
|
+
resolved = resolveProjectConfig(options.env);
|
|
27
|
+
}
|
|
28
|
+
else if (allEnvs.length === 1) {
|
|
29
|
+
resolved = allEnvs[0];
|
|
30
|
+
}
|
|
31
|
+
else if (allEnvs.length > 1) {
|
|
32
|
+
// 多环境时,非 TTY 提示传 --env
|
|
33
|
+
if (!process.stdin.isTTY) {
|
|
34
|
+
console.error(chalk.red('Multiple kite.config*.json found. Pass --env to specify environment.'));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
// 同步场景不能用 async prompt,直接提示
|
|
38
|
+
console.error(chalk.red('Multiple kite.config*.json found. Pass --env to specify environment.'));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
if (resolved && resolved.config.projectId) {
|
|
42
|
+
const tokenKey = envTokenKey(resolved.config.projectId, resolved.env);
|
|
43
|
+
const config = readGlobalConfig();
|
|
44
|
+
config.projectToken = { ...config.projectToken, [tokenKey]: value };
|
|
45
|
+
writeGlobalConfig(config);
|
|
46
|
+
console.log(chalk.green(`Set token for ${tokenKey}`));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
console.log(chalk.yellow('No kite.config*.json found, setting as global token.'));
|
|
50
|
+
}
|
|
51
|
+
setGlobalConfig(key, value);
|
|
52
|
+
console.log(chalk.green(`Set ${key} = ${value}`));
|
|
53
|
+
});
|
|
54
|
+
cli.command('config:get <key>', 'Get global configuration')
|
|
55
|
+
.option('--env <name>', 'Environment name (selects kite.config.<name>.json)')
|
|
56
|
+
.action((key, options) => {
|
|
57
|
+
const config = readGlobalConfig();
|
|
58
|
+
if (key === 'token') {
|
|
59
|
+
const allEnvs = listProjectEnvs();
|
|
60
|
+
let resolved = null;
|
|
61
|
+
if (options.env) {
|
|
62
|
+
resolved = resolveProjectConfig(options.env);
|
|
63
|
+
}
|
|
64
|
+
else if (allEnvs.length === 1) {
|
|
65
|
+
resolved = allEnvs[0];
|
|
66
|
+
}
|
|
67
|
+
else if (allEnvs.length > 1) {
|
|
68
|
+
// 多环境非 TTY 时尝试 default
|
|
69
|
+
resolved = resolveProjectConfig();
|
|
70
|
+
}
|
|
71
|
+
if (resolved && resolved.config.projectId) {
|
|
72
|
+
const tokenKey = envTokenKey(resolved.config.projectId, resolved.env);
|
|
73
|
+
const projectToken = config.projectToken?.[tokenKey]
|
|
74
|
+
|| (resolved.env ? config.projectToken?.[resolved.config.projectId] : undefined);
|
|
75
|
+
if (projectToken) {
|
|
76
|
+
console.log(projectToken);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
console.log(config[key]);
|
|
82
|
+
});
|
|
83
|
+
cli.command('config:list', 'List all global configurations')
|
|
84
|
+
.action(() => {
|
|
85
|
+
const config = readGlobalConfig();
|
|
86
|
+
console.log(config);
|
|
87
|
+
if (config.projectToken && Object.keys(config.projectToken).length > 0) {
|
|
88
|
+
console.log(chalk.gray('\nPer-project tokens:'));
|
|
89
|
+
for (const [pid, tok] of Object.entries(config.projectToken)) {
|
|
90
|
+
console.log(` ${pid}: ${tok}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
cli.command('config', 'Show current effective configuration (merged from all sources)')
|
|
95
|
+
.option('--env <name>', 'Environment name (selects kite.config.<name>.json)')
|
|
96
|
+
.action((options) => {
|
|
97
|
+
const globalConfig = readGlobalConfig();
|
|
98
|
+
const localEnv = readLocalEnv();
|
|
99
|
+
// 列出所有可用环境
|
|
100
|
+
const allEnvs = listProjectEnvs();
|
|
101
|
+
let resolved = null;
|
|
102
|
+
if (options.env) {
|
|
103
|
+
resolved = resolveProjectConfig(options.env);
|
|
104
|
+
}
|
|
105
|
+
else if (allEnvs.length === 1) {
|
|
106
|
+
resolved = allEnvs[0];
|
|
107
|
+
}
|
|
108
|
+
else if (allEnvs.length > 1) {
|
|
109
|
+
resolved = resolveProjectConfig(); // default
|
|
110
|
+
}
|
|
111
|
+
const projectConfig = resolved?.config || {};
|
|
112
|
+
const envName = resolved?.env;
|
|
113
|
+
const configPath = resolved?.configPath;
|
|
114
|
+
const projectId = localEnv.KITE_PROJECT_ID || projectConfig.projectId;
|
|
115
|
+
const tokenKey = projectId ? envTokenKey(projectId, envName) : '';
|
|
116
|
+
const token = localEnv.KITE_DEPLOY_TOKEN || localEnv.KITE_TOKEN || globalConfig.projectToken?.[tokenKey] || (envName && projectId ? globalConfig.projectToken?.[projectId] : undefined) || globalConfig.token;
|
|
117
|
+
const serverUrl = localEnv.KITE_SERVER_URL || globalConfig.serverUrl;
|
|
118
|
+
const outputDir = localEnv.KITE_OUTPUT_DIR || projectConfig.outputDir || './';
|
|
119
|
+
const preDeploy = localEnv.KITE_PRE_DEPLOY || projectConfig.preDeploy;
|
|
120
|
+
const postDeploy = localEnv.KITE_DEPLOY_COMMAND || localEnv.KITE_POST_DEPLOY || projectConfig.command || projectConfig.postDeploy;
|
|
121
|
+
console.log(chalk.bold('Effective config:'));
|
|
122
|
+
if (allEnvs.length > 1) {
|
|
123
|
+
console.log(` env: ${envName || chalk.gray('default')}`);
|
|
124
|
+
}
|
|
125
|
+
console.log(` serverUrl: ${serverUrl || chalk.gray('(not set)')}`);
|
|
126
|
+
console.log(` projectId: ${projectId || chalk.gray('(not set)')}`);
|
|
127
|
+
console.log(` token: ${token ? '****' + token.slice(-4) : chalk.gray('(not set)')}`);
|
|
128
|
+
console.log(` outputDir: ${outputDir}`);
|
|
129
|
+
console.log(` preDeploy: ${preDeploy || chalk.gray('(not set)')}`);
|
|
130
|
+
console.log(` postDeploy: ${postDeploy || chalk.gray('(not set)')}`);
|
|
131
|
+
if (projectConfig.files?.length) {
|
|
132
|
+
console.log(` files: ${projectConfig.files.join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
if (allEnvs.length > 1) {
|
|
135
|
+
console.log(chalk.gray('\nAvailable environments:'));
|
|
136
|
+
for (const e of allEnvs) {
|
|
137
|
+
const label = e.env || 'default';
|
|
138
|
+
const marker = e.env === envName ? chalk.green(' (current)') : '';
|
|
139
|
+
console.log(chalk.gray(` ${label}: ${e.configPath}`) + marker);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
console.log(chalk.gray(`\nSources:`));
|
|
143
|
+
console.log(chalk.gray(` global: ${getConfigPath()}`));
|
|
144
|
+
console.log(chalk.gray(` project: ${configPath || '(not found)'}`));
|
|
145
|
+
console.log(chalk.gray(` env: ${fs.existsSync(path.join(process.cwd(), '.env.local')) ? path.join(process.cwd(), '.env.local') : '(not found)'}`));
|
|
146
|
+
});
|
|
147
|
+
cli.command('home', 'Print Kite home directory')
|
|
148
|
+
.action(() => {
|
|
149
|
+
console.log(getKiteHome());
|
|
150
|
+
});
|
|
151
|
+
const askAdminToken = async () => {
|
|
152
|
+
if (!process.stdin.isTTY) {
|
|
153
|
+
return randomToken('admin');
|
|
154
|
+
}
|
|
155
|
+
const rl = readline.createInterface({ input, output });
|
|
156
|
+
const mode = (await rl.question('Reset admin password with random token or manual input? (random/manual) ')).trim().toLowerCase();
|
|
157
|
+
if (mode === 'manual') {
|
|
158
|
+
const manualToken = (await rl.question('Enter new admin password/token: ')).trim();
|
|
159
|
+
rl.close();
|
|
160
|
+
if (!manualToken) {
|
|
161
|
+
throw new Error('Admin password/token cannot be empty.');
|
|
162
|
+
}
|
|
163
|
+
return manualToken;
|
|
164
|
+
}
|
|
165
|
+
rl.close();
|
|
166
|
+
return randomToken('admin');
|
|
167
|
+
};
|
|
168
|
+
const resetAdminPassword = async (options) => {
|
|
169
|
+
try {
|
|
170
|
+
const nextToken = options.password
|
|
171
|
+
? String(options.password)
|
|
172
|
+
: options.random
|
|
173
|
+
? randomToken('admin')
|
|
174
|
+
: await askAdminToken();
|
|
175
|
+
if (!nextToken) {
|
|
176
|
+
throw new Error('Admin password/token cannot be empty.');
|
|
177
|
+
}
|
|
178
|
+
const store = new LocalStore();
|
|
179
|
+
store.updateAdminToken(nextToken);
|
|
180
|
+
console.log(chalk.green('Admin password/token has been reset.'));
|
|
181
|
+
console.log(chalk.gray(`Data home: ${store.home}`));
|
|
182
|
+
console.log(chalk.yellow(`New admin password/token: ${nextToken}`));
|
|
183
|
+
console.log(chalk.gray('Running `kite serve` instances read this file on each request, so restart is not required.'));
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error(chalk.red(`Failed to reset admin password/token: ${error.message}`));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
cli.command('admin <action>', 'Admin operations')
|
|
191
|
+
.option('--random', 'Generate a random admin password/token')
|
|
192
|
+
.option('--password <password>', 'Set admin password/token manually')
|
|
193
|
+
.action(async (action, options) => {
|
|
194
|
+
if (action !== 'reset-password') {
|
|
195
|
+
console.error(chalk.red(`Unknown admin action: ${action}`));
|
|
196
|
+
console.log('Available actions: reset-password');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
await resetAdminPassword(options);
|
|
200
|
+
});
|
|
201
|
+
cli.command('reset-password', 'Reset Web admin password without restarting Kite server')
|
|
202
|
+
.option('--random', 'Generate a random admin password/token')
|
|
203
|
+
.option('--password <password>', 'Set admin password/token manually')
|
|
204
|
+
.action(resetAdminPassword);
|
|
205
|
+
const askEnvironment = async (envs) => {
|
|
206
|
+
if (!process.stdin.isTTY) {
|
|
207
|
+
console.error(chalk.red('Multiple kite.config*.json files found. Pass --env to specify environment.'));
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
let selected = 0;
|
|
211
|
+
const render = () => {
|
|
212
|
+
// Move cursor up to overwrite previous render
|
|
213
|
+
if (render._rendered) {
|
|
214
|
+
process.stdout.write(`\x1b[${render._rendered + 1}A`);
|
|
215
|
+
}
|
|
216
|
+
console.log(chalk.bold('Select environment:'));
|
|
217
|
+
for (let i = 0; i < envs.length; i++) {
|
|
218
|
+
const e = envs[i];
|
|
219
|
+
const label = e.env || 'default';
|
|
220
|
+
const indicator = i === selected ? chalk.cyan('❯ ') : ' ';
|
|
221
|
+
const name = i === selected ? chalk.cyan.bold(label) : label;
|
|
222
|
+
const file = chalk.gray(path.basename(e.configPath));
|
|
223
|
+
console.log(`${indicator}${name} ${file}`);
|
|
224
|
+
}
|
|
225
|
+
console.log(chalk.gray(' ↑↓ move enter confirm'));
|
|
226
|
+
render._rendered = envs.length + 1; // lines printed (header + items + footer)
|
|
227
|
+
};
|
|
228
|
+
return new Promise((resolve) => {
|
|
229
|
+
process.stdin.setRawMode(true);
|
|
230
|
+
process.stdin.resume();
|
|
231
|
+
process.stdin.setEncoding('utf-8');
|
|
232
|
+
render();
|
|
233
|
+
const onData = (key) => {
|
|
234
|
+
// Enter
|
|
235
|
+
if (key === '\r' || key === '\n') {
|
|
236
|
+
cleanup();
|
|
237
|
+
console.log(); // newline after selection
|
|
238
|
+
resolve(envs[selected].env);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// Ctrl-C
|
|
242
|
+
if (key === '') {
|
|
243
|
+
cleanup();
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
// Up arrow or k
|
|
247
|
+
if (key === '[A' || key === 'k') {
|
|
248
|
+
selected = (selected - 1 + envs.length) % envs.length;
|
|
249
|
+
render();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
// Down arrow or j
|
|
253
|
+
if (key === '[B' || key === 'j') {
|
|
254
|
+
selected = (selected + 1) % envs.length;
|
|
255
|
+
render();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// Number keys 1-9 for quick jump
|
|
259
|
+
const num = parseInt(key, 10);
|
|
260
|
+
if (num >= 1 && num <= envs.length) {
|
|
261
|
+
selected = num - 1;
|
|
262
|
+
render();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const cleanup = () => {
|
|
267
|
+
process.stdin.removeListener('data', onData);
|
|
268
|
+
process.stdin.setRawMode(false);
|
|
269
|
+
process.stdin.pause();
|
|
270
|
+
};
|
|
271
|
+
process.stdin.on('data', onData);
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
const askTokenStore = async () => {
|
|
275
|
+
if (!process.stdin.isTTY)
|
|
276
|
+
return 'none';
|
|
277
|
+
const options = [
|
|
278
|
+
{ value: 'global', label: 'Global config', desc: '~/.kite/config.json' },
|
|
279
|
+
{ value: 'local', label: 'Local .env.local', desc: 'current project directory' },
|
|
280
|
+
{ value: 'none', label: "Don't save", desc: 'save manually later' },
|
|
281
|
+
];
|
|
282
|
+
let selected = 0;
|
|
283
|
+
const render = () => {
|
|
284
|
+
if (render._rendered) {
|
|
285
|
+
process.stdout.write(`\x1b[${render._rendered + 1}A`);
|
|
286
|
+
}
|
|
287
|
+
console.log(chalk.bold('Save deploy token to:'));
|
|
288
|
+
for (let i = 0; i < options.length; i++) {
|
|
289
|
+
const o = options[i];
|
|
290
|
+
const indicator = i === selected ? chalk.cyan('❯ ') : ' ';
|
|
291
|
+
const name = i === selected ? chalk.cyan.bold(o.label) : o.label;
|
|
292
|
+
const desc = chalk.gray(o.desc);
|
|
293
|
+
console.log(`${indicator}${name} ${desc}`);
|
|
294
|
+
}
|
|
295
|
+
console.log(chalk.gray(' ↑↓ move enter confirm'));
|
|
296
|
+
render._rendered = options.length + 1;
|
|
297
|
+
};
|
|
298
|
+
return new Promise((resolve) => {
|
|
299
|
+
process.stdin.setRawMode(true);
|
|
300
|
+
process.stdin.resume();
|
|
301
|
+
process.stdin.setEncoding('utf-8');
|
|
302
|
+
render();
|
|
303
|
+
const onData = (key) => {
|
|
304
|
+
if (key === '\r' || key === '\n') {
|
|
305
|
+
cleanup();
|
|
306
|
+
console.log();
|
|
307
|
+
resolve(options[selected].value);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (key === '\x03') {
|
|
311
|
+
cleanup();
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
if (key === '\x1b[A' || key === 'k') {
|
|
315
|
+
selected = (selected - 1 + options.length) % options.length;
|
|
316
|
+
render();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (key === '\x1b[B' || key === 'j') {
|
|
320
|
+
selected = (selected + 1) % options.length;
|
|
321
|
+
render();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const num = parseInt(key, 10);
|
|
325
|
+
if (num >= 1 && num <= options.length) {
|
|
326
|
+
selected = num - 1;
|
|
327
|
+
render();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const cleanup = () => {
|
|
332
|
+
process.stdin.removeListener('data', onData);
|
|
333
|
+
process.stdin.setRawMode(false);
|
|
334
|
+
process.stdin.pause();
|
|
335
|
+
};
|
|
336
|
+
process.stdin.on('data', onData);
|
|
337
|
+
});
|
|
338
|
+
};
|
|
339
|
+
// ==========================
|
|
340
|
+
// Init command
|
|
341
|
+
// ==========================
|
|
342
|
+
cli.command('init', 'Create kite.config.json without writing token into source config')
|
|
343
|
+
.option('--project <projectId>', 'Project ID')
|
|
344
|
+
.option('--env <name>', 'Environment name (creates kite.config.<name>.json)')
|
|
345
|
+
.option('--out <dir>', 'Output directory', { default: './dist' })
|
|
346
|
+
.option('--files <patterns>', 'Comma separated upload file patterns', { default: '**/*' })
|
|
347
|
+
.option('--server <server>', 'Server URL to save globally')
|
|
348
|
+
.option('--token <token>', 'Deploy token to save globally or in .env.local')
|
|
349
|
+
.option('--token-store <target>', 'Where to save token: global, local, none')
|
|
350
|
+
.option('--pre <script>', 'Pre-deploy script')
|
|
351
|
+
.option('--post <script>', 'Post-deploy script')
|
|
352
|
+
.option('--command <script>', 'Deploy command alias, same as --post')
|
|
353
|
+
.action(async (options) => {
|
|
354
|
+
const projectId = options.project;
|
|
355
|
+
if (!projectId) {
|
|
356
|
+
console.error(chalk.red('Error: --project is required.'));
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
const env = options.env || undefined;
|
|
360
|
+
const configFileName = env ? `kite.config.${env}.json` : 'kite.config.json';
|
|
361
|
+
const configPath = path.resolve(process.cwd(), configFileName);
|
|
362
|
+
const projectConfig = {
|
|
363
|
+
projectId,
|
|
364
|
+
outputDir: options.out || './dist',
|
|
365
|
+
files: String(options.files || '**/*').split(',').map(item => item.trim()).filter(Boolean)
|
|
366
|
+
};
|
|
367
|
+
if (options.pre)
|
|
368
|
+
projectConfig.preDeploy = options.pre;
|
|
369
|
+
if (options.command || options.post)
|
|
370
|
+
projectConfig.postDeploy = options.command || options.post;
|
|
371
|
+
fs.writeFileSync(configPath, `${JSON.stringify(projectConfig, null, 2)}\n`);
|
|
372
|
+
console.log(chalk.green(`Created ${configPath}`));
|
|
373
|
+
console.log(chalk.gray('Deploy token is intentionally not written to kite config file.'));
|
|
374
|
+
if (options.server) {
|
|
375
|
+
setGlobalConfig('serverUrl', options.server);
|
|
376
|
+
console.log(chalk.green(`Saved serverUrl to ${getKiteHome()}/config.json`));
|
|
377
|
+
}
|
|
378
|
+
if (options.token) {
|
|
379
|
+
const tokenStore = options.tokenStore || await askTokenStore();
|
|
380
|
+
const tokenKey = envTokenKey(projectId, env);
|
|
381
|
+
if (tokenStore === 'global') {
|
|
382
|
+
const config = readGlobalConfig();
|
|
383
|
+
config.projectToken = { ...config.projectToken, [tokenKey]: options.token };
|
|
384
|
+
writeGlobalConfig(config);
|
|
385
|
+
console.log(chalk.green(`Saved token for ${tokenKey} to ${getKiteHome()}/config.json`));
|
|
386
|
+
}
|
|
387
|
+
else if (tokenStore === 'local') {
|
|
388
|
+
writeLocalEnvValue('KITE_DEPLOY_TOKEN', options.token);
|
|
389
|
+
console.log(chalk.green('Saved token to .env.local'));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
console.log(chalk.yellow('Token was not saved. Pass --token on push, or save it with `kite config:set token <token>`.'));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
// ==========================
|
|
397
|
+
// Local server command
|
|
398
|
+
// ==========================
|
|
399
|
+
cli.command('serve', 'Start Kite Server and Web console')
|
|
400
|
+
.option('--host <host>', 'Host to listen on', { default: '127.0.0.1' })
|
|
401
|
+
.option('--port <port>', 'Port to listen on', { default: 5431 })
|
|
402
|
+
.option('--runtime <runtime>', 'Runtime to use: bun or node (default: auto, prefer bun)')
|
|
403
|
+
.option('--pm2 [action]', 'Daemonize with pm2 (pass "stop" to stop)')
|
|
404
|
+
.action(async (options) => {
|
|
405
|
+
try {
|
|
406
|
+
const pm2Action = typeof options.pm2 === 'string' ? options.pm2 : undefined;
|
|
407
|
+
await startServe({
|
|
408
|
+
host: options.host,
|
|
409
|
+
port: Number(options.port),
|
|
410
|
+
runtime: options.runtime,
|
|
411
|
+
pm2: !!options.pm2,
|
|
412
|
+
pm2Action: pm2Action,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
console.error(chalk.red(`Failed to start Kite: ${error.message}`));
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
// ==========================
|
|
421
|
+
// Pack result display helper
|
|
422
|
+
// ==========================
|
|
423
|
+
const displayPackResult = (result) => {
|
|
424
|
+
const sizeKB = (result.size / 1024).toFixed(1);
|
|
425
|
+
const sizeMB = (result.size / (1024 * 1024)).toFixed(2);
|
|
426
|
+
const sizeStr = result.size > 1024 * 1024 ? `${sizeMB} MB` : `${sizeKB} KB`;
|
|
427
|
+
console.log(chalk.gray(` Archive size: ${sizeStr} (${result.fileCount} files)`));
|
|
428
|
+
console.log(chalk.gray(' Included:'));
|
|
429
|
+
for (const entry of result.entries) {
|
|
430
|
+
const isDir = entry.endsWith('/');
|
|
431
|
+
console.log(chalk.gray(` ${isDir ? chalk.blue(entry) : entry}`));
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
// ==========================
|
|
435
|
+
// Build command
|
|
436
|
+
// ==========================
|
|
437
|
+
cli.command('build', 'Pack project files and verify packaging (no upload)')
|
|
438
|
+
.option('--env <name>', 'Environment name (selects kite.config.<name>.json)')
|
|
439
|
+
.option('--out <dir>', 'Output directory to pack')
|
|
440
|
+
.action(async (options) => {
|
|
441
|
+
try {
|
|
442
|
+
const allEnvs = listProjectEnvs();
|
|
443
|
+
if (allEnvs.length === 0) {
|
|
444
|
+
console.error(chalk.red('No kite.config*.json found. Run `kite init` first.'));
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
let resolved;
|
|
448
|
+
if (options.env) {
|
|
449
|
+
const found = resolveProjectConfig(options.env);
|
|
450
|
+
if (!found) {
|
|
451
|
+
console.error(chalk.red(`kite.config.${options.env}.json not found.`));
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
resolved = found;
|
|
455
|
+
}
|
|
456
|
+
else if (allEnvs.length === 1) {
|
|
457
|
+
resolved = allEnvs[0];
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
const selectedEnv = await askEnvironment(allEnvs);
|
|
461
|
+
resolved = allEnvs.find(e => e.env === selectedEnv);
|
|
462
|
+
}
|
|
463
|
+
const projectConfig = resolved.config;
|
|
464
|
+
const outputDir = options.out || projectConfig.outputDir || './';
|
|
465
|
+
const files = projectConfig.files || [];
|
|
466
|
+
const sourceDir = path.resolve(process.cwd(), outputDir);
|
|
467
|
+
if (!fs.existsSync(sourceDir)) {
|
|
468
|
+
console.error(chalk.red(`Error: Output directory not found: ${sourceDir}`));
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
const spinner = ora('Packing files...').start();
|
|
472
|
+
const zipFilePath = path.resolve(process.cwd(), '.deploy-archive.zip');
|
|
473
|
+
const result = await packProject(sourceDir, zipFilePath, files.length > 0 ? files : undefined);
|
|
474
|
+
spinner.succeed(chalk.green('Pack successful!'));
|
|
475
|
+
displayPackResult(result);
|
|
476
|
+
console.log(chalk.gray(` Archive: ${zipFilePath}`));
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
console.error(chalk.red(`\nBuild failed: ${error.message}`));
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
// ==========================
|
|
484
|
+
// Push command
|
|
485
|
+
// ==========================
|
|
486
|
+
cli.command('push', 'Push and deploy project')
|
|
487
|
+
.option('--token <token>', 'Deployment token')
|
|
488
|
+
.option('--server <server>', 'Server URL')
|
|
489
|
+
.option('--project <projectId>', 'Project ID')
|
|
490
|
+
.option('--env <name>', 'Environment name (selects kite.config.<name>.json)')
|
|
491
|
+
.option('--out <dir>', 'Output directory to pack')
|
|
492
|
+
.option('--pre <script>', 'Pre-deploy script (Server side)')
|
|
493
|
+
.option('--post <script>', 'Post-deploy script (Server side)')
|
|
494
|
+
.option('--command <script>', 'Deploy command alias, same as --post')
|
|
495
|
+
.action(async (options) => {
|
|
496
|
+
try {
|
|
497
|
+
// 1. 解析环境配置
|
|
498
|
+
const allEnvs = listProjectEnvs();
|
|
499
|
+
if (allEnvs.length === 0) {
|
|
500
|
+
console.error(chalk.red('No kite.config*.json found. Run `kite init` first.'));
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
let resolved;
|
|
504
|
+
if (options.env) {
|
|
505
|
+
const found = resolveProjectConfig(options.env);
|
|
506
|
+
if (!found) {
|
|
507
|
+
console.error(chalk.red(`kite.config.${options.env}.json not found.`));
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
resolved = found;
|
|
511
|
+
}
|
|
512
|
+
else if (allEnvs.length === 1) {
|
|
513
|
+
resolved = allEnvs[0];
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
const selectedEnv = await askEnvironment(allEnvs);
|
|
517
|
+
resolved = allEnvs.find(e => e.env === selectedEnv);
|
|
518
|
+
}
|
|
519
|
+
const projectConfig = resolved.config;
|
|
520
|
+
const envName = resolved.env;
|
|
521
|
+
if (envName) {
|
|
522
|
+
console.log(chalk.gray(`Environment: ${envName}`));
|
|
523
|
+
}
|
|
524
|
+
// 2. 读取全局配置与本地密钥
|
|
525
|
+
const globalConfig = options.token && options.server ? null : readGlobalConfig();
|
|
526
|
+
const localEnv = readLocalEnv();
|
|
527
|
+
// 3. 解析 projectId(token 查找需要依赖它)
|
|
528
|
+
const projectId = options.project || localEnv.KITE_PROJECT_ID || projectConfig.projectId;
|
|
529
|
+
if (!projectId) {
|
|
530
|
+
console.error(chalk.red('Error: projectId is required. Pass --project or set projectId in kite config.'));
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
// 4. 解析 token:env-specific key 优先,再 fallback 到 default key
|
|
534
|
+
const tokenKey = envTokenKey(projectId, envName);
|
|
535
|
+
const token = options.token
|
|
536
|
+
|| localEnv.KITE_DEPLOY_TOKEN
|
|
537
|
+
|| localEnv.KITE_TOKEN
|
|
538
|
+
|| globalConfig?.projectToken?.[tokenKey]
|
|
539
|
+
|| (envName ? globalConfig?.projectToken?.[projectId] : undefined)
|
|
540
|
+
|| globalConfig?.token;
|
|
541
|
+
const serverUrl = options.server || localEnv.KITE_SERVER_URL || globalConfig?.serverUrl;
|
|
542
|
+
if (!token && !serverUrl) {
|
|
543
|
+
console.error(chalk.red('Missing token and serverUrl. Run `kite config:set serverUrl <url>` and `kite config:set token <token>`.'));
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
if (!serverUrl) {
|
|
547
|
+
console.error(chalk.red('Missing serverUrl. Run `kite config:set serverUrl <url>`.'));
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
if (!token) {
|
|
551
|
+
console.error(chalk.red(`Missing token for "${tokenKey}". Run \`kite config:set token <token>\` or pass --token.`));
|
|
552
|
+
process.exit(1);
|
|
553
|
+
}
|
|
554
|
+
const outputDir = options.out || localEnv.KITE_OUTPUT_DIR || projectConfig.outputDir || './';
|
|
555
|
+
const files = projectConfig.files || [];
|
|
556
|
+
const preDeploy = options.pre || localEnv.KITE_PRE_DEPLOY || projectConfig.preDeploy;
|
|
557
|
+
const postDeploy = options.command || options.post || localEnv.KITE_DEPLOY_COMMAND || localEnv.KITE_POST_DEPLOY || projectConfig.command || projectConfig.postDeploy;
|
|
558
|
+
const sourceDir = path.resolve(process.cwd(), outputDir);
|
|
559
|
+
if (!fs.existsSync(sourceDir)) {
|
|
560
|
+
console.error(chalk.red(`Error: Output directory not found: ${sourceDir}`));
|
|
561
|
+
process.exit(1);
|
|
562
|
+
}
|
|
563
|
+
// 5. 打包文件
|
|
564
|
+
const spinner = ora('Packing files...').start();
|
|
565
|
+
const zipFilePath = path.resolve(process.cwd(), '.deploy-archive.zip');
|
|
566
|
+
const packResult = await packProject(sourceDir, zipFilePath, files.length > 0 ? files : undefined);
|
|
567
|
+
spinner.succeed(chalk.green('Packed successfully'));
|
|
568
|
+
displayPackResult(packResult);
|
|
569
|
+
// 6. 上传与部署
|
|
570
|
+
spinner.start(`Uploading to ${serverUrl}...`);
|
|
571
|
+
spinner.stop();
|
|
572
|
+
const result = await uploadZip({
|
|
573
|
+
serverUrl: serverUrl,
|
|
574
|
+
token: token,
|
|
575
|
+
zipFilePath,
|
|
576
|
+
projectId,
|
|
577
|
+
preDeploy,
|
|
578
|
+
postDeploy
|
|
579
|
+
});
|
|
580
|
+
if (result.success) {
|
|
581
|
+
console.log(chalk.green(`\nDeployed successfully! (${result.duration})`));
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
console.error(chalk.red('\nDeployment failed'));
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
console.error(chalk.red(`\nDeployment failed: ${error.message}`));
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
finally {
|
|
593
|
+
// 7. 清理临时文件
|
|
594
|
+
const zipFilePath = path.resolve(process.cwd(), '.deploy-archive.zip');
|
|
595
|
+
if (fs.existsSync(zipFilePath)) {
|
|
596
|
+
fs.unlinkSync(zipFilePath);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
cli.help();
|
|
601
|
+
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
602
|
+
cli.version(pkg.version);
|
|
603
|
+
cli.parse();
|