@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/pack.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import archiver from 'archiver';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
const IGNORED = ['node_modules/**', '.git/**', '*.zip', '.env*'];
|
|
5
|
+
/**
|
|
6
|
+
* 将路径列表折叠:如果一个目录下的所有文件都在列表中,则只展示目录
|
|
7
|
+
*/
|
|
8
|
+
function collapseEntries(rawEntries) {
|
|
9
|
+
if (rawEntries.length === 0)
|
|
10
|
+
return [];
|
|
11
|
+
// 构建目录树
|
|
12
|
+
const tree = {};
|
|
13
|
+
for (const entry of rawEntries) {
|
|
14
|
+
const parts = entry.split('/');
|
|
15
|
+
for (let i = 1; i < parts.length; i++) {
|
|
16
|
+
const dir = parts.slice(0, i).join('/');
|
|
17
|
+
if (!tree[dir])
|
|
18
|
+
tree[dir] = new Set();
|
|
19
|
+
tree[dir].add(parts.slice(0, i + 1).join('/'));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// 递归检查:目录下所有子项都在列表中则折叠
|
|
23
|
+
const allPaths = new Set(rawEntries);
|
|
24
|
+
function isCompleteDir(dirPath) {
|
|
25
|
+
const children = tree[dirPath];
|
|
26
|
+
if (!children)
|
|
27
|
+
return true;
|
|
28
|
+
for (const child of children) {
|
|
29
|
+
if (tree[child]) {
|
|
30
|
+
if (!isCompleteDir(child))
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
else if (!allPaths.has(child)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const result = [];
|
|
40
|
+
const added = new Set();
|
|
41
|
+
for (const entry of rawEntries) {
|
|
42
|
+
// 检查这个文件的祖先目录是否已经完整折叠
|
|
43
|
+
let dominated = false;
|
|
44
|
+
const parts = entry.split('/');
|
|
45
|
+
for (let i = 1; i < parts.length; i++) {
|
|
46
|
+
const ancestor = parts.slice(0, i).join('/');
|
|
47
|
+
if (added.has(ancestor + '/')) {
|
|
48
|
+
dominated = true;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
if (tree[ancestor] && isCompleteDir(ancestor)) {
|
|
52
|
+
result.push(ancestor + '/');
|
|
53
|
+
added.add(ancestor + '/');
|
|
54
|
+
dominated = true;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (dominated)
|
|
59
|
+
continue;
|
|
60
|
+
// 检查这个文件是否是一个完整目录的叶子
|
|
61
|
+
const dir = path.dirname(entry);
|
|
62
|
+
if (dir !== '.' && tree[dir] && isCompleteDir(dir)) {
|
|
63
|
+
if (!added.has(dir + '/')) {
|
|
64
|
+
result.push(dir + '/');
|
|
65
|
+
added.add(dir + '/');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
if (!added.has(entry)) {
|
|
70
|
+
result.push(entry);
|
|
71
|
+
added.add(entry);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 将指定目录或文件打包为 zip 文件
|
|
79
|
+
* @param sourceDir 要打包的源目录
|
|
80
|
+
* @param destZip 目标 zip 文件的路径
|
|
81
|
+
* @param files 允许上传的特定文件或目录列表(可选,如果提供则仅打包这些内容)
|
|
82
|
+
*/
|
|
83
|
+
export async function packProject(sourceDir, destZip, files) {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const output = fs.createWriteStream(destZip);
|
|
86
|
+
const archive = archiver('zip', {
|
|
87
|
+
zlib: { level: 9 }
|
|
88
|
+
});
|
|
89
|
+
const rawEntries = [];
|
|
90
|
+
archive.on('entry', (entry) => {
|
|
91
|
+
if (entry.stats && !entry.stats.isDirectory()) {
|
|
92
|
+
rawEntries.push(entry.name);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
output.on('close', () => {
|
|
96
|
+
const entries = collapseEntries(rawEntries);
|
|
97
|
+
resolve({
|
|
98
|
+
size: archive.pointer(),
|
|
99
|
+
entries,
|
|
100
|
+
fileCount: rawEntries.length
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
archive.on('warning', (err) => {
|
|
104
|
+
if (err.code === 'ENOENT') {
|
|
105
|
+
console.warn(err);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
reject(err);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
archive.on('error', (err) => {
|
|
112
|
+
reject(err);
|
|
113
|
+
});
|
|
114
|
+
archive.pipe(output);
|
|
115
|
+
if (files && files.length > 0) {
|
|
116
|
+
files.forEach(pattern => {
|
|
117
|
+
const fullPath = path.resolve(sourceDir, pattern);
|
|
118
|
+
if (fs.existsSync(fullPath)) {
|
|
119
|
+
const stat = fs.statSync(fullPath);
|
|
120
|
+
if (stat.isDirectory()) {
|
|
121
|
+
archive.directory(fullPath, pattern);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
archive.file(fullPath, { name: pattern });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
archive.glob(pattern, { cwd: sourceDir, ignore: IGNORED });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
archive.glob('**/*', { cwd: sourceDir, ignore: IGNORED });
|
|
134
|
+
}
|
|
135
|
+
archive.finalize();
|
|
136
|
+
});
|
|
137
|
+
}
|
package/dist/serve.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { randomToken, readLocalEnv, writeLocalEnvValue, getKiteHome } from './home.js';
|
|
6
|
+
function getServerBundlePath() {
|
|
7
|
+
// dist/server/index.js relative to this compiled file (dist/serve.js)
|
|
8
|
+
const bundlePath = path.resolve(new URL('../dist/server/index.js', import.meta.url).pathname);
|
|
9
|
+
return bundlePath;
|
|
10
|
+
}
|
|
11
|
+
function getWebDirPath() {
|
|
12
|
+
return path.resolve(new URL('../dist/web', import.meta.url).pathname);
|
|
13
|
+
}
|
|
14
|
+
function detectRuntime(preferred) {
|
|
15
|
+
const checkRuntime = (name) => {
|
|
16
|
+
const result = spawnSync(name, ['--version'], { stdio: 'pipe' });
|
|
17
|
+
if (result.error)
|
|
18
|
+
return null;
|
|
19
|
+
const ver = result.stdout.toString().trim();
|
|
20
|
+
return { name, version: name === 'bun' ? `v${ver}` : ver };
|
|
21
|
+
};
|
|
22
|
+
if (preferred) {
|
|
23
|
+
const rt = checkRuntime(preferred);
|
|
24
|
+
if (!rt) {
|
|
25
|
+
console.error(chalk.red(`${preferred} is not installed.`));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
return rt;
|
|
29
|
+
}
|
|
30
|
+
// Default: try bun first, fallback to node
|
|
31
|
+
return checkRuntime('bun') || checkRuntime('node') || (() => {
|
|
32
|
+
console.error(chalk.red('Neither Bun nor Node.js is installed.'));
|
|
33
|
+
console.error(chalk.gray('Install Bun from https://bun.sh or Node.js from https://nodejs.org'));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function ensureAdminToken() {
|
|
38
|
+
const localEnv = readLocalEnv();
|
|
39
|
+
if (localEnv.KITE_DEPLOY_TOKEN) {
|
|
40
|
+
// Use existing token from .env.local
|
|
41
|
+
// But we need ADMIN_TOKEN specifically
|
|
42
|
+
}
|
|
43
|
+
// Check if ADMIN_TOKEN already exists in .env.local
|
|
44
|
+
const envPath = path.join(process.cwd(), '.env.local');
|
|
45
|
+
if (fs.existsSync(envPath)) {
|
|
46
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
47
|
+
const match = content.match(/^ADMIN_TOKEN=(.+)$/m);
|
|
48
|
+
if (match) {
|
|
49
|
+
return match[1].replace(/^['"]|['"]$/g, '');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Generate a new admin token
|
|
53
|
+
const token = randomToken('admin');
|
|
54
|
+
writeLocalEnvValue('ADMIN_TOKEN', token);
|
|
55
|
+
return token;
|
|
56
|
+
}
|
|
57
|
+
function buildServerEnv(options, adminToken) {
|
|
58
|
+
const cliPkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url).pathname, 'utf-8'));
|
|
59
|
+
return {
|
|
60
|
+
...process.env,
|
|
61
|
+
PORT: String(options.port),
|
|
62
|
+
HOST: options.host,
|
|
63
|
+
ADMIN_TOKEN: adminToken,
|
|
64
|
+
KITE_WEB_DIR: getWebDirPath(),
|
|
65
|
+
KITE_DB_DIR: getKiteHome(),
|
|
66
|
+
KITE_SERVER_VERSION: cliPkg.version || '1.0.0',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function startForeground(options, env, runtime) {
|
|
70
|
+
const bundlePath = getServerBundlePath();
|
|
71
|
+
if (!fs.existsSync(bundlePath)) {
|
|
72
|
+
console.error(chalk.red(`Server bundle not found at ${bundlePath}`));
|
|
73
|
+
console.error(chalk.gray('Run the build step first: bun run build'));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
console.log(chalk.green('Starting Kite Server...'));
|
|
77
|
+
console.log(chalk.gray(` Runtime: ${runtime.name} ${runtime.version}`));
|
|
78
|
+
console.log(chalk.gray(` Host: ${options.host}`));
|
|
79
|
+
console.log(chalk.gray(` Port: ${options.port}`));
|
|
80
|
+
console.log(chalk.gray(` Web Dir: ${env.KITE_WEB_DIR}`));
|
|
81
|
+
console.log(chalk.gray(` DB Dir: ${env.KITE_DB_DIR}`));
|
|
82
|
+
console.log(chalk.yellow(` Admin Token: ${env.ADMIN_TOKEN}`));
|
|
83
|
+
console.log();
|
|
84
|
+
const args = runtime.name === 'bun' ? ['run', bundlePath] : [bundlePath];
|
|
85
|
+
const child = spawn(runtime.name, args, {
|
|
86
|
+
stdio: 'inherit',
|
|
87
|
+
env,
|
|
88
|
+
cwd: process.cwd(),
|
|
89
|
+
});
|
|
90
|
+
let shuttingDown = false;
|
|
91
|
+
child.on('error', (err) => {
|
|
92
|
+
console.error(chalk.red(`Failed to start server: ${err.message}`));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
});
|
|
95
|
+
child.on('exit', (code, signal) => {
|
|
96
|
+
if (!shuttingDown) {
|
|
97
|
+
shuttingDown = true;
|
|
98
|
+
if (signal) {
|
|
99
|
+
console.log(chalk.gray(`\nServer stopped by signal ${signal}`));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.log(chalk.gray(`\nServer exited with code ${code}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
process.exit(code ?? 0);
|
|
106
|
+
});
|
|
107
|
+
// Graceful shutdown on SIGINT/SIGTERM
|
|
108
|
+
const shutdown = (signal) => {
|
|
109
|
+
if (shuttingDown)
|
|
110
|
+
return;
|
|
111
|
+
shuttingDown = true;
|
|
112
|
+
console.log(chalk.gray(`\nReceived ${signal}, shutting down server...`));
|
|
113
|
+
child.kill('SIGTERM');
|
|
114
|
+
// Force kill after 5s if child doesn't exit
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
console.log(chalk.yellow('Force killing server...'));
|
|
117
|
+
child.kill('SIGKILL');
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}, 5000).unref();
|
|
120
|
+
};
|
|
121
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
122
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
123
|
+
}
|
|
124
|
+
function getPm2Dir() {
|
|
125
|
+
const dir = path.join(getKiteHome(), 'pm2');
|
|
126
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
127
|
+
return dir;
|
|
128
|
+
}
|
|
129
|
+
function startPm2(options, env, runtime) {
|
|
130
|
+
const bundlePath = getServerBundlePath();
|
|
131
|
+
if (!fs.existsSync(bundlePath)) {
|
|
132
|
+
console.error(chalk.red(`Server bundle not found at ${bundlePath}`));
|
|
133
|
+
console.error(chalk.gray('Run the build step first: bun run build'));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
// Check pm2 availability
|
|
137
|
+
const pm2Check = spawnSync('pm2', ['--version'], { stdio: 'pipe' });
|
|
138
|
+
if (pm2Check.error) {
|
|
139
|
+
console.error(chalk.red('pm2 is not installed.'));
|
|
140
|
+
console.error(chalk.gray('Install it with: npm install -g pm2'));
|
|
141
|
+
console.error(chalk.gray('Or run without --pm2 for foreground mode.'));
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
// Stop existing process if running
|
|
145
|
+
spawnSync('pm2', ['delete', 'kite-server'], { stdio: 'pipe' });
|
|
146
|
+
const pm2Dir = getPm2Dir();
|
|
147
|
+
const configPath = path.join(pm2Dir, 'ecosystem.config.cjs');
|
|
148
|
+
const pm2Args = runtime.name === 'bun' ? `run ${bundlePath}` : bundlePath;
|
|
149
|
+
const config = `module.exports = {
|
|
150
|
+
apps: [{
|
|
151
|
+
name: 'kite-server',
|
|
152
|
+
script: '${runtime.name}',
|
|
153
|
+
args: '${pm2Args}',
|
|
154
|
+
cwd: ${JSON.stringify(process.cwd())},
|
|
155
|
+
env: ${JSON.stringify(env, null, 6)},
|
|
156
|
+
max_restarts: 5,
|
|
157
|
+
min_uptime: '10s',
|
|
158
|
+
error_file: ${JSON.stringify(path.join(pm2Dir, 'error.log'))},
|
|
159
|
+
out_file: ${JSON.stringify(path.join(pm2Dir, 'out.log'))},
|
|
160
|
+
merge_logs: true,
|
|
161
|
+
}]
|
|
162
|
+
};
|
|
163
|
+
`;
|
|
164
|
+
fs.writeFileSync(configPath, config);
|
|
165
|
+
const result = spawnSync('pm2', ['start', configPath], { stdio: 'inherit' });
|
|
166
|
+
if (result.error) {
|
|
167
|
+
console.error(chalk.red(`Failed to start pm2: ${result.error.message}`));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
console.log();
|
|
171
|
+
console.log(chalk.green('Kite Server started with pm2!'));
|
|
172
|
+
console.log(chalk.gray(` Name: kite-server`));
|
|
173
|
+
console.log(chalk.gray(` Runtime: ${runtime.name} ${runtime.version}`));
|
|
174
|
+
console.log(chalk.gray(` Host: ${options.host}`));
|
|
175
|
+
console.log(chalk.gray(` Port: ${options.port}`));
|
|
176
|
+
console.log(chalk.gray(` Web Dir: ${env.KITE_WEB_DIR}`));
|
|
177
|
+
console.log(chalk.gray(` DB Dir: ${env.KITE_DB_DIR}`));
|
|
178
|
+
console.log(chalk.yellow(` Admin Token: ${env.ADMIN_TOKEN}`));
|
|
179
|
+
console.log();
|
|
180
|
+
console.log(chalk.gray('Commands:'));
|
|
181
|
+
console.log(chalk.gray(' pm2 logs kite-server # View logs'));
|
|
182
|
+
console.log(chalk.gray(' pm2 status # Check status'));
|
|
183
|
+
console.log(chalk.gray(' kite serve --pm2 stop # Stop server'));
|
|
184
|
+
}
|
|
185
|
+
function stopPm2() {
|
|
186
|
+
const result = spawnSync('pm2', ['delete', 'kite-server'], { stdio: 'inherit' });
|
|
187
|
+
if (result.error) {
|
|
188
|
+
console.error(chalk.red(`Failed to stop pm2: ${result.error.message}`));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
console.log(chalk.green('Kite Server stopped.'));
|
|
192
|
+
}
|
|
193
|
+
export async function startServe(options) {
|
|
194
|
+
// Handle pm2 stop
|
|
195
|
+
if (options.pm2 && options.pm2Action === 'stop') {
|
|
196
|
+
stopPm2();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const runtime = detectRuntime(options.runtime);
|
|
200
|
+
const adminToken = ensureAdminToken();
|
|
201
|
+
const env = buildServerEnv(options, adminToken);
|
|
202
|
+
if (options.pm2) {
|
|
203
|
+
startPm2(options, env, runtime);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
startForeground(options, env, runtime);
|
|
207
|
+
}
|
|
208
|
+
}
|