@adep/cli 0.1.4 → 0.1.5

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.
Files changed (2) hide show
  1. package/dist/index.js +315 -34
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -175,7 +175,8 @@ async function login(paths, input) {
175
175
  if (response.status !== 200) {
176
176
  throw new CliError("LOGIN_FAILED", "\u767B\u5F55\u5931\u8D25\uFF1A\u90AE\u7BB1\u6216\u5BC6\u7801\u4E0D\u6B63\u786E\uFF08\u6216\u8BE5\u8D26\u53F7\u672A\u6CE8\u518C\uFF09");
177
177
  }
178
- const token = sessionCookieOf(response.headers.getSetCookie());
178
+ const cookie = sessionCookieOf(response.headers.getSetCookie());
179
+ const token = cookie.slice(cookie.indexOf("=") + 1);
179
180
  await saveCredentials(paths, {
180
181
  server,
181
182
  email: input.email,
@@ -2682,7 +2683,7 @@ function createLocalStorageDriver(options) {
2682
2683
  }
2683
2684
  const root = bucketDir(projectId);
2684
2685
  const metas = [];
2685
- const walk = async (dir, relative4) => {
2686
+ const walk = async (dir, relative6) => {
2686
2687
  let entries;
2687
2688
  try {
2688
2689
  entries = await readdir2(dir, { withFileTypes: true });
@@ -2693,7 +2694,7 @@ function createLocalStorageDriver(options) {
2693
2694
  for (const entry of entries) {
2694
2695
  if (entry.name.endsWith(META_SUFFIX)) continue;
2695
2696
  const full = join4(dir, entry.name);
2696
- const rel = relative4 === "" ? entry.name : `${relative4}/${entry.name}`;
2697
+ const rel = relative6 === "" ? entry.name : `${relative6}/${entry.name}`;
2697
2698
  if (entry.isFile()) {
2698
2699
  if (prefix !== void 0 && !rel.startsWith(prefix)) continue;
2699
2700
  tasks.push(
@@ -5246,11 +5247,12 @@ var init_client = __esm({
5246
5247
  var deploy_exports = {};
5247
5248
  __export(deploy_exports, {
5248
5249
  collectEntries: () => collectEntries,
5250
+ collectShared: () => collectShared,
5249
5251
  deploy: () => deploy
5250
5252
  });
5251
5253
  import { createHash as createHash2 } from "node:crypto";
5252
5254
  import { readdir as readdir5, readFile as readFile9 } from "node:fs/promises";
5253
- import { join as join12, resolve as resolve6, basename as basename3 } from "node:path";
5255
+ import { join as join12, resolve as resolve6, basename as basename3, relative as relative3, sep } from "node:path";
5254
5256
  function functionUrlBase(prefix) {
5255
5257
  const raw = (prefix ?? "/api").trim();
5256
5258
  const normalized = raw === "/" ? "/" : raw.replace(/\/+$/, "");
@@ -5273,6 +5275,31 @@ async function collectEntries(functionsDir) {
5273
5275
  function sha256Hex(content) {
5274
5276
  return createHash2("sha256").update(content).digest("hex");
5275
5277
  }
5278
+ async function collectShared(functionsDir) {
5279
+ const sharedDir = join12(functionsDir, "_shared");
5280
+ const files = {};
5281
+ const walk = async (dir) => {
5282
+ let entries;
5283
+ try {
5284
+ entries = await readdir5(dir, { withFileTypes: true });
5285
+ } catch {
5286
+ return;
5287
+ }
5288
+ for (const entry of entries) {
5289
+ if (entry.isDirectory()) {
5290
+ if (entry.name === "__tests__" || entry.name === "node_modules") continue;
5291
+ await walk(join12(dir, entry.name));
5292
+ continue;
5293
+ }
5294
+ if (!entry.name.endsWith(".ts") || entry.name.endsWith(".test.ts")) continue;
5295
+ const full = join12(dir, entry.name);
5296
+ const rel = relative3(sharedDir, full).split(sep).join("/");
5297
+ files[`_shared/${rel}`] = await readFile9(full, "utf8");
5298
+ }
5299
+ };
5300
+ await walk(sharedDir);
5301
+ return files;
5302
+ }
5276
5303
  async function deploy(paths, options) {
5277
5304
  await requireNetwork("adep deploy");
5278
5305
  const log = options.silent === true ? () => void 0 : options.log ?? ((line) => process.stdout.write(`${line}
@@ -5286,14 +5313,25 @@ async function deploy(paths, options) {
5286
5313
  if (Object.keys(local).length === 0) {
5287
5314
  throw new CliError("NO_FUNCTIONS", `${functionsDir} \u4E0B\u6CA1\u6709\u51FD\u6570\u6587\u4EF6`);
5288
5315
  }
5316
+ const shared = await collectShared(functionsDir);
5317
+ const filesFor = (name) => ({
5318
+ "index.ts": local[name],
5319
+ ...shared
5320
+ });
5321
+ const hashFiles = (name) => {
5322
+ const map = filesFor(name);
5323
+ const hashed = {};
5324
+ for (const [path, content] of Object.entries(map)) hashed[path] = sha256Hex(content);
5325
+ return hashed;
5326
+ };
5289
5327
  const remote = await client.request(
5290
5328
  "/api/v1/deploy/diff",
5291
5329
  {
5292
5330
  body: {
5293
5331
  slug,
5294
- functions: Object.entries(local).map(([name, content]) => ({
5332
+ functions: Object.entries(local).map(([name]) => ({
5295
5333
  name,
5296
- files: { "index.ts": sha256Hex(content) }
5334
+ files: hashFiles(name)
5297
5335
  }))
5298
5336
  }
5299
5337
  },
@@ -5304,7 +5342,6 @@ async function deploy(paths, options) {
5304
5342
  const functionUrl = (name) => `${baseUrl}${prefixBase}/${name}`;
5305
5343
  const deployed = [];
5306
5344
  for (const item of remote.functions) {
5307
- const content = local[item.name];
5308
5345
  if (item.action === "none") {
5309
5346
  deployed.push({
5310
5347
  name: item.name,
@@ -5320,14 +5357,14 @@ async function deploy(paths, options) {
5320
5357
  const created = await client.request(
5321
5358
  `/api/v1/projects/${remote.projectId}/functions`,
5322
5359
  {
5323
- body: { name: item.name, files: { "index.ts": content } }
5360
+ body: { name: item.name, files: filesFor(item.name) }
5324
5361
  }
5325
5362
  );
5326
5363
  functionId = created.id;
5327
5364
  } else {
5328
5365
  await client.request(`/api/v1/projects/${remote.projectId}/functions/${item.name}`, {
5329
5366
  method: "PATCH",
5330
- body: { files: { "index.ts": content } }
5367
+ body: { files: filesFor(item.name) }
5331
5368
  });
5332
5369
  }
5333
5370
  const published = await client.request(
@@ -5359,6 +5396,120 @@ var init_deploy = __esm({
5359
5396
  }
5360
5397
  });
5361
5398
 
5399
+ // packages/cli/src/frontend.ts
5400
+ var frontend_exports = {};
5401
+ __export(frontend_exports, {
5402
+ frontendSync: () => frontendSync
5403
+ });
5404
+ import { readdir as readdir6, readFile as readFile10, stat as stat7 } from "node:fs/promises";
5405
+ import { join as join13, relative as relative4, sep as sep2 } from "node:path";
5406
+ async function resolveProjectId(client, cwd, slug) {
5407
+ const resolvedSlug = await resolveSlug(cwd, slug);
5408
+ const listed = await client.request("/api/v1/projects", {}, "PROJECT_LIST_FAILED");
5409
+ const match = listed.projects.find((project2) => project2.slug === resolvedSlug);
5410
+ if (match === void 0) {
5411
+ throw new CliError(
5412
+ "PROJECT_NOT_FOUND",
5413
+ `\u5E73\u53F0\u9879\u76EE "${resolvedSlug}" \u4E0D\u5B58\u5728\u6216\u4F60\u6CA1\u6709\u8BBF\u95EE\u6743\u9650\uFF1A\u5148\u6267\u884C adep projects create ${resolvedSlug}`
5414
+ );
5415
+ }
5416
+ return { id: match.id, slug: match.slug };
5417
+ }
5418
+ async function collectFiles(root, dir) {
5419
+ const files = /* @__PURE__ */ new Map();
5420
+ const skipped = [];
5421
+ const entries = await readdir6(dir, { withFileTypes: true });
5422
+ for (const entry of entries) {
5423
+ if (entry.isDirectory()) {
5424
+ if (IGNORED_DIRS.has(entry.name)) continue;
5425
+ const nested = await collectFiles(root, join13(dir, entry.name));
5426
+ for (const [path, content] of nested.files) files.set(path, content);
5427
+ skipped.push(...nested.skipped);
5428
+ continue;
5429
+ }
5430
+ const full = join13(dir, entry.name);
5431
+ const rel = relative4(root, full).split(sep2).join("/");
5432
+ const info = await stat7(full);
5433
+ if (info.size > 1024 * 1024) {
5434
+ skipped.push(rel);
5435
+ continue;
5436
+ }
5437
+ try {
5438
+ const content = await readFile10(full, "utf8");
5439
+ if (content.includes("\0")) {
5440
+ skipped.push(rel);
5441
+ continue;
5442
+ }
5443
+ files.set(rel, content);
5444
+ } catch {
5445
+ skipped.push(rel);
5446
+ }
5447
+ }
5448
+ return { files, skipped };
5449
+ }
5450
+ async function frontendSync(paths, options) {
5451
+ const log = options.silent === true ? () => void 0 : options.log ?? ((line) => process.stdout.write(`${line}
5452
+ `));
5453
+ const webDir = options.dir ?? join13(options.cwd, "web");
5454
+ const rootStat = await stat7(webDir).catch(() => null);
5455
+ if (rootStat === null || !rootStat.isDirectory()) {
5456
+ throw new CliError(
5457
+ "FRONTEND_DIR_MISSING",
5458
+ `\u672C\u5730\u524D\u7AEF\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${webDir}\uFF08adep frontend sync \u671F\u671B ${webDir}/ \u4E0B\u662F Vite \u5DE5\u7A0B\uFF09`
5459
+ );
5460
+ }
5461
+ const { files, skipped } = await collectFiles(webDir, webDir);
5462
+ if (files.size === 0) {
5463
+ throw new CliError("FRONTEND_EMPTY", `\u672C\u5730\u524D\u7AEF\u76EE\u5F55\u4E3A\u7A7A\u6216\u65E0\u53EF\u4E0A\u4F20\u6587\u672C\u6587\u4EF6\uFF1A${webDir}`);
5464
+ }
5465
+ if (files.size > MAX_FILES) {
5466
+ throw new CliError("FRONTEND_TOO_MANY_FILES", `\u6587\u4EF6\u6570 ${files.size} \u8D85\u8FC7\u4E0A\u9650\uFF08${MAX_FILES}\uFF09`);
5467
+ }
5468
+ let totalBytes = 0;
5469
+ const drafts = {};
5470
+ for (const [rel, content] of files) {
5471
+ const path = `web/${rel}`;
5472
+ if (path.includes("..") || path.includes("\\") || !FILE_PATH_PATTERN.test(rel)) {
5473
+ throw new CliError(
5474
+ "FRONTEND_INVALID_PATH",
5475
+ `\u975E\u6CD5\u6587\u4EF6\u8DEF\u5F84 "${rel}"\uFF1A\u8349\u7A3F\u4EC5\u652F\u6301\u5B57\u6BCD\u6570\u5B57._/-\uFF08\u8BF7\u68C0\u67E5\u6587\u4EF6\u540D\uFF09`
5476
+ );
5477
+ }
5478
+ totalBytes += new TextEncoder().encode(content).length;
5479
+ drafts[path] = content;
5480
+ }
5481
+ if (totalBytes > MAX_TOTAL_SOURCE_BYTES2) {
5482
+ throw new CliError(
5483
+ "FRONTEND_TOO_LARGE",
5484
+ `\u6E90\u7801\u603B\u91CF ${totalBytes} \u5B57\u8282\u8D85\u8FC7\u4E0A\u9650\uFF08${MAX_TOTAL_SOURCE_BYTES2} \u5B57\u8282\uFF09`
5485
+ );
5486
+ }
5487
+ const client = await createClient(paths);
5488
+ const project2 = await resolveProjectId(client, options.cwd, options.slug);
5489
+ log(`[adep] \u540C\u6B65\u672C\u5730 web/ \u8349\u7A3F\u5230\u5E73\u53F0\uFF08\u9879\u76EE ${project2.slug}\uFF0C${files.size} \u4E2A\u6587\u4EF6\uFF09\u2026`);
5490
+ await client.request(
5491
+ `/api/v1/projects/${project2.id}/frontend/files`,
5492
+ { method: "PUT", body: { files: drafts } },
5493
+ "FRONTEND_SYNC_FAILED"
5494
+ );
5495
+ if (skipped.length > 0) {
5496
+ log(`[adep] \u8DF3\u8FC7\u975E\u6587\u672C\u6587\u4EF6\uFF08\u8349\u7A3F\u901A\u9053\u4E0D\u652F\u6301\u4E8C\u8FDB\u5236\uFF09\uFF1A${skipped.join(", ")}`);
5497
+ }
5498
+ return { projectId: project2.id, uploaded: files.size, skipped, totalBytes };
5499
+ }
5500
+ var MAX_FILES, MAX_TOTAL_SOURCE_BYTES2, FILE_PATH_PATTERN, IGNORED_DIRS;
5501
+ var init_frontend = __esm({
5502
+ "packages/cli/src/frontend.ts"() {
5503
+ "use strict";
5504
+ init_auth();
5505
+ init_client();
5506
+ MAX_FILES = 100;
5507
+ MAX_TOTAL_SOURCE_BYTES2 = 256 * 1024;
5508
+ FILE_PATH_PATTERN = /^[a-zA-Z0-9._][a-zA-Z0-9._/-]*$/;
5509
+ IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".adep", ".vite", "coverage"]);
5510
+ }
5511
+ });
5512
+
5362
5513
  // packages/cli/src/export/client.ts
5363
5514
  var client_exports2 = {};
5364
5515
  __export(client_exports2, {
@@ -5512,11 +5663,11 @@ var fs_exports = {};
5512
5663
  __export(fs_exports, {
5513
5664
  createExportFileSystem: () => createExportFileSystem
5514
5665
  });
5515
- import { mkdir as mkdir8, readFile as readFile10, rm as rm3, stat as stat7, writeFile as writeFile7 } from "node:fs/promises";
5516
- import { dirname as dirname7, join as join13 } from "node:path";
5666
+ import { mkdir as mkdir8, readFile as readFile11, rm as rm3, stat as stat9, writeFile as writeFile7 } from "node:fs/promises";
5667
+ import { dirname as dirname7, join as join15 } from "node:path";
5517
5668
  function createExportFileSystem() {
5518
5669
  return {
5519
- readFile: (path) => readFile10(path),
5670
+ readFile: (path) => readFile11(path),
5520
5671
  writeFile: (path, content) => writeFile7(path, content),
5521
5672
  // 同时用于清理解压临时目录(${zip}.tmp 是个目录)——递归强制删除,缺失不报错。
5522
5673
  deleteFile: async (path) => {
@@ -5524,7 +5675,7 @@ function createExportFileSystem() {
5524
5675
  },
5525
5676
  fileExists: async (path) => {
5526
5677
  try {
5527
- await stat7(path);
5678
+ await stat9(path);
5528
5679
  return true;
5529
5680
  } catch {
5530
5681
  return false;
@@ -5534,9 +5685,9 @@ function createExportFileSystem() {
5534
5685
  await mkdir8(path, { recursive: true });
5535
5686
  },
5536
5687
  unzip: async (zipPath, targetDir) => {
5537
- const entries = zipRead(await readFile10(zipPath));
5688
+ const entries = zipRead(await readFile11(zipPath));
5538
5689
  for (const [name, data] of entries) {
5539
- const dest = join13(targetDir, name);
5690
+ const dest = join15(targetDir, name);
5540
5691
  await mkdir8(dirname7(dest), { recursive: true });
5541
5692
  await writeFile7(dest, data);
5542
5693
  }
@@ -6049,14 +6200,17 @@ ${detail}`);
6049
6200
  var db_exports = {};
6050
6201
  __export(db_exports, {
6051
6202
  dbExec: () => dbExec,
6203
+ dbMigrate: () => dbMigrate,
6052
6204
  dbRollback: () => dbRollback,
6053
6205
  dbSnapshotCreate: () => dbSnapshotCreate,
6054
6206
  dbSnapshotList: () => dbSnapshotList,
6055
6207
  dbSnapshotRestore: () => dbSnapshotRestore,
6056
6208
  dbStart: () => dbStart,
6057
6209
  dbStatus: () => dbStatus,
6058
- dbStop: () => dbStop
6210
+ dbStop: () => dbStop,
6211
+ splitSqlStatements: () => splitSqlStatements
6059
6212
  });
6213
+ import { readFile as readFile12 } from "node:fs/promises";
6060
6214
  import { resolve as resolve8 } from "node:path";
6061
6215
  async function open2(paths, options) {
6062
6216
  const client = await createClient(paths);
@@ -6118,9 +6272,79 @@ async function dbRollback(paths, options) {
6118
6272
  { method: "POST", body: { to: options.to } }
6119
6273
  );
6120
6274
  }
6275
+ function splitSqlStatements(source) {
6276
+ const statements = [];
6277
+ let buffer = "";
6278
+ let inQuote = false;
6279
+ const lines = source.split("\n");
6280
+ for (const rawLine of lines) {
6281
+ const line = rawLine.trim();
6282
+ if (line.length === 0) continue;
6283
+ if (line.startsWith("--")) continue;
6284
+ for (let i = 0; i < line.length; i++) {
6285
+ const ch = line[i];
6286
+ if (ch === "'") {
6287
+ if (inQuote && line[i + 1] === "'") {
6288
+ buffer += ch + line[i + 1];
6289
+ i++;
6290
+ continue;
6291
+ }
6292
+ inQuote = !inQuote;
6293
+ buffer += ch;
6294
+ continue;
6295
+ }
6296
+ if (ch === ";" && !inQuote) {
6297
+ const statement = buffer.trim();
6298
+ if (statement.length > 0) statements.push(statement);
6299
+ buffer = "";
6300
+ continue;
6301
+ }
6302
+ buffer += ch;
6303
+ }
6304
+ buffer += " ";
6305
+ }
6306
+ if (inQuote) {
6307
+ throw new Error(`SQL \u8FC1\u79FB\u6587\u4EF6\u5F15\u53F7\u672A\u95ED\u5408\uFF1A${buffer.trim().slice(0, 80)}\u2026`);
6308
+ }
6309
+ const tail = buffer.trim();
6310
+ if (tail.length > 0) statements.push(tail);
6311
+ return statements;
6312
+ }
6313
+ async function dbMigrate(paths, options) {
6314
+ const source = await readFile12(resolve8(options.file), "utf8");
6315
+ const statements = splitSqlStatements(source);
6316
+ if (statements.length === 0) {
6317
+ throw new Error(`\u8FC1\u79FB\u6587\u4EF6\u6CA1\u6709\u53EF\u6267\u884C\u8BED\u53E5\uFF1A${options.file}`);
6318
+ }
6319
+ const { project: project2 } = await open2(paths, options);
6320
+ const applied = [];
6321
+ const changes = [];
6322
+ for (const statement of statements) {
6323
+ try {
6324
+ const result = await dbExec(paths, {
6325
+ cwd: options.cwd,
6326
+ ...options.slug === void 0 ? {} : { slug: options.slug },
6327
+ sql: statement
6328
+ });
6329
+ applied.push(statement);
6330
+ changes.push(result.changes);
6331
+ } catch (error) {
6332
+ const code = error instanceof CliError ? error.code : "MIGRATE_FAILED";
6333
+ const message = error instanceof Error ? error.message : String(error);
6334
+ return {
6335
+ projectId: project2,
6336
+ applied,
6337
+ changes,
6338
+ failed: { statement, code, message }
6339
+ };
6340
+ }
6341
+ }
6342
+ return { projectId: project2, applied, changes };
6343
+ }
6121
6344
  var init_db2 = __esm({
6122
6345
  "packages/cli/src/db.ts"() {
6123
6346
  "use strict";
6347
+ init_auth();
6124
6348
  init_client();
6125
6349
  }
6126
6350
  });
@@ -6134,8 +6358,8 @@ __export(storage_exports, {
6134
6358
  storageRemove: () => storageRemove,
6135
6359
  storageUpload: () => storageUpload
6136
6360
  });
6137
- import { mkdir as mkdir9, readFile as readFile11, stat as stat8, writeFile as writeFile8 } from "node:fs/promises";
6138
- import { basename as basename4, dirname as dirname8, extname, join as join14, resolve as resolve9 } from "node:path";
6361
+ import { mkdir as mkdir9, readFile as readFile13, stat as stat10, writeFile as writeFile8 } from "node:fs/promises";
6362
+ import { basename as basename4, dirname as dirname8, extname, join as join16, resolve as resolve9 } from "node:path";
6139
6363
  async function open3(paths, options) {
6140
6364
  const client = await createClient(paths);
6141
6365
  const project2 = await resolveSlug(resolve9(options.cwd), options.slug);
@@ -6172,12 +6396,12 @@ function contentTypeOf(path) {
6172
6396
  async function storageUpload(paths, options) {
6173
6397
  const { client, project: project2 } = await open3(paths, options);
6174
6398
  const local = resolve9(options.file);
6175
- const info = await stat8(local).catch(() => null);
6399
+ const info = await stat10(local).catch(() => null);
6176
6400
  if (info === null || !info.isFile()) {
6177
6401
  throw new CliError("FILE_NOT_FOUND", `\u672C\u5730\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${local}`);
6178
6402
  }
6179
6403
  const visibility = options.visibility ?? "private";
6180
- const bytes = await readFile11(local);
6404
+ const bytes = await readFile13(local);
6181
6405
  const form = new FormData();
6182
6406
  form.set("path", options.path);
6183
6407
  form.set("visibility", visibility);
@@ -6218,7 +6442,7 @@ async function storageDownload(paths, options) {
6218
6442
  const url = await resolveDownloadUrl(client, project2, options.path);
6219
6443
  const response = await client.download(url, "STORAGE_DOWNLOAD_FAILED");
6220
6444
  const bytes = Buffer.from(await response.arrayBuffer());
6221
- const output = resolve9(options.output ?? join14(resolve9(options.cwd), basename4(options.path)));
6445
+ const output = resolve9(options.output ?? join16(resolve9(options.cwd), basename4(options.path)));
6222
6446
  await mkdir9(dirname8(output), { recursive: true });
6223
6447
  await writeFile8(output, bytes);
6224
6448
  return { path: options.path, output, size: bytes.length };
@@ -6244,7 +6468,7 @@ __export(doctor_exports, {
6244
6468
  formatDoctorReport: () => formatDoctorReport,
6245
6469
  runDoctor: () => runDoctor
6246
6470
  });
6247
- import { stat as stat9 } from "node:fs/promises";
6471
+ import { stat as stat11 } from "node:fs/promises";
6248
6472
  async function probeServer(server) {
6249
6473
  const controller = new AbortController();
6250
6474
  const timer = setTimeout(() => controller.abort(), SERVER_PROBE_TIMEOUT_MS);
@@ -6262,7 +6486,7 @@ async function probeServer(server) {
6262
6486
  }
6263
6487
  async function credentialMode(file) {
6264
6488
  try {
6265
- const info = await stat9(file);
6489
+ const info = await stat11(file);
6266
6490
  return (info.mode & 511).toString(8).padStart(3, "0");
6267
6491
  } catch {
6268
6492
  return void 0;
@@ -6617,8 +6841,8 @@ __export(hosting_exports, {
6617
6841
  hostingInfo: () => hostingInfo,
6618
6842
  hostingPull: () => hostingPull
6619
6843
  });
6620
- import { readdir as readdir6, readFile as readFile12, stat as stat10 } from "node:fs/promises";
6621
- import { dirname as dirname9, join as join15, relative as relative3, resolve as resolve12, sep } from "node:path";
6844
+ import { readdir as readdir7, readFile as readFile14, stat as stat12 } from "node:fs/promises";
6845
+ import { dirname as dirname9, join as join17, relative as relative5, resolve as resolve12, sep as sep3 } from "node:path";
6622
6846
  async function open4(paths, options) {
6623
6847
  const client = await createClient(paths);
6624
6848
  const project2 = await resolveSlug(resolve12(options.cwd), options.slug);
@@ -6639,16 +6863,16 @@ async function collectSiteFiles(dir) {
6639
6863
  const walk = async (sub) => {
6640
6864
  let entries;
6641
6865
  try {
6642
- entries = await readdir6(sub, { withFileTypes: true });
6866
+ entries = await readdir7(sub, { withFileTypes: true });
6643
6867
  } catch {
6644
6868
  return;
6645
6869
  }
6646
6870
  for (const entry of entries) {
6647
- const full = join15(sub, entry.name);
6871
+ const full = join17(sub, entry.name);
6648
6872
  if (entry.isDirectory()) {
6649
6873
  await walk(full);
6650
6874
  } else if (entry.isFile()) {
6651
- const rel = relative3(root, full).split(sep).join("/");
6875
+ const rel = relative5(root, full).split(sep3).join("/");
6652
6876
  files.set(rel, full);
6653
6877
  }
6654
6878
  }
@@ -6667,8 +6891,8 @@ async function hostingDeploy(paths, options) {
6667
6891
  const uploaded = [];
6668
6892
  for (const [rel, full] of files) {
6669
6893
  log(`[adep] \u4E0A\u4F20 site/${rel}`);
6670
- const info2 = await stat10(full);
6671
- const bytes = await readFile12(full);
6894
+ const info2 = await stat12(full);
6895
+ const bytes = await readFile14(full);
6672
6896
  const form = new FormData();
6673
6897
  form.set("path", `site/${rel}`);
6674
6898
  form.set("visibility", "public");
@@ -6706,7 +6930,7 @@ async function hostingPull(paths, options) {
6706
6930
  const response = await client.download(url, "HOSTING_DOWNLOAD_FAILED");
6707
6931
  const bytes = Buffer.from(await response.arrayBuffer());
6708
6932
  const rel = entry.path.replace(/^site\//, "");
6709
- const target = join15(outputDir, ...rel.split("/"));
6933
+ const target = join17(outputDir, ...rel.split("/"));
6710
6934
  await mkdir10(dirname9(target), { recursive: true });
6711
6935
  await writeFile9(target, bytes);
6712
6936
  files.push({ path: entry.path, size: bytes.length });
@@ -8445,8 +8669,9 @@ function registerDevServers(program2, ctx) {
8445
8669
  init_auth();
8446
8670
  init_client();
8447
8671
  init_deploy();
8448
- import { resolve as resolve7 } from "node:path";
8449
- async function resolveProjectId(client, cwd, slug) {
8672
+ import { stat as stat8 } from "node:fs/promises";
8673
+ import { join as join14, resolve as resolve7 } from "node:path";
8674
+ async function resolveProjectId2(client, cwd, slug) {
8450
8675
  const resolvedSlug = await resolveSlug(resolve7(cwd), slug);
8451
8676
  const listed = await client.request("/api/v1/projects", {}, "PROJECT_LIST_FAILED");
8452
8677
  const match = listed.projects.find((project2) => project2.slug === resolvedSlug);
@@ -8462,7 +8687,7 @@ async function publishFrontend(paths, options) {
8462
8687
  const log = options.silent === true ? () => void 0 : options.log ?? ((line) => process.stdout.write(`${line}
8463
8688
  `));
8464
8689
  const client = await createClient(paths);
8465
- const project2 = await resolveProjectId(client, options.cwd, options.slug);
8690
+ const project2 = await resolveProjectId2(client, options.cwd, options.slug);
8466
8691
  log(`[adep] \u89E6\u53D1\u5E73\u53F0\u4FA7\u524D\u7AEF\u6784\u5EFA\uFF08\u9879\u76EE ${project2.slug}\uFF09\u2026`);
8467
8692
  const published = await client.request(
8468
8693
  `/api/v1/projects/${project2.id}/frontend/publish`,
@@ -8511,6 +8736,17 @@ async function runPublish(paths, io, cwd, flags) {
8511
8736
  });
8512
8737
  }
8513
8738
  if (wantFrontend) {
8739
+ const webDir = join14(cwd, "web");
8740
+ const webStat = await stat8(webDir).catch(() => null);
8741
+ if (webStat !== null && webStat.isDirectory()) {
8742
+ const { frontendSync: frontendSync2 } = await Promise.resolve().then(() => (init_frontend(), frontend_exports));
8743
+ await frontendSync2(paths, {
8744
+ cwd,
8745
+ ...flags.project === void 0 ? {} : { slug: flags.project },
8746
+ silent: io.json,
8747
+ log: (line) => io.line(line)
8748
+ });
8749
+ }
8514
8750
  result.frontend = await publishFrontend(paths, {
8515
8751
  cwd,
8516
8752
  ...flags.project === void 0 ? {} : { slug: flags.project },
@@ -8706,6 +8942,28 @@ function registerDb(program2, ctx) {
8706
8942
  });
8707
8943
  });
8708
8944
  });
8945
+ db.command("migrate").description(
8946
+ "\u6267\u884C SQL \u8FC1\u79FB\u6587\u4EF6\uFF08`;` \u5206\u9694\u9010\u6761\u6267\u884C\uFF0C`--` \u6CE8\u91CA\u5FFD\u7565\uFF1BCREATE \u7B49\u975E\u7834\u574F\u6027\u8BED\u53E5\u514D\u786E\u8BA4\uFF09"
8947
+ ).argument("<file>", "\u8FC1\u79FB SQL \u6587\u4EF6\u8DEF\u5F84\uFF08\u76F8\u5BF9\u5F53\u524D\u76EE\u5F55\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09").option("-p, --project <slug>", "\u76EE\u6807\u5E73\u53F0\u9879\u76EE slug\uFF08\u7F3A\u7701\u53D6 adep.config.ts \u7684 name\uFF09").action(async (file, flags) => {
8948
+ const command = ctx.io("db migrate");
8949
+ await command.run(async () => {
8950
+ const { dbMigrate: dbMigrate2 } = await Promise.resolve().then(() => (init_db2(), db_exports));
8951
+ const result = await dbMigrate2(ctx.paths, {
8952
+ cwd: ctx.cwd,
8953
+ file,
8954
+ ...flags.project === void 0 ? {} : { slug: flags.project }
8955
+ });
8956
+ if (result.failed !== void 0) {
8957
+ command.fail(result.failed.code, result.failed.message);
8958
+ return;
8959
+ }
8960
+ command.ok(result, (data) => {
8961
+ const r = data;
8962
+ const kinds = r.applied.map((s) => s.split(/\s+/)[0] ?? s).join(", ");
8963
+ return `\u2713 ${r.applied.length} \u6761\u8BED\u53E5\u5DF2\u6267\u884C\uFF08${kinds}\uFF09`;
8964
+ });
8965
+ });
8966
+ });
8709
8967
  const snapshot = db.command("snapshot").description("\u6570\u636E\u5E93\u5FEB\u7167\uFF08\u4ED8\u8D39\u6863\u4F4D\uFF09\uFF1A\u5217\u51FA\uFF08\u7F3A\u7701\uFF09/ \u521B\u5EFA / \u8FD8\u539F");
8710
8968
  snapshot.command("list").description("\u5217\u51FA\u9879\u76EE\u6570\u636E\u5E93\u5FEB\u7167\uFF08\u6309\u521B\u5EFA\u65F6\u95F4\u5012\u5E8F\uFF09").option("-p, --project <slug>", "\u76EE\u6807\u5E73\u53F0\u9879\u76EE slug\uFF08\u7F3A\u7701\u53D6 adep.config.ts \u7684 name\uFF09").action(async (flags) => {
8711
8969
  const command = ctx.io("db snapshot list");
@@ -8767,6 +9025,28 @@ function registerDb(program2, ctx) {
8767
9025
  });
8768
9026
  }
8769
9027
 
9028
+ // packages/cli/src/commands/frontend.ts
9029
+ function registerFrontend(program2, ctx) {
9030
+ const frontend = program2.command("frontend").description("\u524D\u7AEF\u8349\u7A3F\u7EF4\u62A4\uFF1Async\uFF08\u672C\u5730 web/ \u2192 \u5E73\u53F0\u8349\u7A3F\uFF09").option("-p, --project <slug>", "\u76EE\u6807\u5E73\u53F0\u9879\u76EE slug\uFF08\u7F3A\u7701\u53D6 adep.config.ts \u7684 name\uFF09");
9031
+ frontend.command("sync").description("\u4E0A\u4F20\u672C\u5730 web/ \u6E90\u7801\u4E3A\u5E73\u53F0\u524D\u7AEF\u8349\u7A3F\uFF08\u6574\u7EC4\u66FF\u6362\uFF1Bpublish \u5168\u6808\u65F6\u81EA\u52A8\u6267\u884C\uFF09").option("-p, --project <slug>", "\u76EE\u6807\u5E73\u53F0\u9879\u76EE slug\uFF08\u7F3A\u7701\u53D6 adep.config.ts \u7684 name\uFF09").option("-d, --dir <dir>", "\u672C\u5730\u524D\u7AEF\u6E90\u7801\u76EE\u5F55\uFF08\u7F3A\u7701 cwd/web\uFF09").action(async (flags) => {
9032
+ const command = ctx.io("frontend sync");
9033
+ await command.run(async () => {
9034
+ const { frontendSync: frontendSync2 } = await Promise.resolve().then(() => (init_frontend(), frontend_exports));
9035
+ const result = await frontendSync2(ctx.paths, {
9036
+ cwd: ctx.cwd,
9037
+ ...flags.project === void 0 ? {} : { slug: flags.project },
9038
+ ...flags.dir === void 0 ? {} : { dir: flags.dir },
9039
+ log: (line) => command.line(line)
9040
+ });
9041
+ command.ok(result, (data) => {
9042
+ const r = data;
9043
+ const skip = r.skipped.length > 0 ? `\uFF08\u8DF3\u8FC7\u4E8C\u8FDB\u5236\uFF1A${r.skipped.join(", ")}\uFF09` : "";
9044
+ return `\u2713 ${r.uploaded} files synced to platform frontend drafts (${r.totalBytes} bytes)${skip}`;
9045
+ });
9046
+ });
9047
+ });
9048
+ }
9049
+
8770
9050
  // packages/cli/src/commands/storage.ts
8771
9051
  function registerStorage(program2, ctx) {
8772
9052
  const storage = program2.command("storage").description("\u4E91\u5B58\u50A8 / \u6587\u4EF6\u5B58\u50A8\uFF1A\u4E0A\u4F20 / \u4E0B\u8F7D / \u5217\u8868 / \u5220\u9664").option("-p, --project <slug>", "\u76EE\u6807\u5E73\u53F0\u9879\u76EE slug\uFF08\u7F3A\u7701\u53D6 adep.config.ts \u7684 name\uFF09");
@@ -9116,6 +9396,7 @@ function buildProgram(options = {}) {
9116
9396
  registerPublish(program2, ctx);
9117
9397
  registerExport(program2, ctx);
9118
9398
  registerDb(program2, ctx);
9399
+ registerFrontend(program2, ctx);
9119
9400
  registerStorage(program2, ctx);
9120
9401
  registerDoctor(program2, ctx);
9121
9402
  registerAuth(program2, ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adep/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "`adep` 命令行工具包位:登录 / 初始化 / 本地调试 / 部署 / 数据库维护 / 云存储 / 静态托管 / 微前端组件(widget),实现见任务单 CLI-001 ~ CLI-003 与 FE-002;子路径 `@adep/cli/vite` 提供云函数 vite 插件(CLI-014)",
6
6
  "bin": {
@@ -20,12 +20,12 @@
20
20
  "dependencies": {
21
21
  "commander": "14.0.2",
22
22
  "esbuild": "0.25.12",
23
- "@adep/cf-compat": "0.1.1",
24
- "@adep/vite-plugin": "0.1.2"
23
+ "@adep/vite-plugin": "0.1.2",
24
+ "@adep/cf-compat": "0.1.1"
25
25
  },
26
26
  "devDependencies": {
27
- "@adep/types": "0.2.2",
28
- "@adep/runtime": "0.2.2"
27
+ "@adep/runtime": "0.2.2",
28
+ "@adep/types": "0.2.2"
29
29
  },
30
30
  "scripts": {
31
31
  "test": "vitest run",