@adep/cli 0.4.4 → 0.4.6

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 (46) hide show
  1. package/README.md +107 -11
  2. package/dist/index.js +33802 -27354
  3. package/dist/vite.js +247 -17
  4. package/dist/worker-entry.js +163 -1
  5. package/package.json +7 -6
  6. package/templates/app-desktop/README.md +67 -2
  7. package/templates/app-desktop/identity-store.mjs +309 -0
  8. package/templates/app-desktop/identity.mjs +517 -0
  9. package/templates/app-desktop/main.cjs +17 -0
  10. package/templates/app-desktop/main.mjs +218 -2
  11. package/templates/app-desktop/offline-runtime.mjs +72 -3
  12. package/templates/app-desktop/package.json +10 -6
  13. package/templates/app-desktop/recovery-ui.mjs +300 -0
  14. package/templates/app-desktop/sync-loop.mjs +137 -0
  15. package/templates/app-mobile/README.md +47 -9
  16. package/templates/app-mobile/package.json +2 -1
  17. package/templates/app-mobile/plugins/adep-bridge/Package.swift +5 -1
  18. package/templates/app-mobile/plugins/adep-bridge/android/build.gradle +34 -3
  19. package/templates/app-mobile/plugins/adep-bridge/android/src/main/AndroidManifest.xml +12 -5
  20. package/templates/app-mobile/plugins/adep-bridge/android/src/main/java/com/adep/bridge/AdepBridge.kt +198 -12
  21. package/templates/app-mobile/plugins/adep-bridge/android/src/main/java/com/adep/bridge/AdepIdentity.kt +342 -0
  22. package/templates/app-mobile/plugins/adep-bridge/android/src/main/java/com/adep/bridge/AdepKeystore.kt +56 -2
  23. package/templates/app-mobile/plugins/adep-bridge/android/src/main/java/com/adep/bridge/AdepSqlite.kt +506 -0
  24. package/templates/app-mobile/plugins/adep-bridge/android/src/main/java/com/adep/bridge/AdepSqliteLogic.kt +235 -0
  25. package/templates/app-mobile/plugins/adep-bridge/dist/plugin.js +30 -0
  26. package/templates/app-mobile/plugins/adep-bridge/ios/AdepBridge.podspec +3 -0
  27. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepBridge.swift +221 -9
  28. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepIdentity.swift +500 -0
  29. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepKeychain.swift +69 -17
  30. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepPermissionGate.swift +25 -4
  31. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepShellConfig.swift +54 -0
  32. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepSqlite.swift +614 -0
  33. package/templates/app-mobile/plugins/adep-bridge/package.json +10 -0
  34. package/templates/app-mobile/template.json +12 -3
  35. package/templates/app-mobile/web/adep-bridge.js +103 -9
  36. package/templates/app-mobile/web/offline-runtime.js +230 -0
  37. package/templates/app-mobile-xtool/AdepShell-Info.plist +24 -0
  38. package/templates/app-mobile-xtool/Package.swift +59 -0
  39. package/templates/app-mobile-xtool/README.md +81 -0
  40. package/templates/app-mobile-xtool/Sources/AdepShell/App.swift +36 -0
  41. package/templates/app-mobile-xtool/Sources/AdepShell/ShellViewController.swift +223 -0
  42. package/templates/app-mobile-xtool/adep.permissions.json +4 -0
  43. package/templates/app-mobile-xtool/app/site/.gitkeep +0 -0
  44. package/templates/app-mobile-xtool/template.json +15 -0
  45. package/templates/app-mobile-xtool/xtool.yml +35 -0
  46. package/templates/app-mobile/plugins/adep-bridge/capacitor.plugin.json +0 -10
package/dist/vite.js CHANGED
@@ -237,6 +237,9 @@ function depsKeyOf(dependencies) {
237
237
  const canonical = Object.entries(dependencies).map(([name, version]) => `${name}@${version}`).toSorted().join("\n");
238
238
  return createHash("sha256").update(canonical).digest("hex").slice(0, 16);
239
239
  }
240
+ function parseNameList(value) {
241
+ return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
242
+ }
240
243
 
241
244
  // packages/runtime/src/functions/deps/resolve.ts
