@adep/cli 0.5.0 → 0.5.1

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 +295 -1
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -8472,6 +8472,163 @@ var init_hash = __esm({
8472
8472
  }
8473
8473
  });
8474
8474
 
8475
+ // packages/cli/src/publish-checks.ts
8476
+ import { posix as posix3 } from "node:path";
8477
+ function formatFindings(findings) {
8478
+ const lines = findings.map(
8479
+ (finding) => `${finding.file}:${finding.line} [${finding.rule}] ${finding.severity === "error" ? "\u5371\u9669" : "\u8B66\u544A"}\uFF1A${finding.message}`
8480
+ );
8481
+ return [`\u53D1\u5E03\u524D\u9759\u6001\u6821\u9A8C\u53D1\u73B0 ${findings.length} \u4E2A\u95EE\u9898\uFF1A`, ...lines, "\u4FEE\u590D\u540E\u91CD\u8BD5\uFF1Bwarn \u7EA7\u53EF\u7528 --force \u653E\u884C\uFF0Cerror \u7EA7\u4E0D\u53EF\u653E\u884C\u3002"].join(
8482
+ "\n"
8483
+ );
8484
+ }
8485
+ function locate(content, index) {
8486
+ const before = content.slice(0, index);
8487
+ const line = before.split("\n").length;
8488
+ const start = before.lastIndexOf("\n") + 1;
8489
+ const end = content.indexOf("\n", index);
8490
+ return { line, text: content.slice(start, end === -1 ? content.length : end) };
8491
+ }
8492
+ function checkFilePath(functionName, file2) {
8493
+ const normalized = file2.split("\\").join("/");
8494
+ const segments = normalized.split("/");
8495
+ const escapes = normalized.startsWith("/") || segments.some((s) => ESCAPE_KEY_SEGMENTS.has(s));
8496
+ if (!escapes) return [];
8497
+ return [
8498
+ {
8499
+ rule: "PATH_ESCAPE",
8500
+ severity: "error",
8501
+ functionName,
8502
+ file: file2,
8503
+ line: 1,
8504
+ message: `bundle \u952E\u4E0D\u80FD\u662F\u7EDD\u5BF9\u8DEF\u5F84\u6216\u5305\u542B ".."\uFF08\u6536\u5230 "${file2}"\uFF09`
8505
+ }
8506
+ ];
8507
+ }
8508
+ function checkImportSpecifiers(functionName, file2, content) {
8509
+ const findings = [];
8510
+ for (const matched of content.matchAll(IMPORT_SPECIFIER)) {
8511
+ const specifier = matched[1] ?? "";
8512
+ if (!specifier.startsWith(".")) continue;
8513
+ const resolved = posix3.normalize(posix3.join(posix3.dirname(file2.split("\\").join("/")), specifier));
8514
+ if (!resolved.startsWith("..") && !posix3.isAbsolute(resolved)) continue;
8515
+ const { line } = locate(content, matched.index ?? 0);
8516
+ findings.push({
8517
+ rule: "PATH_ESCAPE",
8518
+ severity: "error",
8519
+ functionName,
8520
+ file: file2,
8521
+ line,
8522
+ message: `import "${specifier}" \u89E3\u6790\u5230 "${resolved}"\uFF0C\u9003\u51FA\u51FD\u6570 bundle \u6839`
8523
+ });
8524
+ }
8525
+ return findings;
8526
+ }
8527
+ function checkSqlConcat(functionName, file2, content) {
8528
+ const findings = [];
8529
+ for (const matched of content.matchAll(SQL_CALL)) {
8530
+ const arg = matched.groups?.["arg"] ?? "";
8531
+ if (!SQL_KEYWORD.test(arg) || !CONCAT_OR_TEMPLATE.test(arg)) continue;
8532
+ const { line } = locate(content, matched.index ?? 0);
8533
+ findings.push({
8534
+ rule: "SQL_CONCAT",
8535
+ severity: "error",
8536
+ functionName,
8537
+ file: file2,
8538
+ line,
8539
+ message: "SQL \u8BED\u53E5\u542B\u5B57\u7B26\u4E32\u62FC\u63A5 / \u6A21\u677F\u63D2\u503C\uFF1B\u6539\u7528\u53C2\u6570\u5316\u5360\u4F4D\u7B26\uFF08? / $1\uFF09"
8540
+ });
8541
+ }
8542
+ return findings;
8543
+ }
8544
+ function checkSecrets(functionName, file2, content) {
8545
+ const findings = [];
8546
+ const lines = content.split("\n");
8547
+ lines.forEach((text, index) => {
8548
+ for (const rule of SECRET_RULES) {
8549
+ if (!rule.pattern.test(text)) continue;
8550
+ findings.push({
8551
+ rule: "SECRET_LITERAL",
8552
+ severity: rule.severity,
8553
+ functionName,
8554
+ file: file2,
8555
+ line: index + 1,
8556
+ message: rule.message
8557
+ });
8558
+ }
8559
+ });
8560
+ return findings;
8561
+ }
8562
+ function collectPrecheckFindings(functions) {
8563
+ const findings = [];
8564
+ for (const [functionName, files] of Object.entries(functions).toSorted(
8565
+ ([a], [b]) => a.localeCompare(b)
8566
+ )) {
8567
+ const sortedFiles = Object.entries(files).toSorted(([a], [b]) => a.localeCompare(b));
8568
+ for (const [file2, content] of sortedFiles) {
8569
+ findings.push(...checkFilePath(functionName, file2));
8570
+ findings.push(...checkImportSpecifiers(functionName, file2, content));
8571
+ findings.push(...checkSqlConcat(functionName, file2, content));
8572
+ findings.push(...checkSecrets(functionName, file2, content));
8573
+ }
8574
+ }
8575
+ const seen = /* @__PURE__ */ new Set();
8576
+ return findings.filter((finding) => {
8577
+ const key = `${finding.rule}|${finding.functionName}|${finding.file}|${finding.line}|${finding.message}`;
8578
+ if (seen.has(key)) return false;
8579
+ seen.add(key);
8580
+ return true;
8581
+ }).toSorted(
8582
+ (a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.rule.localeCompare(b.rule)
8583
+ );
8584
+ }
8585
+ function assertPrechecks(functions, options = {}) {
8586
+ const findings = collectPrecheckFindings(functions);
8587
+ const errors = findings.filter((finding) => finding.severity === "error");
8588
+ const warnings = findings.filter((finding) => finding.severity === "warn");
8589
+ if (errors.length > 0) throw new PrecheckError(findings);
8590
+ if (warnings.length > 0 && options.force !== true) throw new PrecheckError(warnings);
8591
+ return warnings;
8592
+ }
8593
+ var PrecheckError, ESCAPE_KEY_SEGMENTS, IMPORT_SPECIFIER, SQL_CALL, SQL_KEYWORD, CONCAT_OR_TEMPLATE, SECRET_RULES;
8594
+ var init_publish_checks = __esm({
8595
+ "packages/cli/src/publish-checks.ts"() {
8596
+ "use strict";
8597
+ init_auth();
8598
+ PrecheckError = class extends CliError {
8599
+ findings;
8600
+ constructor(findings) {
8601
+ super("PRECHECK_FAILED", formatFindings(findings));
8602
+ this.name = "PrecheckError";
8603
+ this.findings = findings;
8604
+ }
8605
+ };
8606
+ ESCAPE_KEY_SEGMENTS = /* @__PURE__ */ new Set([".."]);
8607
+ IMPORT_SPECIFIER = /(?:from\s*|import\s*|require\s*\(\s*)['"]([^'"]+)['"]/g;
8608
+ SQL_CALL = /\.(?:query|execute|run|all|get|prepare)\s*\(\s*(?<arg>[^\n]*)/g;
8609
+ SQL_KEYWORD = /\b(?:SELECT|INSERT|UPDATE|DELETE|REPLACE|WITH|WHERE|FROM)\b/i;
8610
+ CONCAT_OR_TEMPLATE = /(?:\$\{|`\s*\+|\+\s*['"`]|['"`]\s*\+)/;
8611
+ SECRET_RULES = [
8612
+ {
8613
+ pattern: /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/,
8614
+ message: "\u6E90\u7801\u542B PEM \u79C1\u94A5\u660E\u6587",
8615
+ severity: "error"
8616
+ },
8617
+ { pattern: /AKIA[0-9A-Z]{16}/, message: "\u6E90\u7801\u542B AWS Access Key \u660E\u6587", severity: "error" },
8618
+ {
8619
+ pattern: /(?:sk|pk)_(?:live|test)_[0-9A-Za-z]{16,}/,
8620
+ message: "\u6E90\u7801\u542B\u652F\u4ED8\u6E20\u9053\u5BC6\u94A5\u660E\u6587",
8621
+ severity: "error"
8622
+ },
8623
+ {
8624
+ pattern: /(?:password|passwd|secret|api[_-]?key|access[_-]?token)\s*[:=]\s*['"][^'"]{8,}['"]/i,
8625
+ message: "\u7591\u4F3C\u5BC6\u94A5 / \u53E3\u4EE4\u786C\u7F16\u7801\uFF1B\u6539\u4ECE ctx.env / \u5E73\u53F0\u73AF\u5883\u53D8\u91CF\u8BFB\u53D6",
8626
+ severity: "warn"
8627
+ }
8628
+ ];
8629
+ }
8630
+ });
8631
+
8475
8632
  // packages/cli/src/deploy.ts
8476
8633
  var deploy_exports = {};
8477
8634
  __export(deploy_exports, {
@@ -8577,6 +8734,10 @@ async function deploy(paths, options) {
8577
8734
  }
8578
8735
  const shared = await collectShared(functionsDir);
8579
8736
  const filesFor = (name) => local[name] ?? {};
8737
+ const precheckWarnings = assertPrechecks(local, { force: options.force === true });
8738
+ for (const finding of precheckWarnings) {
8739
+ log(`[adep] \u6821\u9A8C\u8B66\u544A ${finding.file}:${finding.line} [${finding.rule}] ${finding.message}`);
8740
+ }
8580
8741
  const sharedRows = Object.entries(shared).map(([path, content]) => ({ path, content }));
8581
8742
  const hashFiles = (name) => {
8582
8743
  const filesAsRuntimeSees = mergeSharedSource(filesFor(name), sharedRows);
@@ -8677,6 +8838,7 @@ var init_deploy = __esm({
8677
8838
  init_hash();
8678
8839
  init_dev();
8679
8840
  init_net();
8841
+ init_publish_checks();
8680
8842
  init_shared_source();
8681
8843
  init_shared_source();
8682
8844
  AmbiguousFunctionError = class extends CliError {
@@ -10037,7 +10199,9 @@ var init_git = __esm({
10037
10199
  var functions_exports = {};
10038
10200
  __export(functions_exports, {
10039
10201
  functionsList: () => functionsList,
10040
- functionsLogs: () => functionsLogs
10202
+ functionsLogs: () => functionsLogs,
10203
+ logsByTrace: () => logsByTrace,
10204
+ resolveProject: () => resolveProject2
10041
10205
  });
10042
10206
  import { resolve as resolve20 } from "node:path";
10043
10207
  async function functionsList(paths, options) {
@@ -10080,6 +10244,22 @@ async function functionsLogs(paths, options) {
10080
10244
  );
10081
10245
  return { functionId: target.id, name: target.name, logs: result.logs };
10082
10246
  }
10247
+ async function logsByTrace(paths, options) {
10248
+ const trace = options.trace.trim();
10249
+ if (trace.length === 0) {
10250
+ throw new CliError("INVALID_ARGUMENT", "\u7F3A\u5C11 traceId\uFF1Aadep logs --trace <traceId>");
10251
+ }
10252
+ const client = await createClient(paths);
10253
+ const project2 = await resolveProject2(client, options);
10254
+ const params = new URLSearchParams({ trace });
10255
+ if (options.tail !== void 0) params.set("tail", String(options.tail));
10256
+ const result = await client.request(
10257
+ `/api/v1/projects/${project2.id}/logs?${params.toString()}`,
10258
+ {},
10259
+ "FUNCTION_LOGS_FAILED"
10260
+ );
10261
+ return { projectId: project2.id, slug: project2.slug, traceId: result.traceId, logs: result.logs };
10262
+ }
10083
10263
  async function resolveProject2(client, options) {
10084
10264
  const slug = await resolveSlug(resolve20(options.cwd), options.slug);
10085
10265
  const projects = await projectsListFrom(client);
@@ -10104,6 +10284,65 @@ var init_functions = __esm({
10104
10284
  }
10105
10285
  });
10106
10286
 
10287
+ // packages/cli/src/rollback.ts
10288
+ var rollback_exports = {};
10289
+ __export(rollback_exports, {
10290
+ parseVersionFlag: () => parseVersionFlag,
10291
+ rollbackFunctionTo: () => rollbackFunctionTo
10292
+ });
10293
+ function parseVersionFlag(raw) {
10294
+ const text = (raw ?? "").trim();
10295
+ const matched = /^v?(\d+)$/.exec(text);
10296
+ const version2 = matched === null ? Number.NaN : Number(matched[1]);
10297
+ if (!Number.isInteger(version2) || version2 <= 0) {
10298
+ throw new CliError("INVALID_ARGUMENT", `--to \u987B\u4E3A\u7248\u672C\u53F7\uFF08\u5982 v2 \u6216 2\uFF09\uFF0C\u6536\u5230 "${raw ?? ""}"`);
10299
+ }
10300
+ return version2;
10301
+ }
10302
+ async function rollbackFunctionTo(paths, options) {
10303
+ if (!Number.isInteger(options.to) || options.to <= 0) {
10304
+ throw new CliError("INVALID_ARGUMENT", `--to \u987B\u4E3A\u6B63\u6574\u6570\uFF0C\u6536\u5230 ${options.to}`);
10305
+ }
10306
+ const client = await createClient(paths);
10307
+ const project2 = await resolveProject2(client, options);
10308
+ const listed = await client.request(
10309
+ `/api/v1/projects/${project2.id}/functions`,
10310
+ {},
10311
+ "FUNCTION_LIST_FAILED"
10312
+ );
10313
+ const target = listed.functions.find((fn) => fn.name === options.name);
10314
+ if (target === void 0) {
10315
+ throw new CliError("FN_NOT_FOUND", `\u51FD\u6570 "${options.name}" \u4E0D\u5B58\u5728\u4E8E\u9879\u76EE "${project2.slug}"`);
10316
+ }
10317
+ const versions = await client.request(`/api/v1/functions/${target.id}/versions`, {}, "FUNCTION_VERSIONS_FAILED");
10318
+ if (!versions.versions.some((version2) => version2.version === options.to)) {
10319
+ const available = versions.versions.map((version2) => `v${version2.version}`).join(", ");
10320
+ throw new CliError(
10321
+ "VERSION_NOT_FOUND",
10322
+ `\u7248\u672C v${options.to} \u4E0D\u5728\u4FDD\u7559\u7A97\u53E3\u5185\uFF08\u5F53\u524D v${versions.current ?? "\u2014"}\uFF1B\u53EF\u7528\uFF1A${available === "" ? "\u65E0" : available}\uFF09`
10323
+ );
10324
+ }
10325
+ const result = await client.request(
10326
+ `/api/v1/functions/${target.id}/rollback`,
10327
+ { method: "POST", body: { version: options.to } },
10328
+ "ROLLBACK_FAILED"
10329
+ );
10330
+ return {
10331
+ functionId: target.id,
10332
+ name: target.name,
10333
+ from: versions.current,
10334
+ to: result.version
10335
+ };
10336
+ }
10337
+ var init_rollback = __esm({
10338
+ "packages/cli/src/rollback.ts"() {
10339
+ "use strict";
10340
+ init_auth();
10341
+ init_client();
10342
+ init_functions();
10343
+ }
10344
+ });
10345
+
10107
10346
  // packages/cli/src/cli.ts
10108
10347
  init_config();
10109
10348
  import { Command } from "commander";
@@ -38765,6 +39004,59 @@ function registerHosting(program2, ctx) {
38765
39004
  });
38766
39005
  }
38767
39006
 
39007
+ // packages/cli/src/commands/logs.ts
39008
+ init_auth();
39009
+ function registerLogs(program2, ctx) {
39010
+ program2.command("logs").description("\u6309 traceId \u67E5\u8BE2\u4E00\u6B21\u8C03\u7528\u94FE\u7684\u65E5\u5FD7\uFF08traceId \u89C1\u9519\u8BEF\u4FE1\u5C01\u6216\u54CD\u5E94\u5934\uFF09").option("--trace <traceId>", "W3C traceId\uFF0832 \u4F4D hex\uFF09").option("--tail <count>", "\u53D6\u6700\u8FD1\u591A\u5C11\u6761\uFF08\u7F3A\u7701 100\uFF0C\u670D\u52A1\u7AEF\u6709\u4E0A\u9650\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) => {
39011
+ const command = ctx.io("logs");
39012
+ await command.run(async () => {
39013
+ const trace = flags.trace?.trim() ?? "";
39014
+ if (trace.length === 0) {
39015
+ throw new CliError("INVALID_ARGUMENT", "\u7F3A\u5C11 --trace\uFF1Aadep logs --trace <traceId>");
39016
+ }
39017
+ const tail = flags.tail === void 0 ? void 0 : Number(flags.tail);
39018
+ if (tail !== void 0 && (!Number.isFinite(tail) || tail <= 0)) {
39019
+ throw new CliError("INVALID_TAIL", `--tail \u987B\u4E3A\u6B63\u6574\u6570\uFF0C\u6536\u5230 "${flags.tail}"`);
39020
+ }
39021
+ const { logsByTrace: logsByTrace2 } = await Promise.resolve().then(() => (init_functions(), functions_exports));
39022
+ const result = await logsByTrace2(ctx.paths, {
39023
+ cwd: ctx.cwd,
39024
+ trace,
39025
+ ...tail === void 0 ? {} : { tail },
39026
+ ...flags.project === void 0 ? {} : { slug: flags.project }
39027
+ });
39028
+ command.ok(result, (data) => {
39029
+ const r = data;
39030
+ if (r.logs.length === 0) {
39031
+ return `\uFF08trace ${r.traceId} \u6682\u65E0\u65E5\u5FD7\uFF1A\u786E\u8BA4 traceId \u6B63\u786E\uFF0C\u4E14\u8BE5\u8C03\u7528\u53D1\u751F\u5728\u65E5\u5FD7\u7F13\u51B2\u4FDD\u7559\u7A97\u53E3\u5185\uFF09`;
39032
+ }
39033
+ return r.logs.map((log) => `${log.at} [${log.level}] ${log.message}`);
39034
+ });
39035
+ });
39036
+ });
39037
+ }
39038
+
39039
+ // packages/cli/src/commands/rollback.ts
39040
+ function registerRollback(program2, ctx) {
39041
+ program2.command("rollback <fn>").description("\u628A\u5DF2\u53D1\u5E03\u51FD\u6570\u5207\u56DE\u5386\u53F2\u7248\u672C\uFF08\u7248\u672C\u6765\u81EA adep publish \u751F\u6210\u7684\u81EA\u589E\u53F7\uFF09").option("--to <version>", "\u76EE\u6807\u7248\u672C\u53F7\uFF0C\u5982 v2 \u6216 2").option("-p, --project <slug>", "\u76EE\u6807\u5E73\u53F0\u9879\u76EE slug\uFF08\u7F3A\u7701\u53D6 adep.config.ts \u7684 name\uFF09").action(async (name, flags) => {
39042
+ const command = ctx.io("rollback");
39043
+ await command.run(async () => {
39044
+ const { parseVersionFlag: parseVersionFlag2, rollbackFunctionTo: rollbackFunctionTo2 } = await Promise.resolve().then(() => (init_rollback(), rollback_exports));
39045
+ const to = parseVersionFlag2(flags.to);
39046
+ const result = await rollbackFunctionTo2(ctx.paths, {
39047
+ cwd: ctx.cwd,
39048
+ name,
39049
+ to,
39050
+ ...flags.project === void 0 ? {} : { slug: flags.project }
39051
+ });
39052
+ command.ok(result, (data) => {
39053
+ const r = data;
39054
+ return `\u5DF2\u56DE\u6EDA ${r.name}\uFF1Av${r.from ?? "\u2014"} \u2192 v${r.to}`;
39055
+ });
39056
+ });
39057
+ });
39058
+ }
39059
+
38768
39060
  // packages/cli/src/cli.ts
38769
39061
  var requireJson = createRequire2(import.meta.url);
38770
39062
  var APP_VERSION = requireJson("../package.json").version;
@@ -38800,6 +39092,8 @@ function buildProgram(options = {}) {
38800
39092
  registerAuth(program2, ctx);
38801
39093
  registerMcp(program2, ctx);
38802
39094
  registerGit(program2, ctx);
39095
+ registerLogs(program2, ctx);
39096
+ registerRollback(program2, ctx);
38803
39097
  registerDeploy(program2, ctx);
38804
39098
  registerProjects(program2, ctx);
38805
39099
  registerFunctions(program2, ctx);
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "homepage": "https://adep.top",
9
9
  "bugs": "https://gitcode.com/yunqiang_wu/ai-yunke/issues",
10
- "version": "0.5.0",
10
+ "version": "0.5.1",
11
11
  "type": "module",
12
12
  "description": "`adep` 命令行工具包位:登录 / 初始化 / 本地调试 / 部署 / 数据库维护 / 云存储 / 静态托管 / 微前端组件(widget),实现见任务单 CLI-001 ~ CLI-003 与 FE-002;子路径 `@adep/cli/vite` 提供云函数 vite 插件(CLI-014)",
13
13
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "@adep/runtime": "0.3.8"
38
38
  },
39
39
  "devDependencies": {
40
- "@adep/types": "0.4.0"
40
+ "@adep/types": "0.4.1"
41
41
  },
42
42
  "scripts": {
43
43
  "test": "vitest run",