@kitecd/cli 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +271 -9
- package/dist/ops.js +338 -0
- package/dist/pack.js +15 -5
- package/dist/serve.js +27 -1
- package/dist/server/index.js +14562 -5621
- package/dist/upload.js +11 -3
- package/dist/verify.js +161 -0
- package/dist/web/assets/AuditLog-BFmJfgzL.js +1 -0
- package/dist/web/assets/ConfirmDialog-CJ8lJeUc.js +1 -0
- package/dist/web/assets/ConfirmDialog-D8avT8FJ.css +1 -0
- package/dist/web/assets/Dashboard-BTliTkq1.js +1 -0
- package/dist/web/assets/DefaultLayout-BG_y85yG.js +1 -0
- package/dist/web/assets/DefaultLayout-CZENO67n.css +1 -0
- package/dist/web/assets/FileExplorer-Bf32_MMS.js +1 -0
- package/dist/web/assets/LogBoard-0so-XiW3.css +1 -0
- package/dist/web/assets/LogBoard-CDz4n0DY.js +6 -0
- package/dist/web/assets/Login-C3zfObzP.js +1 -0
- package/dist/web/assets/Migration-BaKlcCkB.js +1 -0
- package/dist/web/assets/ProjectDetail-BL_D0OJY.js +1 -0
- package/dist/web/assets/ProjectDetail-ia6-z1kZ.css +1 -0
- package/dist/web/assets/ProjectList-7AnhOS7h.css +1 -0
- package/dist/web/assets/ProjectList-C4KuZEq4.js +1 -0
- package/dist/web/assets/Settings-CzWG9312.js +1 -0
- package/dist/web/assets/Storage-BCao_e7Y.js +1 -0
- package/dist/web/assets/Storage-DNadqpUy.css +1 -0
- package/dist/web/assets/{activity-DItEGOtI.js → activity-Ba-YPfmn.js} +1 -1
- package/dist/web/assets/archive-CJ5gDBKY.js +1 -0
- package/dist/web/assets/arrow-left-WEOMLXIi.js +1 -0
- package/dist/web/assets/chevron-right-DLiDJVJl.js +1 -0
- package/dist/web/assets/{circle-alert-Bfrn_ovD.js → circle-alert-DQzM-U4P.js} +1 -1
- package/dist/web/assets/clock-IwxBKgP4.js +1 -0
- package/dist/web/assets/constants-Ch47JPs3.js +1 -0
- package/dist/web/assets/copy-C1rADgcQ.js +1 -0
- package/dist/web/assets/createLucideIcon-CE-ry2oA.js +1 -0
- package/dist/web/assets/database-CI6acTSY.js +1 -0
- package/dist/web/assets/eye-Cas8HsmK.js +1 -0
- package/dist/web/assets/eye-off-4PD31MPV.js +1 -0
- package/dist/web/assets/file-text-C6nzo1us.js +1 -0
- package/dist/web/assets/folder-D9pvA6oI.js +1 -0
- package/dist/web/assets/folder-open-CGeIri0U.js +1 -0
- package/dist/web/assets/hard-drive-CGWwV4Ei.js +1 -0
- package/dist/web/assets/house-8ZvvlaEh.js +1 -0
- package/dist/web/assets/index-BFE6PEIL.js +2 -0
- package/dist/web/assets/index-XrzJwjrk.css +1 -0
- package/dist/web/assets/loader-circle-uiC7GaCG.js +1 -0
- package/dist/web/assets/plus-CfMi1Jv1.js +1 -0
- package/dist/web/assets/refresh-cw-0yb8DZDc.js +1 -0
- package/dist/web/assets/rotate-ccw-YevaWXw9.js +1 -0
- package/dist/web/assets/save-BuzCP3T1.js +1 -0
- package/dist/web/assets/scroll-text-BIYMFNN5.js +1 -0
- package/dist/web/assets/{server-C33taHNn.js → server-B31hj0g7.js} +1 -1
- package/dist/web/assets/{square-terminal-C8toRwjx.js → square-terminal-DT6aoXm0.js} +1 -1
- package/dist/web/assets/sun-BaEbKNDV.js +1 -0
- package/dist/web/assets/trash-2-BWqtqkeW.js +1 -0
- package/dist/web/index.html +3 -3
- package/package.json +2 -2
- package/dist/web/assets/Dashboard-pjIWWLub.js +0 -1
- package/dist/web/assets/DefaultLayout-Bj8fPWym.css +0 -1
- package/dist/web/assets/DefaultLayout-DelfwTTT.js +0 -1
- package/dist/web/assets/FileExplorer-xY5ejhhN.js +0 -1
- package/dist/web/assets/LogBoard-DzW-cEqH.css +0 -1
- package/dist/web/assets/LogBoard-tT61QjOx.js +0 -6
- package/dist/web/assets/Login-B4C149oC.js +0 -1
- package/dist/web/assets/ProjectDetail-Z8cZoqr5.js +0 -1
- package/dist/web/assets/ProjectList-9rbMuJeY.js +0 -1
- package/dist/web/assets/Settings-CtCNDUXY.js +0 -1
- package/dist/web/assets/clock-BPXGSCIV.js +0 -1
- package/dist/web/assets/constants-C4Zrkm2g.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-C615tnMi.js +0 -2
- package/dist/web/assets/index-C9LiRc31.css +0 -1
- 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/import.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import AdmZip from 'adm-zip';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { createClient } from '@libsql/client';
|
|
7
|
+
import { ensureKiteHome, readGlobalConfig, writeGlobalConfig } from './home.js';
|
|
8
|
+
const DEFAULT_LOCAL_SERVER_URL = 'http://127.0.0.1:5431';
|
|
9
|
+
const SUPPORTED_SCHEMA = 1;
|
|
10
|
+
const PROJECT_COLUMNS = [
|
|
11
|
+
'id', 'name', 'description', 'deploy_path', 'token',
|
|
12
|
+
'pre_deploy_script', 'post_deploy_script', 'env', 'status',
|
|
13
|
+
'created_at', 'updated_at',
|
|
14
|
+
];
|
|
15
|
+
const DEPLOYMENT_COLUMNS = [
|
|
16
|
+
'id', 'project_id', 'project_name', 'status', 'trigger_source',
|
|
17
|
+
'duration', 'output', 'start_time', 'end_time',
|
|
18
|
+
];
|
|
19
|
+
const CREATE_PROJECTS = `
|
|
20
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
21
|
+
id TEXT PRIMARY KEY,
|
|
22
|
+
name TEXT NOT NULL,
|
|
23
|
+
description TEXT,
|
|
24
|
+
deploy_path TEXT NOT NULL,
|
|
25
|
+
token TEXT NOT NULL UNIQUE,
|
|
26
|
+
pre_deploy_script TEXT,
|
|
27
|
+
post_deploy_script TEXT,
|
|
28
|
+
env TEXT,
|
|
29
|
+
status TEXT DEFAULT 'idle',
|
|
30
|
+
created_at TEXT NOT NULL,
|
|
31
|
+
updated_at TEXT NOT NULL
|
|
32
|
+
);
|
|
33
|
+
`;
|
|
34
|
+
const CREATE_SETTINGS = `
|
|
35
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
36
|
+
key TEXT PRIMARY KEY,
|
|
37
|
+
value TEXT NOT NULL
|
|
38
|
+
);
|
|
39
|
+
`;
|
|
40
|
+
const CREATE_DEPLOYMENTS = `
|
|
41
|
+
CREATE TABLE IF NOT EXISTS deployments (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
44
|
+
project_name TEXT NOT NULL,
|
|
45
|
+
status TEXT NOT NULL,
|
|
46
|
+
trigger_source TEXT NOT NULL,
|
|
47
|
+
duration TEXT,
|
|
48
|
+
output TEXT,
|
|
49
|
+
start_time TEXT NOT NULL,
|
|
50
|
+
end_time TEXT
|
|
51
|
+
);
|
|
52
|
+
`;
|
|
53
|
+
const ensureSchema = async (client) => {
|
|
54
|
+
await client.execute(CREATE_PROJECTS);
|
|
55
|
+
try {
|
|
56
|
+
await client.execute(`ALTER TABLE projects ADD COLUMN env TEXT`);
|
|
57
|
+
}
|
|
58
|
+
catch { /* exists */ }
|
|
59
|
+
await client.execute(CREATE_SETTINGS);
|
|
60
|
+
await client.execute(CREATE_DEPLOYMENTS);
|
|
61
|
+
};
|
|
62
|
+
const readJsonEntry = (zip, name) => {
|
|
63
|
+
const entry = zip.getEntry(name);
|
|
64
|
+
if (!entry)
|
|
65
|
+
return null;
|
|
66
|
+
return JSON.parse(entry.getData().toString('utf-8'));
|
|
67
|
+
};
|
|
68
|
+
export async function runImport(file, options) {
|
|
69
|
+
const strategy = options.strategy || 'skip-existing';
|
|
70
|
+
if (!['merge', 'overwrite', 'skip-existing'].includes(strategy)) {
|
|
71
|
+
console.error(chalk.red(`Invalid strategy: ${strategy}. Use merge | overwrite | skip-existing.`));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
if (strategy === 'overwrite' && !options.yes && !options.dryRun) {
|
|
75
|
+
console.error(chalk.red('Strategy "overwrite" requires --yes to confirm destructive replacement.'));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const absFile = path.resolve(process.cwd(), file);
|
|
79
|
+
if (!fs.existsSync(absFile)) {
|
|
80
|
+
console.error(chalk.red(`Import file not found: ${absFile}`));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const home = ensureKiteHome();
|
|
84
|
+
const dbPath = path.join(home, 'kite.db');
|
|
85
|
+
const spinner = ora('Reading import package...').start();
|
|
86
|
+
const zip = new AdmZip(absFile);
|
|
87
|
+
const manifest = readJsonEntry(zip, 'kite-export/manifest.json');
|
|
88
|
+
if (!manifest) {
|
|
89
|
+
spinner.fail();
|
|
90
|
+
console.error(chalk.red('Invalid import package: kite-export/manifest.json not found.'));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
if (manifest.schemaVersion !== SUPPORTED_SCHEMA) {
|
|
94
|
+
spinner.fail();
|
|
95
|
+
console.error(chalk.red(`Unsupported schemaVersion ${manifest.schemaVersion} (CLI supports ${SUPPORTED_SCHEMA}). Upgrade CLI and retry.`));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
spinner.succeed(chalk.green(`Loaded export package (exported ${manifest.exportedAt}, kite ${manifest.kiteVersion})`));
|
|
99
|
+
const projects = readJsonEntry(zip, 'kite-export/projects.json') || [];
|
|
100
|
+
const settings = readJsonEntry(zip, 'kite-export/settings.json') || [];
|
|
101
|
+
const deployments = manifest.includes.deployments
|
|
102
|
+
? (readJsonEntry(zip, 'kite-export/deployments.json') || [])
|
|
103
|
+
: [];
|
|
104
|
+
const client = createClient({ url: `file:${dbPath}` });
|
|
105
|
+
try {
|
|
106
|
+
await ensureSchema(client);
|
|
107
|
+
// 预扫现状用于摘要 + skip-existing 判断
|
|
108
|
+
const existingProjectIds = new Set();
|
|
109
|
+
const existingTokens = new Set();
|
|
110
|
+
for (const row of (await client.execute(`SELECT id, token FROM projects`)).rows) {
|
|
111
|
+
existingProjectIds.add(String(row.id));
|
|
112
|
+
existingTokens.add(String(row.token));
|
|
113
|
+
}
|
|
114
|
+
const existingSettingKeys = new Set();
|
|
115
|
+
for (const row of (await client.execute(`SELECT key FROM settings`)).rows) {
|
|
116
|
+
existingSettingKeys.add(String(row.key));
|
|
117
|
+
}
|
|
118
|
+
const existingDeploymentIds = new Set();
|
|
119
|
+
for (const row of (await client.execute(`SELECT id FROM deployments`)).rows) {
|
|
120
|
+
existingDeploymentIds.add(String(row.id));
|
|
121
|
+
}
|
|
122
|
+
const projectCounts = { added: 0, updated: 0, skipped: 0 };
|
|
123
|
+
const settingCounts = { added: 0, updated: 0, skipped: 0 };
|
|
124
|
+
const deploymentCounts = { added: 0, updated: 0, skipped: 0 };
|
|
125
|
+
const projectsToWrite = [];
|
|
126
|
+
const settingsToWrite = [];
|
|
127
|
+
const deploymentsToWrite = [];
|
|
128
|
+
for (const p of projects) {
|
|
129
|
+
const id = String(p.id);
|
|
130
|
+
const token = String(p.token);
|
|
131
|
+
const idExists = existingProjectIds.has(id);
|
|
132
|
+
const tokenConflict = !idExists && existingTokens.has(token);
|
|
133
|
+
if (idExists) {
|
|
134
|
+
if (strategy === 'overwrite') {
|
|
135
|
+
projectsToWrite.push(p);
|
|
136
|
+
projectCounts.updated++;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
projectCounts.skipped++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (tokenConflict) {
|
|
143
|
+
// token unique constraint 会冲突,跳过避免报错
|
|
144
|
+
projectCounts.skipped++;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
projectsToWrite.push(p);
|
|
148
|
+
projectCounts.added++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const s of settings) {
|
|
152
|
+
const key = String(s.key);
|
|
153
|
+
const exists = existingSettingKeys.has(key);
|
|
154
|
+
if (exists) {
|
|
155
|
+
if (strategy === 'overwrite') {
|
|
156
|
+
settingsToWrite.push(s);
|
|
157
|
+
settingCounts.updated++;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
settingCounts.skipped++;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
settingsToWrite.push(s);
|
|
165
|
+
settingCounts.added++;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const d of deployments) {
|
|
169
|
+
const id = String(d.id);
|
|
170
|
+
const exists = existingDeploymentIds.has(id);
|
|
171
|
+
if (exists) {
|
|
172
|
+
if (strategy === 'overwrite') {
|
|
173
|
+
deploymentsToWrite.push(d);
|
|
174
|
+
deploymentCounts.updated++;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
deploymentCounts.skipped++;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
deploymentsToWrite.push(d);
|
|
182
|
+
deploymentCounts.added++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// 摘要
|
|
186
|
+
console.log(chalk.bold('\nImport summary:'));
|
|
187
|
+
console.log(` Strategy: ${strategy}`);
|
|
188
|
+
console.log(` DB path: ${dbPath}`);
|
|
189
|
+
console.log(` Projects: + ${projectCounts.added} new, ~ ${projectCounts.updated} update, - ${projectCounts.skipped} skip`);
|
|
190
|
+
console.log(` Settings: + ${settingCounts.added} new, ~ ${settingCounts.updated} update, - ${settingCounts.skipped} skip`);
|
|
191
|
+
if (manifest.includes.deployments) {
|
|
192
|
+
console.log(` Deployments: + ${deploymentCounts.added} new, ~ ${deploymentCounts.updated} update, - ${deploymentCounts.skipped} skip`);
|
|
193
|
+
}
|
|
194
|
+
if (manifest.includes.artifacts) {
|
|
195
|
+
const restoreCount = options.restoreArtifacts ? manifest.artifacts.filter(a => a.archive).length : 0;
|
|
196
|
+
console.log(` Artifacts: ${options.restoreArtifacts ? `restore ${restoreCount}` : 'available, not restored (pass --restore-artifacts)'}`);
|
|
197
|
+
}
|
|
198
|
+
console.log(` Global cfg: will write projectToken for ${projectsToWrite.length} projects to ~/.kite/config.json`);
|
|
199
|
+
if (options.dryRun) {
|
|
200
|
+
console.log(chalk.gray('\nDry-run complete. No changes written.'));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const writeSpinner = ora('Writing to database...').start();
|
|
204
|
+
const placeholders = (cols) => cols.map(() => '?').join(', ');
|
|
205
|
+
const upsertProject = (cols) => `INSERT INTO projects (${cols.join(', ')}) VALUES (${placeholders(cols)}) ` +
|
|
206
|
+
`ON CONFLICT(id) DO UPDATE SET ${cols.filter(c => c !== 'id').map(c => `${c} = excluded.${c}`).join(', ')}`;
|
|
207
|
+
const upsertSetting = `INSERT INTO settings (key, value) VALUES (?, ?) ` +
|
|
208
|
+
`ON CONFLICT(key) DO UPDATE SET value = excluded.value`;
|
|
209
|
+
const upsertDeployment = (cols) => `INSERT INTO deployments (${cols.join(', ')}) VALUES (${placeholders(cols)}) ` +
|
|
210
|
+
`ON CONFLICT(id) DO UPDATE SET ${cols.filter(c => c !== 'id').map(c => `${c} = excluded.${c}`).join(', ')}`;
|
|
211
|
+
const tx = await client.transaction('write');
|
|
212
|
+
try {
|
|
213
|
+
for (const p of projectsToWrite) {
|
|
214
|
+
const args = PROJECT_COLUMNS.map(c => (p[c] ?? null));
|
|
215
|
+
await tx.execute({ sql: upsertProject(PROJECT_COLUMNS), args });
|
|
216
|
+
}
|
|
217
|
+
for (const s of settingsToWrite) {
|
|
218
|
+
await tx.execute({ sql: upsertSetting, args: [String(s.key), String(s.value ?? '')] });
|
|
219
|
+
}
|
|
220
|
+
for (const d of deploymentsToWrite) {
|
|
221
|
+
const args = DEPLOYMENT_COLUMNS.map(c => (d[c] ?? null));
|
|
222
|
+
await tx.execute({ sql: upsertDeployment(DEPLOYMENT_COLUMNS), args });
|
|
223
|
+
}
|
|
224
|
+
await tx.commit();
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
await tx.rollback();
|
|
228
|
+
writeSpinner.fail();
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
writeSpinner.succeed(chalk.green('Database updated.'));
|
|
232
|
+
// 写回 CLI 全局 config,方便目标机器直接 kite push
|
|
233
|
+
try {
|
|
234
|
+
const globalConfig = readGlobalConfig();
|
|
235
|
+
const nextProjectToken = { ...(globalConfig.projectToken || {}) };
|
|
236
|
+
let tokenAdded = 0;
|
|
237
|
+
let tokenUpdated = 0;
|
|
238
|
+
let tokenSkipped = 0;
|
|
239
|
+
for (const p of projectsToWrite) {
|
|
240
|
+
const projectId = String(p.id);
|
|
241
|
+
const token = p.token != null ? String(p.token) : '';
|
|
242
|
+
if (!projectId || !token)
|
|
243
|
+
continue;
|
|
244
|
+
const existing = nextProjectToken[projectId];
|
|
245
|
+
if (existing === undefined) {
|
|
246
|
+
nextProjectToken[projectId] = token;
|
|
247
|
+
tokenAdded++;
|
|
248
|
+
}
|
|
249
|
+
else if (strategy === 'overwrite') {
|
|
250
|
+
if (existing !== token) {
|
|
251
|
+
nextProjectToken[projectId] = token;
|
|
252
|
+
tokenUpdated++;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
tokenSkipped++;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const serverUrlMissing = !globalConfig.serverUrl;
|
|
260
|
+
const nextServerUrl = serverUrlMissing ? DEFAULT_LOCAL_SERVER_URL : globalConfig.serverUrl;
|
|
261
|
+
writeGlobalConfig({
|
|
262
|
+
...globalConfig,
|
|
263
|
+
serverUrl: nextServerUrl,
|
|
264
|
+
projectToken: nextProjectToken,
|
|
265
|
+
});
|
|
266
|
+
console.log(chalk.green('Global config updated (~/.kite/config.json).'));
|
|
267
|
+
console.log(chalk.gray(` projectToken: + ${tokenAdded} new, ~ ${tokenUpdated} update, - ${tokenSkipped} skip`));
|
|
268
|
+
if (serverUrlMissing) {
|
|
269
|
+
console.log(chalk.gray(` serverUrl: set to ${DEFAULT_LOCAL_SERVER_URL} (override via \`kite config:set serverUrl <url>\`)`));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
console.log(chalk.yellow(` ! Failed to update global config: ${err.message}`));
|
|
274
|
+
}
|
|
275
|
+
if (manifest.includes.artifacts && options.restoreArtifacts) {
|
|
276
|
+
// 重新读取写入后 projects 表,获取最新 deploy_path(可能用户保留了已有项目的路径)
|
|
277
|
+
const deployPathMap = new Map();
|
|
278
|
+
for (const row of (await client.execute(`SELECT id, deploy_path FROM projects`)).rows) {
|
|
279
|
+
deployPathMap.set(String(row.id), String(row.deploy_path));
|
|
280
|
+
}
|
|
281
|
+
const artifactSpinner = ora('Restoring artifacts...').start();
|
|
282
|
+
let okCount = 0;
|
|
283
|
+
let warnCount = 0;
|
|
284
|
+
for (const a of manifest.artifacts) {
|
|
285
|
+
if (!a.archive)
|
|
286
|
+
continue;
|
|
287
|
+
const target = deployPathMap.get(a.projectId);
|
|
288
|
+
if (!target) {
|
|
289
|
+
console.log(chalk.yellow(` ! ${a.projectId}: project not in DB, skip restore`));
|
|
290
|
+
warnCount++;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const entry = zip.getEntry(`kite-export/${a.archive}`);
|
|
294
|
+
if (!entry) {
|
|
295
|
+
console.log(chalk.yellow(` ! ${a.projectId}: archive entry missing`));
|
|
296
|
+
warnCount++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
fs.mkdirSync(target, { recursive: true });
|
|
301
|
+
const tmpZipPath = path.join(home, 'tmp', `restore-${a.projectId}-${Date.now()}.zip`);
|
|
302
|
+
fs.mkdirSync(path.dirname(tmpZipPath), { recursive: true });
|
|
303
|
+
fs.writeFileSync(tmpZipPath, entry.getData());
|
|
304
|
+
const innerZip = new AdmZip(tmpZipPath);
|
|
305
|
+
// 路径穿越校验
|
|
306
|
+
for (const e of innerZip.getEntries()) {
|
|
307
|
+
const dest = path.resolve(target, e.entryName);
|
|
308
|
+
if (!dest.startsWith(path.resolve(target) + path.sep) && dest !== path.resolve(target)) {
|
|
309
|
+
throw new Error(`Refusing to extract entry outside target: ${e.entryName}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
innerZip.extractAllTo(target, true);
|
|
313
|
+
fs.unlinkSync(tmpZipPath);
|
|
314
|
+
okCount++;
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
console.log(chalk.yellow(` ! ${a.projectId}: restore failed - ${err.message}`));
|
|
318
|
+
warnCount++;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
artifactSpinner.succeed(chalk.green(`Artifacts restored: ${okCount} ok, ${warnCount} warnings`));
|
|
322
|
+
}
|
|
323
|
+
console.log(chalk.green('\nImport completed.'));
|
|
324
|
+
if (!manifest.includes.artifacts || !options.restoreArtifacts) {
|
|
325
|
+
console.log(chalk.gray('Tip: start `kite serve` to verify projects in the dashboard.'));
|
|
326
|
+
}
|
|
327
|
+
console.log(chalk.gray('Tip: `kite push` from your project directory now uses the restored token automatically.'));
|
|
328
|
+
console.log(chalk.gray('Tip: run `kite verify` to self-check migration integrity (db, deploy paths, tokens).'));
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
client.close();
|
|
332
|
+
}
|
|
333
|
+
}
|