242
245
  function resolveExecutionDeps(config, projectId, manifestContent) {
@@ -742,10 +745,21 @@ function compileSelect(state, dialect) {
742
745
  if (state.orderBys.length > 0) {
743
746
  sql += ` ORDER BY ${state.orderBys.map((order) => `${dialect.quote(order.column)} ${order.direction.toUpperCase()}`).join(", ")}`;
744
747
  }
745
- if (state.limit !== void 0) sql += ` LIMIT ${pushParam(params, dialect, state.limit)}`;
746
- if (state.offset !== void 0) sql += ` OFFSET ${pushParam(params, dialect, state.offset)}`;
748
+ if (state.limit !== void 0) {
749
+ assertInlineableInt(state.limit, "limit");
750
+ sql += ` LIMIT ${state.limit}`;
751
+ }
752
+ if (state.offset !== void 0) {
753
+ assertInlineableInt(state.offset, "offset");
754
+ sql += ` OFFSET ${state.offset}`;
755
+ }
747
756
  return { sql, params };
748
757
  }
758
+ function assertInlineableInt(value, name) {
759
+ if (!Number.isInteger(value) || value < 0) {
760
+ throw unsafeOperation(`${name} \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570`);
761
+ }
762
+ }
749
763
  function compileCount(state, dialect) {
750
764
  const params = [];
751
765
  let sql = `SELECT count(*) AS n FROM ${dialect.quote(state.table)}`;
@@ -1299,11 +1313,21 @@ function parseWhereClauses(whereRaw, cursor) {
1299
1313
  const t = part.trim();
1300
1314
  if (t.length === 0) continue;
1301
1315
  if (/\bIS\s+NULL\b/i.test(t)) {
1302
- clauses.push({ column: unqualifyColumn(ident(t.split(/\s+IS\s+NULL\b/i)[0] ?? "")), operator: "IS NULL", value: null, list: false });
1316
+ clauses.push({
1317
+ column: unqualifyColumn(ident(t.split(/\s+IS\s+NULL\b/i)[0] ?? "")),
1318
+ operator: "IS NULL",
1319
+ value: null,
1320
+ list: false
1321
+ });
1303
1322
  continue;
1304
1323
  }
1305
1324
  if (/\bIS\s+NOT\s+NULL\b/i.test(t)) {
1306
- clauses.push({ column: unqualifyColumn(ident(t.split(/\s+IS\s+NOT\s+NULL\b/i)[0] ?? "")), operator: "IS NOT NULL", value: null, list: false });
1325
+ clauses.push({
1326
+ column: unqualifyColumn(ident(t.split(/\s+IS\s+NOT\s+NULL\b/i)[0] ?? "")),
1327
+ operator: "IS NOT NULL",
1328
+ value: null,
1329
+ list: false
1330
+ });
1307
1331
  continue;
1308
1332
  }
1309
1333
  const opMatch = /^\s*((?:"[^"]+")|([A-Za-z_][A-Za-z0-9_.]*))\s*(=|!=|>=|<=|>|<|like|in|not\s+in)\s*(.+)$/i.exec(
@@ -1480,7 +1504,10 @@ function parseConflictClause(clause, cursor, fallbackCols) {
1480
1504
  clause.trim()
1481
1505
  );
1482
1506
  if (m === null) {
1483
- throw new SimDbError("DB_UNSAFE_OP", `\u65E0\u6CD5\u89E3\u6790 ON CONFLICT \u5B50\u53E5\uFF1A${clause.trim().slice(0, 60)}\u2026`);
1507
+ throw new SimDbError(
1508
+ "DB_UNSAFE_OP",
1509
+ `\u65E0\u6CD5\u89E3\u6790 ON CONFLICT \u5B50\u53E5\uFF1A${clause.trim().slice(0, 60)}\u2026`
1510
+ );
1484
1511
  }
1485
1512
  const colsRaw = m[1];
1486
1513
  const cols = colsRaw === void 0 ? fallbackCols : colsRaw.split(",").map(ident);
@@ -1684,16 +1711,16 @@ var SimSqlEngine = class {
1684
1711
  row[col] = values[i] ?? null;
1685
1712
  });
1686
1713
  if (conflictSpec !== null) {
1687
- const existing = table.rows.find(
1688
- (r) => conflictSpec.cols.every((c) => r[c] === row[c])
1689
- );
1714
+ const existing = table.rows.find((r) => conflictSpec.cols.every((c) => r[c] === row[c]));
1690
1715
  if (existing !== void 0) {
1691
1716
  const oldRow = { ...existing };
1692
1717
  for (const set of conflictSpec.sets) {
1693
1718
  existing[set.col] = resolveSetValue(set.value, oldRow, row);
1694
1719
  }
1695
1720
  inserted += 1;
1696
- void this.persist();
1721
+ this.persist().catch((error) => {
1722
+ console.warn("[sim] \u6301\u4E45\u5316\u5931\u8D25\uFF1A", error);
1723
+ });
1697
1724
  continue;
1698
1725
  }
1699
1726
  }
@@ -1701,7 +1728,9 @@ var SimSqlEngine = class {
1701
1728
  table.rows.push(row);
1702
1729
  inserted += 1;
1703
1730
  }
1704
- void this.persist();
1731
+ this.persist().catch((error) => {
1732
+ console.warn("[sim] \u6301\u4E45\u5316\u5931\u8D25\uFF1A", error);
1733
+ });
1705
1734
  return { changes: inserted };
1706
1735
  }
1707
1736
  execUpdate(sql, params) {
@@ -1731,7 +1760,9 @@ var SimSqlEngine = class {
1731
1760
  }
1732
1761
  changes += 1;
1733
1762
  }
1734
- void this.persist();
1763
+ this.persist().catch((error) => {
1764
+ console.warn("[sim] \u6301\u4E45\u5316\u5931\u8D25\uFF1A", error);
1765
+ });
1735
1766
  return { changes };
1736
1767
  }
1737
1768
  execDelete(sql, params) {
@@ -1749,7 +1780,9 @@ var SimSqlEngine = class {
1749
1780
  else changes += 1;
1750
1781
  }
1751
1782
  table.rows = keep;
1752
- void this.persist();
1783
+ this.persist().catch((error) => {
1784
+ console.warn("[sim] \u6301\u4E45\u5316\u5931\u8D25\uFF1A", error);
1785
+ });
1753
1786
  return { changes };
1754
1787
  }
1755
1788
  select(sql, params) {
@@ -1771,7 +1804,9 @@ var SimSqlEngine = class {
1771
1804
  rows = table.rows.map((row) => {
1772
1805
  const merged = { ...row };
1773
1806
  for (const j of joins) {
1774
- const joined = this.tables[j.table]?.rows.find((jr) => jr[j.leftCol] === merged[j.rightCol]);
1807
+ const joined = this.tables[j.table]?.rows.find(
1808
+ (jr) => jr[j.leftCol] === merged[j.rightCol]
1809
+ );
1775
1810
  if (joined !== void 0) {
1776
1811
  for (const [k, v] of Object.entries(joined)) merged[`${j.alias}.${k}`] = v;
1777
1812
  }
@@ -2872,7 +2907,7 @@ function envDbFile() {
2872
2907
  function displayPath(cwd, target) {
2873
2908
  if (target === ":memory:") return target;
2874
2909
  const rel = relative(cwd, target);
2875
- return rel.startsWith("..") ? target : rel;
2910
+ return rel.startsWith("..") ? target : rel.split(/[\\/]/u).join("/");
2876
2911
  }
2877
2912
  function simDbTarget(cwd) {
2878
2913
  return displayPath(cwd, join5(simDir(cwd), "db.json"));
@@ -2980,6 +3015,25 @@ async function loadSimEnv(cwd, config = {}) {
2980
3015
  return merged;
2981
3016
  }
2982
3017
 
3018
+ // packages/cli/src/sim/fetch-config.ts
3019
+ var LOCAL_FETCH_TIMEOUT_MS_DEFAULT = 1e4;
3020
+ function resolveLocalFetchConfig(simEnv) {
3021
+ const rawAllowlist = simEnv.FUNCTIONS_FETCH_ALLOWLIST;
3022
+ if (rawAllowlist === void 0) return void 0;
3023
+ const rawTimeout = simEnv.FUNCTIONS_FETCH_TIMEOUT_MS;
3024
+ let timeoutMs = LOCAL_FETCH_TIMEOUT_MS_DEFAULT;
3025
+ if (rawTimeout !== void 0 && rawTimeout.trim() !== "") {
3026
+ const parsed = Number(rawTimeout);
3027
+ if (!Number.isInteger(parsed) || parsed <= 0) {
3028
+ throw new Error(
3029
+ `[adep] FUNCTIONS_FETCH_TIMEOUT_MS \u975E\u6CD5\uFF1A"${rawTimeout}"\uFF08\u987B\u4E3A\u6B63\u6574\u6570\u6BEB\u79D2\u6570\uFF1B\u4E0D\u914D\u5219\u7F3A\u7701 ${LOCAL_FETCH_TIMEOUT_MS_DEFAULT}\uFF09`
3030
+ );
3031
+ }
3032
+ timeoutMs = parsed;
3033
+ }
3034
+ return { allowlist: parseNameList(rawAllowlist), timeoutMs };
3035
+ }
3036
+
2983
3037
  // packages/cli/src/sim/invoke.ts
2984
3038
  function resolveFunctionEntry(files, fnName) {
2985
3039
  const flat = `${fnName}.ts`;
@@ -3087,6 +3141,11 @@ var SIM_BOUNDARIES = [
3087
3141
  kind: "diff",
3088
3142
  title: "\u4E0D\u8BA1\u91CF",
3089
3143
  detail: "\u672C\u6A21\u62DF\u8FD0\u884C\u65F6\u4E0D\u4EA7\u51FA\u8BA1\u91CF\u4E8B\u4EF6\uFF0C\u4E5F\u4E0D\u5199 billing\uFF1B\u79BB\u7EBF\u5F00\u53D1\u4E0D\u8BA1\u8D39\u3002"
3144
+ },
3145
+ {
3146
+ kind: "diff",
3147
+ title: "\u51FA\u7F51\u7531\u767D\u540D\u5355\u63A7\u5236",
3148
+ detail: "cloud.fetch \u9ED8\u8BA4\u65E0\u7F51\u7EDC\uFF08\u672A\u914D\u7F6E\u65F6\u8C03\u7528\u629B FN_FETCH_DISABLED\uFF09\uFF1B.env \u914D FUNCTIONS_FETCH_ALLOWLIST\uFF08\u9017\u53F7\u5206\u9694\u4E3B\u673A\uFF0C*. \u901A\u914D\uFF1BFUNCTIONS_FETCH_TIMEOUT_MS \u7F3A\u7701 10s\uFF09\u540E\u91CD\u542F\u6CE8\u5165\u53D7\u63A7 fetch\u2014\u2014\u95E8\u7981\u4E0E\u7EBF\u4E0A\u4E00\u81F4\uFF08\u767D\u540D\u5355 / \u8D85\u65F6 / \u91CD\u5B9A\u5411\u9010\u8DF3\u590D\u68C0\uFF09\uFF0C\u4F46\u672C\u5730\u7531\u5F00\u53D1\u673A\u7F51\u7EDC\u6808\u76F4\u8FDE\uFF0C\u65E0\u5E73\u53F0\u51FA\u7F51\u4E2D\u7EE7\u4E0E\u5BA1\u8BA1\u3002"
3090
3149
  }
3091
3150
  ];
3092
3151
 
@@ -3660,6 +3719,158 @@ function parseExportClause(clause) {
3660
3719
  }
3661
3720
  return entries;
3662
3721
  }
3722
+ var CONTINUES_AFTER = "=+-*/%&|^<>?:,.[(";
3723
+ var CONTINUES_BEFORE = "`([.?:,+-*/%&|^<>=";
3724
+ var REGEX_PRECEDERS = "(,=:[!&|?{};+-*%~^<>";
3725
+ var MATCHING_OPEN = { ")": "(", "]": "[", "}": "{" };
3726
+ function lineNumberAt(code, index) {
3727
+ let line = 1;
3728
+ for (let i = 0; i < index; i += 1) if (code.charCodeAt(i) === 10) line += 1;
3729
+ return line;
3730
+ }
3731
+ function skipQuoted(code, start) {
3732
+ const quote = code.charAt(start);
3733
+ let i = start + 1;
3734
+ while (i < code.length) {
3735
+ const ch = code.charAt(i);
3736
+ if (ch === "\\") {
3737
+ i += 2;
3738
+ continue;
3739
+ }
3740
+ if (ch === quote) return i + 1;
3741
+ if (ch === "\n") return i;
3742
+ i += 1;
3743
+ }
3744
+ return code.length;
3745
+ }
3746
+ function skipRegex(code, start) {
3747
+ let i = start + 1;
3748
+ let inClass = false;
3749
+ while (i < code.length) {
3750
+ const ch = code.charAt(i);
3751
+ if (ch === "\\") {
3752
+ i += 2;
3753
+ continue;
3754
+ }
3755
+ if (ch === "\n") return start + 1;
3756
+ if (ch === "[") inClass = true;
3757
+ else if (ch === "]") inClass = false;
3758
+ else if (ch === "/" && !inClass) return i + 1;
3759
+ i += 1;
3760
+ }
3761
+ return code.length;
3762
+ }
3763
+ function regexCanStart(prev) {
3764
+ return prev === "" || REGEX_PRECEDERS.includes(prev);
3765
+ }
3766
+ function continuesOnto(code, newline, prev) {
3767
+ if (prev !== "" && CONTINUES_AFTER.includes(prev)) return true;
3768
+ let i = newline + 1;
3769
+ while (i < code.length && /\s/.test(code.charAt(i))) i += 1;
3770
+ const next = code.charAt(i);
3771
+ return next !== "" && CONTINUES_BEFORE.includes(next);
3772
+ }
3773
+ function scanDeclaration(code, from) {
3774
+ const commas = [];
3775
+ const stack = [];
3776
+ let prev = "";
3777
+ let i = from;
3778
+ while (i < code.length) {
3779
+ const ch = code.charAt(i);
3780
+ if (stack[stack.length - 1] === "`") {
3781
+ if (ch === "\\") {
3782
+ i += 2;
3783
+ continue;
3784
+ }
3785
+ if (ch === "`") {
3786
+ stack.pop();
3787
+ prev = "`";
3788
+ i += 1;
3789
+ continue;
3790
+ }
3791
+ if (ch === "$" && code.charAt(i + 1) === "{") {
3792
+ stack.push("{");
3793
+ prev = "{";
3794
+ i += 2;
3795
+ continue;
3796
+ }
3797
+ i += 1;
3798
+ continue;
3799
+ }
3800
+ if (ch === "/" && code.charAt(i + 1) === "/") {
3801
+ const newline = code.indexOf("\n", i);
3802
+ i = newline === -1 ? code.length : newline;
3803
+ continue;
3804
+ }
3805
+ if (ch === "/" && code.charAt(i + 1) === "*") {
3806
+ const close = code.indexOf("*/", i + 2);
3807
+ i = close === -1 ? code.length : close + 2;
3808
+ continue;
3809
+ }
3810
+ if (ch === '"' || ch === "'") {
3811
+ i = skipQuoted(code, i);
3812
+ prev = ch;
3813
+ continue;
3814
+ }
3815
+ if (ch === "`") {
3816
+ stack.push("`");
3817
+ i += 1;
3818
+ continue;
3819
+ }
3820
+ if (ch === "/" && regexCanStart(prev)) {
3821
+ i = skipRegex(code, i);
3822
+ prev = "/";
3823
+ continue;
3824
+ }
3825
+ if (ch === "(" || ch === "[" || ch === "{") {
3826
+ stack.push(ch);
3827
+ prev = ch;
3828
+ i += 1;
3829
+ continue;
3830
+ }
3831
+ if (ch === ")" || ch === "]" || ch === "}") {
3832
+ if (stack[stack.length - 1] !== MATCHING_OPEN[ch]) return { commas, end: i };
3833
+ stack.pop();
3834
+ prev = ch;
3835
+ i += 1;
3836
+ continue;
3837
+ }
3838
+ if (stack.length === 0) {
3839
+ if (ch === ";") return { commas, end: i };
3840
+ if (ch === ",") commas.push(i);
3841
+ if (ch === "\n" && !continuesOnto(code, i, prev)) return { commas, end: i };
3842
+ }
3843
+ if (!/\s/.test(ch)) prev = ch;
3844
+ i += 1;
3845
+ }
3846
+ return { commas, end: code.length };
3847
+ }
3848
+ function declaratorName(segment) {
3849
+ const match = /^\s*([A-Za-z_$][\w$]*)\s*(?:=(?![=>])|$)/.exec(segment);
3850
+ return match === null ? null : match[1] ?? null;
3851
+ }
3852
+ function restDeclarators(code, from) {
3853
+ const { commas, end } = scanDeclaration(code, from);
3854
+ if (commas.length === 0) return [];
3855
+ const segments = [];
3856
+ let cursor = from;
3857
+ for (const comma of commas) {
3858
+ segments.push(code.slice(cursor, comma));
3859
+ cursor = comma + 1;
3860
+ }
3861
+ segments.push(code.slice(cursor, end));
3862
+ const names = [];
3863
+ for (const segment of segments.slice(1)) {
3864
+ const name = declaratorName(segment);
3865
+ if (name === null) {
3866
+ throw new Error(
3867
+ `\u4E0D\u652F\u6301\u7684\u5BFC\u51FA\u58F0\u660E\u5F62\u6001\uFF1A"${segment.trim()}"\uFF08\u6C99\u7BB1\u5B50\u96C6\u4EC5\u63A5\u53D7\u9017\u53F7\u5206\u9694\u7684\u7B80\u5355\u6807\u8BC6\u7B26\u58F0\u660E\uFF0C\u5982 \`export const a = 1, b = 2\`\uFF1B\u542B\u89E3\u6784\u6216\u590D\u6742\u521D\u503C\u65F6\u8BF7\u62C6\u6210\u591A\u6761 export\uFF09`
3868
+ );
3869
+ }
3870
+ names.push(name);
3871
+ }
3872
+ return names;
3873
+ }
3663
3874
  function transformModule(code) {
3664
3875
  let out = code;
3665
3876
  const named = [];
@@ -3722,10 +3933,20 @@ function transformModule(code) {
3722
3933
  return `${lead}${indent}const ${temp} = require(${source})${pad}`;
3723
3934
  }
3724
3935
  );
3936
+ const destructured = /(^|\n)[ \t]*export\s+(const|let|var)\s*[{[]/.exec(out);
3937
+ if (destructured !== null) {
3938
+ const offset = destructured.index + (destructured[1] === "\n" ? 1 : 0);
3939
+ throw new Error(
3940
+ `\u4E0D\u652F\u6301\u7684\u5BFC\u51FA\u58F0\u660E\u5F62\u6001\uFF08\u7B2C ${lineNumberAt(out, offset)} \u884C\uFF09\uFF1A\u89E3\u6784\u5BFC\u51FA \`export ${destructured[2] ?? ""} { \u2026 } = \u2026\`\u3002\u6C99\u7BB1\u5B50\u96C6\u4EC5\u63A5\u53D7\u7B80\u5355\u6807\u8BC6\u7B26\u58F0\u660E\uFF1A\u8BF7\u5148\u58F0\u660E\u518D \`export { a, b }\`\uFF0C\u6216\u62C6\u6210\u591A\u6761 \`export const\``
3941
+ );
3942
+ }
3725
3943
  out = out.replace(
3726
3944
  /export\s+(const|let|var)\s+([A-Za-z_$][\w$]*)/g,
3727
- (_match, kw, name) => {
3945
+ (match, kw, name, offset, source) => {
3728
3946
  named.push({ local: name, exported: name });
3947
+ for (const extra of restDeclarators(source, offset + match.length)) {
3948
+ named.push({ local: extra, exported: extra });
3949
+ }
3729
3950
  return `${kw} ${name}`;
3730
3951
  }
3731
3952
  );
@@ -4804,9 +5025,18 @@ async function startDevServer(options) {
4804
5025
  ...options.dbFile === void 0 ? {} : { dbFile: options.dbFile }
4805
5026
  });
4806
5027
  const bearerUser = bearerUserResolverFromEnv();
5028
+ const simEnv = await loadSimEnv(cwd).catch(() => ({}));
5029
+ const fetchConfig = resolveLocalFetchConfig(simEnv);
4807
5030
  const executor = new WorkerFunctionExecutor({
4808
- deps: { rootDir: cwd, builtin: LOCAL_BUILTIN_DEPS }
5031
+ deps: { rootDir: cwd, builtin: LOCAL_BUILTIN_DEPS },
5032
+ // GAME-007 本地接线:配置白名单才注入受控 fetch,语义与线上逐字一致。
5033
+ ...fetchConfig === void 0 ? {} : { fetch: fetchConfig }
4809
5034
  });
5035
+ if (fetchConfig !== void 0) {
5036
+ log(
5037
+ `[adep] \u53D7\u63A7 fetch \u5DF2\u6CE8\u5165\uFF08FUNCTIONS_FETCH_ALLOWLIST\uFF09\uFF1A\u767D\u540D\u5355 ${fetchConfig.allowlist.length} \u9879\uFF0C\u8D85\u65F6 ${fetchConfig.timeoutMs}ms`
5038
+ );
5039
+ }
4810
5040
  await mkdir4(functionsDir, { recursive: true });
4811
5041
  const db = await openLocalDbDriver(dbTarget, { cwd, tag: "adep", log });
4812
5042
  const runtime = await createSimRuntime({
@@ -4829,7 +5059,7 @@ async function startDevServer(options) {
4829
5059
  let files = mergeSharedIntoFunctions(await collectFunctions(functionsDir));
4830
5060
  let env = {
4831
5061
  ...config.environment,
4832
- ...await loadSimEnv(cwd).catch(() => ({}))
5062
+ ...simEnv
4833
5063
  };
4834
5064
  const dbBundle = runtime.bundle;
4835
5065
  const invokeHandler = createSimInvokeHandler({ executor, files });
@@ -127,6 +127,158 @@ function parseExportClause(clause) {
127
127
  }
128
128
  return entries;
129
129
  }
130
+ var CONTINUES_AFTER = "=+-*/%&|^<>?:,.[(";
131
+ var CONTINUES_BEFORE = "`([.?:,+-*/%&|^<>=";
132
+ var REGEX_PRECEDERS = "(,=:[!&|?{};+-*%~^<>";
133
+ var MATCHING_OPEN = { ")": "(", "]": "[", "}": "{" };
134
+ function lineNumberAt(code, index) {
135
+ let line = 1;
136
+ for (let i = 0; i < index; i += 1) if (code.charCodeAt(i) === 10) line += 1;
137
+ return line;
138
+ }
139
+ function skipQuoted(code, start) {
140
+ const quote = code.charAt(start);
141
+ let i = start + 1;
142
+ while (i < code.length) {
143
+ const ch = code.charAt(i);
144
+ if (ch === "\\") {
145
+ i += 2;
146
+ continue;
147
+ }
148
+ if (ch === quote) return i + 1;
149
+ if (ch === "\n") return i;
150
+ i += 1;
151
+ }
152
+ return code.length;
153
+ }
154
+ function skipRegex(code, start) {
155
+ let i = start + 1;
156
+ let inClass = false;
157
+ while (i < code.length) {
158
+ const ch = code.charAt(i);
159
+ if (ch === "\\") {
160
+ i += 2;
161
+ continue;
162
+ }
163
+ if (ch === "\n") return start + 1;
164
+ if (ch === "[") inClass = true;
165
+ else if (ch === "]") inClass = false;
166
+ else if (ch === "/" && !inClass) return i + 1;
167
+ i += 1;
168
+ }
169
+ return code.length;
170
+ }
171
+ function regexCanStart(prev) {
172
+ return prev === "" || REGEX_PRECEDERS.includes(prev);
173
+ }
174
+ function continuesOnto(code, newline, prev) {
175
+ if (prev !== "" && CONTINUES_AFTER.includes(prev)) return true;
176
+ let i = newline + 1;
177
+ while (i < code.length && /\s/.test(code.charAt(i))) i += 1;
178
+ const next = code.charAt(i);
179
+ return next !== "" && CONTINUES_BEFORE.includes(next);
180
+ }
181
+ function scanDeclaration(code, from) {
182
+ const commas = [];
183
+ const stack = [];
184
+ let prev = "";
185
+ let i = from;
186
+ while (i < code.length) {
187
+ const ch = code.charAt(i);
188
+ if (stack[stack.length - 1] === "`") {
189
+ if (ch === "\\") {
190
+ i += 2;
191
+ continue;
192
+ }
193
+ if (ch === "`") {
194
+ stack.pop();
195
+ prev = "`";
196
+ i += 1;
197
+ continue;
198
+ }
199
+ if (ch === "$" && code.charAt(i + 1) === "{") {
200
+ stack.push("{");
201
+ prev = "{";
202
+ i += 2;
203
+ continue;
204
+ }
205
+ i += 1;
206
+ continue;
207
+ }
208
+ if (ch === "/" && code.charAt(i + 1) === "/") {
209
+ const newline = code.indexOf("\n", i);
210
+ i = newline === -1 ? code.length : newline;
211
+ continue;
212
+ }
213
+ if (ch === "/" && code.charAt(i + 1) === "*") {
214
+ const close = code.indexOf("*/", i + 2);
215
+ i = close === -1 ? code.length : close + 2;
216
+ continue;
217
+ }
218
+ if (ch === '"' || ch === "'") {
219
+ i = skipQuoted(code, i);
220
+ prev = ch;
221
+ continue;
222
+ }
223
+ if (ch === "`") {
224
+ stack.push("`");
225
+ i += 1;
226
+ continue;
227
+ }
228
+ if (ch === "/" && regexCanStart(prev)) {
229
+ i = skipRegex(code, i);
230
+ prev = "/";
231
+ continue;
232
+ }
233
+ if (ch === "(" || ch === "[" || ch === "{") {
234
+ stack.push(ch);
235
+ prev = ch;
236
+ i += 1;
237
+ continue;
238
+ }
239
+ if (ch === ")" || ch === "]" || ch === "}") {
240
+ if (stack[stack.length - 1] !== MATCHING_OPEN[ch]) return { commas, end: i };
241
+ stack.pop();
242
+ prev = ch;
243
+ i += 1;
244
+ continue;
245
+ }
246
+ if (stack.length === 0) {
247
+ if (ch === ";") return { commas, end: i };
248
+ if (ch === ",") commas.push(i);
249
+ if (ch === "\n" && !continuesOnto(code, i, prev)) return { commas, end: i };
250
+ }
251
+ if (!/\s/.test(ch)) prev = ch;
252
+ i += 1;
253
+ }
254
+ return { commas, end: code.length };
255
+ }
256
+ function declaratorName(segment) {
257
+ const match = /^\s*([A-Za-z_$][\w$]*)\s*(?:=(?![=>])|$)/.exec(segment);
258
+ return match === null ? null : match[1] ?? null;
259
+ }
260
+ function restDeclarators(code, from) {
261
+ const { commas, end } = scanDeclaration(code, from);
262
+ if (commas.length === 0) return [];
263
+ const segments = [];
264
+ let cursor = from;
265
+ for (const comma of commas) {
266
+ segments.push(code.slice(cursor, comma));
267
+ cursor = comma + 1;
268
+ }
269
+ segments.push(code.slice(cursor, end));
270
+ const names = [];
271
+ for (const segment of segments.slice(1)) {
272
+ const name = declaratorName(segment);
273
+ if (name === null) {
274
+ throw new Error(
275
+ `\u4E0D\u652F\u6301\u7684\u5BFC\u51FA\u58F0\u660E\u5F62\u6001\uFF1A"${segment.trim()}"\uFF08\u6C99\u7BB1\u5B50\u96C6\u4EC5\u63A5\u53D7\u9017\u53F7\u5206\u9694\u7684\u7B80\u5355\u6807\u8BC6\u7B26\u58F0\u660E\uFF0C\u5982 \`export const a = 1, b = 2\`\uFF1B\u542B\u89E3\u6784\u6216\u590D\u6742\u521D\u503C\u65F6\u8BF7\u62C6\u6210\u591A\u6761 export\uFF09`
276
+ );
277
+ }
278
+ names.push(name);
279
+ }
280
+ return names;
281
+ }
130
282
  function transformModule(code) {
131
283
  let out = code;
132
284
  const named = [];
@@ -189,10 +341,20 @@ function transformModule(code) {
189
341
  return `${lead}${indent}const ${temp} = require(${source})${pad}`;
190
342
  }
191
343
  );
344
+ const destructured = /(^|\n)[ \t]*export\s+(const|let|var)\s*[{[]/.exec(out);
345
+ if (destructured !== null) {
346
+ const offset = destructured.index + (destructured[1] === "\n" ? 1 : 0);
347
+ throw new Error(
348
+ `\u4E0D\u652F\u6301\u7684\u5BFC\u51FA\u58F0\u660E\u5F62\u6001\uFF08\u7B2C ${lineNumberAt(out, offset)} \u884C\uFF09\uFF1A\u89E3\u6784\u5BFC\u51FA \`export ${destructured[2] ?? ""} { \u2026 } = \u2026\`\u3002\u6C99\u7BB1\u5B50\u96C6\u4EC5\u63A5\u53D7\u7B80\u5355\u6807\u8BC6\u7B26\u58F0\u660E\uFF1A\u8BF7\u5148\u58F0\u660E\u518D \`export { a, b }\`\uFF0C\u6216\u62C6\u6210\u591A\u6761 \`export const\``
349
+ );
350
+ }
192
351
  out = out.replace(
193
352
  /export\s+(const|let|var)\s+([A-Za-z_$][\w$]*)/g,
194
- (_match, kw, name) => {
353
+ (match, kw, name, offset, source) => {
195
354
  named.push({ local: name, exported: name });
355
+ for (const extra of restDeclarators(source, offset + match.length)) {
356
+ named.push({ local: extra, exported: extra });
357
+ }
196
358
  return `${kw} ${name}`;
197
359
  }
198
360
  );
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.4.4",
10
+ "version": "0.4.6",
11
11
  "type": "module",
12
12
  "description": "`adep` 命令行工具包位:登录 / 初始化 / 本地调试 / 部署 / 数据库维护 / 云存储 / 静态托管 / 微前端组件(widget),实现见任务单 CLI-001 ~ CLI-003 与 FE-002;子路径 `@adep/cli/vite` 提供云函数 vite 插件(CLI-014)",
13
13
  "bin": {
@@ -29,11 +29,12 @@
29
29
  "commander": "14.0.2",
30
30
  "esbuild": "0.25.12",
31
31
  "yaml": "2.9.1",
32
- "zod": "^4.4.3",
33
- "@adep/cf-compat": "0.1.3",
34
- "@adep/runtime": "0.3.4",
35
- "@adep/crypto": "0.1.1",
36
- "@adep/vite-plugin": "0.1.9"
32
+ "zod": "^4.6.6",
33
+ "@adep/runtime": "0.3.5",
34
+ "@adep/app-runtime": "0.1.1",
35
+ "@adep/cf-compat": "0.1.4",
36
+ "@adep/vite-plugin": "0.1.9",
37
+ "@adep/crypto": "0.1.1"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@adep/types": "0.3.2"
@@ -18,7 +18,7 @@ GUI(前端产物)随包本地加载,云函数经 HTTP 在服务端执行
18
18
 
19
19
  1. 把平台项目的前端构建产物放进 `app/site/`(`adep app build` 会自动拉取已发布产物);
20
20
  2. `npm install`;
21
- 3. `npm start`(配置从 `adep.app.json` 读取,无需环境变量)。
21
+ 3. `npm start`(或仓库根的 `adep app open`,等价——直接调壳工程内的 electron;配置从 `adep.app.json` 读取,无需环境变量)。
22
22
 
23
23
  ## 页面侧调用云函数(在线运行时)
24
24
 
@@ -31,12 +31,77 @@ const client = createAdepClient({ baseUrl: window.__ADEP_APP__.platformUrl })
31
31
  const result = await client.invokeFunction('hello', { name: 'world' })
32
32
  ```
33
33
 
34
+ ## 设备身份与同步(离线构建之后)
35
+
36
+ 离线构建把设备端运行时注入 `app/offline/`,首次启动时在 `data/offline.sqlite` 建本地库:
37
+ 白名单函数在本地执行,写 owned 表时同时追加变更流。
38
+
39
+ **设备身份是首启自动签发的**(APP-033 / PRD §2.11.5):主进程启动后在后台调一次
40
+ `POST /api/v1/projects/<slug>/app-installations`(`platform` 按**宿主 OS** 映射为
41
+ `macos` / `windows` / `linux`——服务端没有 `desktop` 这个取值),拿到 `ownerKey` +
42
+ 一次性恢复码后写进受保护存储;此后每次启动只读不发请求。
43
+
44
+ ```text
45
+ identity.mjs 判定层(配置 / 端点 / 形态 / 响应解析 / 平台映射)+ 自举流程
46
+ identity-store.mjs 受保护存储:macOS 钥匙串 → Linux Secret Service → 0600 文件(明说降级)
47
+ recovery-ui.mjs 恢复码呈现与导出(APP-034;全在主进程,页面侧拿不到)
48
+ sync-loop.mjs 后台同步驱动(首轮延迟 → 每 5 分钟一轮;不重叠、失败不致命)
49
+ ```
50
+
51
+ 存储后端:
52
+
53
+ - macOS:`security add-generic-password`(登录钥匙串);
54
+ - Linux:`secret-tool`(Secret Service);
55
+ - **Windows 与「系统库不可用」**:退化到 `data/identity/` 下的 0600 文件,并在日志与
56
+ `adep app doctor` 里**明说「非系统凭据」**——不会静默假装是钥匙串。
57
+
58
+ 身份状态(**非机密**:只有存储类型 / 原因码 / 安装 id / 时间)写在
59
+ `data/identity/status.json`,`adep app doctor` 的 `identity` 项读它:
60
+
61
+ ```bash
62
+ adep app doctor # 已签发(系统凭据库 keychain) / 非系统凭据(file) / 首启签发失败(原因码)
63
+ ```
64
+
65
+ ⚠️ **页面侧拿不到 `ownerKey`**(与移动壳同一条边界):同步跑在主进程,渲染进程不需要这枚
66
+ bearer 凭据,给它等于「任意 XSS 都能劫持该设备的数据分区」。
67
+
68
+ ## 恢复码(APP-034:呈现 / 再次查看 / 导出)
69
+
70
+ 签发响应里的恢复码**只出现一次**(服务端只存摘要、任何端点不重发),壳负责把它递到用户手里:
71
+
72
+ - **首次呈现**:自举成功后自动弹一次原生对话框(码可读且没呈现过才弹——老安装升级上来
73
+ 同样有机会看到)。此后不再主动弹,标记写在 `data/identity/recovery-code-shown`;
74
+ - **再次查看 / 导出**:应用菜单 **「凭据」→「查看恢复码…」/「导出恢复码…」**(导出文件
75
+ 头部写明「服务端无法重发,丢了码等于放弃认领」);
76
+ - **复制到剪贴板**:两个对话框里都有一键复制。
77
+
78
+ 呈现与导出**全在主进程**(应用菜单 + 原生对话框):不开 IPC、不改 preload,恢复码与
79
+ `ownerKey` 一样不进渲染进程。码不可读时(未签发 / 存储失败)对话框会说明原因并指向
80
+ `adep app doctor`,不是静默空窗。
81
+
82
+ 身份就绪后主进程自动把同步挂到运行时上:
83
+
84
+ ```js
85
+ const runtime = await createOfflineRuntime() // 页面先可用
86
+ const sync = runtime.attachSync({ ownerKey }) // 身份到位后再挂(不重开库)
87
+ await sync.push() // 本地变更流 → outbox → 云端
88
+ await sync.pull() // 云端增量 → 本地库(直写驱动,不回灌 outbox)
89
+ ```
90
+
91
+ 手动同步(开发期 / 排障)仍用 CLI:`adep app sync --push`(身份取 `--owner-key` 或
92
+ `ADEP_OWNER_KEY`)。**不要**在 `adep.app.json` 或环境变量里编一个 key:本地自造会写出一个
93
+ 「看起来成功」的空分区(PRD §2.11.5 明确「本地自造判违规」)。
94
+
95
+ ⚠️ 已知限制(已登记):打包形态下 `data/` 仍在应用目录内(`.app` / Program Files),
96
+ 写入可能被系统拒绝——此时 `identity` 项会显示 `IDENTITY_STORE_FAILED`,属于**响亮失败**。
97
+ 把本地数据目录迁到 `app.getPath('userData')` 是独立的一批(含 `data/offline.sqlite`)。
98
+
34
99
  ## 打安装包
35
100
 
36
101
  推荐用 CLI(会自动拉取**已发布**的前端产物、回写产物版本、产出构建报告):
37
102
 
38
103
  ```bash
39
- adep app build # 当前平台、走 package.json build 段的 target(mac dmg / win nsis / linux AppImage)
104
+ adep app build # 当前平台、走 package.json build 段的 target(mac dmg / win nsis+zip / linux AppImage+deb)
40
105
  adep app build --target dir # 只出未压缩目录(macOS 即 .app),本地快速验证
41
106
  adep app build --prepare-only # 只拉产物 + 注入 + 出构建报告,不调 electron-builder
42
107
  ```