@adep/cli 0.4.4 → 0.4.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 (46) hide show
  1. package/README.md +107 -11
  2. package/dist/index.js +33409 -27038
  3. package/dist/vite.js +209 -15
  4. package/dist/worker-entry.js +163 -1
  5. package/package.json +5 -4
  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
@@ -742,10 +742,21 @@ function compileSelect(state, dialect) {
742
742
  if (state.orderBys.length > 0) {
743
743
  sql += ` ORDER BY ${state.orderBys.map((order) => `${dialect.quote(order.column)} ${order.direction.toUpperCase()}`).join(", ")}`;
744
744
  }
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)}`;
745
+ if (state.limit !== void 0) {
746
+ assertInlineableInt(state.limit, "limit");
747
+ sql += ` LIMIT ${state.limit}`;
748
+ }
749
+ if (state.offset !== void 0) {
750
+ assertInlineableInt(state.offset, "offset");
751
+ sql += ` OFFSET ${state.offset}`;
752
+ }
747
753
  return { sql, params };
748
754
  }
755
+ function assertInlineableInt(value, name) {
756
+ if (!Number.isInteger(value) || value < 0) {
757
+ throw unsafeOperation(`${name} \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570`);
758
+ }
759
+ }
749
760
  function compileCount(state, dialect) {
750
761
  const params = [];
751
762
  let sql = `SELECT count(*) AS n FROM ${dialect.quote(state.table)}`;
@@ -1299,11 +1310,21 @@ function parseWhereClauses(whereRaw, cursor) {
1299
1310
  const t = part.trim();
1300
1311
  if (t.length === 0) continue;
1301
1312
  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 });
1313
+ clauses.push({
1314
+ column: unqualifyColumn(ident(t.split(/\s+IS\s+NULL\b/i)[0] ?? "")),
1315
+ operator: "IS NULL",
1316
+ value: null,
1317
+ list: false
1318
+ });
1303
1319
  continue;
1304
1320
  }
1305
1321
  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 });
1322
+ clauses.push({
1323
+ column: unqualifyColumn(ident(t.split(/\s+IS\s+NOT\s+NULL\b/i)[0] ?? "")),
1324
+ operator: "IS NOT NULL",
1325
+ value: null,
1326
+ list: false
1327
+ });
1307
1328
  continue;
1308
1329
  }
1309
1330
  const opMatch = /^\s*((?:"[^"]+")|([A-Za-z_][A-Za-z0-9_.]*))\s*(=|!=|>=|<=|>|<|like|in|not\s+in)\s*(.+)$/i.exec(
@@ -1480,7 +1501,10 @@ function parseConflictClause(clause, cursor, fallbackCols) {
1480
1501
  clause.trim()
1481
1502
  );
1482
1503
  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`);
1504
+ throw new SimDbError(
1505
+ "DB_UNSAFE_OP",
1506
+ `\u65E0\u6CD5\u89E3\u6790 ON CONFLICT \u5B50\u53E5\uFF1A${clause.trim().slice(0, 60)}\u2026`
1507
+ );
1484
1508
  }
1485
1509
  const colsRaw = m[1];
1486
1510
  const cols = colsRaw === void 0 ? fallbackCols : colsRaw.split(",").map(ident);
@@ -1684,16 +1708,16 @@ var SimSqlEngine = class {
1684
1708
  row[col] = values[i] ?? null;
1685
1709
  });
1686
1710
  if (conflictSpec !== null) {
1687
- const existing = table.rows.find(
1688
- (r) => conflictSpec.cols.every((c) => r[c] === row[c])
1689
- );
1711
+ const existing = table.rows.find((r) => conflictSpec.cols.every((c) => r[c] === row[c]));
1690
1712
  if (existing !== void 0) {
1691
1713
  const oldRow = { ...existing };
1692
1714
  for (const set of conflictSpec.sets) {
1693
1715
  existing[set.col] = resolveSetValue(set.value, oldRow, row);
1694
1716
  }
1695
1717
  inserted += 1;
1696
- void this.persist();
1718
+ this.persist().catch((error) => {
1719
+ console.warn("[sim] \u6301\u4E45\u5316\u5931\u8D25\uFF1A", error);
1720
+ });
1697
1721
  continue;
1698
1722
  }
1699
1723
  }
@@ -1701,7 +1725,9 @@ var SimSqlEngine = class {
1701
1725
  table.rows.push(row);
1702
1726
  inserted += 1;
1703
1727
  }
1704
- void this.persist();
1728
+ this.persist().catch((error) => {
1729
+ console.warn("[sim] \u6301\u4E45\u5316\u5931\u8D25\uFF1A", error);
1730
+ });
1705
1731
  return { changes: inserted };
1706
1732
  }
1707
1733
  execUpdate(sql, params) {
@@ -1731,7 +1757,9 @@ var SimSqlEngine = class {
1731
1757
  }
1732
1758
  changes += 1;
1733
1759
  }
1734
- void this.persist();
1760
+ this.persist().catch((error) => {
1761
+ console.warn("[sim] \u6301\u4E45\u5316\u5931\u8D25\uFF1A", error);
1762
+ });
1735
1763
  return { changes };
1736
1764
  }
1737
1765
  execDelete(sql, params) {
@@ -1749,7 +1777,9 @@ var SimSqlEngine = class {
1749
1777
  else changes += 1;
1750
1778
  }
1751
1779
  table.rows = keep;
1752
- void this.persist();
1780
+ this.persist().catch((error) => {
1781
+ console.warn("[sim] \u6301\u4E45\u5316\u5931\u8D25\uFF1A", error);
1782
+ });
1753
1783
  return { changes };
1754
1784
  }
1755
1785
  select(sql, params) {
@@ -1771,7 +1801,9 @@ var SimSqlEngine = class {
1771
1801
  rows = table.rows.map((row) => {
1772
1802
  const merged = { ...row };
1773
1803
  for (const j of joins) {
1774
- const joined = this.tables[j.table]?.rows.find((jr) => jr[j.leftCol] === merged[j.rightCol]);
1804
+ const joined = this.tables[j.table]?.rows.find(
1805
+ (jr) => jr[j.leftCol] === merged[j.rightCol]
1806
+ );
1775
1807
  if (joined !== void 0) {
1776
1808
  for (const [k, v] of Object.entries(joined)) merged[`${j.alias}.${k}`] = v;
1777
1809
  }
@@ -2872,7 +2904,7 @@ function envDbFile() {
2872
2904
  function displayPath(cwd, target) {
2873
2905
  if (target === ":memory:") return target;
2874
2906
  const rel = relative(cwd, target);
2875
- return rel.startsWith("..") ? target : rel;
2907
+ return rel.startsWith("..") ? target : rel.split(/[\\/]/u).join("/");
2876
2908
  }
2877
2909
  function simDbTarget(cwd) {
2878
2910
  return displayPath(cwd, join5(simDir(cwd), "db.json"));
@@ -3660,6 +3692,158 @@ function parseExportClause(clause) {
3660
3692
  }
3661
3693
  return entries;
3662
3694
  }
3695
+ var CONTINUES_AFTER = "=+-*/%&|^<>?:,.[(";
3696
+ var CONTINUES_BEFORE = "`([.?:,+-*/%&|^<>=";
3697
+ var REGEX_PRECEDERS = "(,=:[!&|?{};+-*%~^<>";
3698
+ var MATCHING_OPEN = { ")": "(", "]": "[", "}": "{" };
3699
+ function lineNumberAt(code, index) {
3700
+ let line = 1;
3701
+ for (let i = 0; i < index; i += 1) if (code.charCodeAt(i) === 10) line += 1;
3702
+ return line;
3703
+ }
3704
+ function skipQuoted(code, start) {
3705
+ const quote = code.charAt(start);
3706
+ let i = start + 1;
3707
+ while (i < code.length) {
3708
+ const ch = code.charAt(i);
3709
+ if (ch === "\\") {
3710
+ i += 2;
3711
+ continue;
3712
+ }
3713
+ if (ch === quote) return i + 1;
3714
+ if (ch === "\n") return i;
3715
+ i += 1;
3716
+ }
3717
+ return code.length;
3718
+ }
3719
+ function skipRegex(code, start) {
3720
+ let i = start + 1;
3721
+ let inClass = false;
3722
+ while (i < code.length) {
3723
+ const ch = code.charAt(i);
3724
+ if (ch === "\\") {
3725
+ i += 2;
3726
+ continue;
3727
+ }
3728
+ if (ch === "\n") return start + 1;
3729
+ if (ch === "[") inClass = true;
3730
+ else if (ch === "]") inClass = false;
3731
+ else if (ch === "/" && !inClass) return i + 1;
3732
+ i += 1;
3733
+ }
3734
+ return code.length;
3735
+ }
3736
+ function regexCanStart(prev) {
3737
+ return prev === "" || REGEX_PRECEDERS.includes(prev);
3738
+ }
3739
+ function continuesOnto(code, newline, prev) {
3740
+ if (prev !== "" && CONTINUES_AFTER.includes(prev)) return true;
3741
+ let i = newline + 1;
3742
+ while (i < code.length && /\s/.test(code.charAt(i))) i += 1;
3743
+ const next = code.charAt(i);
3744
+ return next !== "" && CONTINUES_BEFORE.includes(next);
3745
+ }
3746
+ function scanDeclaration(code, from) {
3747
+ const commas = [];
3748
+ const stack = [];
3749
+ let prev = "";
3750
+ let i = from;
3751
+ while (i < code.length) {
3752
+ const ch = code.charAt(i);
3753
+ if (stack[stack.length - 1] === "`") {
3754
+ if (ch === "\\") {
3755
+ i += 2;
3756
+ continue;
3757
+ }
3758
+ if (ch === "`") {
3759
+ stack.pop();
3760
+ prev = "`";
3761
+ i += 1;
3762
+ continue;
3763
+ }
3764
+ if (ch === "$" && code.charAt(i + 1) === "{") {
3765
+ stack.push("{");
3766
+ prev = "{";
3767
+ i += 2;
3768
+ continue;
3769
+ }
3770
+ i += 1;
3771
+ continue;
3772
+ }
3773
+ if (ch === "/" && code.charAt(i + 1) === "/") {
3774
+ const newline = code.indexOf("\n", i);
3775
+ i = newline === -1 ? code.length : newline;
3776
+ continue;
3777
+ }
3778
+ if (ch === "/" && code.charAt(i + 1) === "*") {
3779
+ const close = code.indexOf("*/", i + 2);
3780
+ i = close === -1 ? code.length : close + 2;
3781
+ continue;
3782
+ }
3783
+ if (ch === '"' || ch === "'") {
3784
+ i = skipQuoted(code, i);
3785
+ prev = ch;
3786
+ continue;
3787
+ }
3788
+ if (ch === "`") {
3789
+ stack.push("`");
3790
+ i += 1;
3791
+ continue;
3792
+ }
3793
+ if (ch === "/" && regexCanStart(prev)) {
3794
+ i = skipRegex(code, i);
3795
+ prev = "/";
3796
+ continue;
3797
+ }
3798
+ if (ch === "(" || ch === "[" || ch === "{") {
3799
+ stack.push(ch);
3800
+ prev = ch;
3801
+ i += 1;
3802
+ continue;
3803
+ }
3804
+ if (ch === ")" || ch === "]" || ch === "}") {
3805
+ if (stack[stack.length - 1] !== MATCHING_OPEN[ch]) return { commas, end: i };
3806
+ stack.pop();
3807
+ prev = ch;
3808
+ i += 1;
3809
+ continue;
3810
+ }
3811
+ if (stack.length === 0) {
3812
+ if (ch === ";") return { commas, end: i };
3813
+ if (ch === ",") commas.push(i);
3814
+ if (ch === "\n" && !continuesOnto(code, i, prev)) return { commas, end: i };
3815
+ }
3816
+ if (!/\s/.test(ch)) prev = ch;
3817
+ i += 1;
3818
+ }
3819
+ return { commas, end: code.length };
3820
+ }
3821
+ function declaratorName(segment) {
3822
+ const match = /^\s*([A-Za-z_$][\w$]*)\s*(?:=(?![=>])|$)/.exec(segment);
3823
+ return match === null ? null : match[1] ?? null;
3824
+ }
3825
+ function restDeclarators(code, from) {
3826
+ const { commas, end } = scanDeclaration(code, from);
3827
+ if (commas.length === 0) return [];
3828
+ const segments = [];
3829
+ let cursor = from;
3830
+ for (const comma of commas) {
3831
+ segments.push(code.slice(cursor, comma));
3832
+ cursor = comma + 1;
3833
+ }
3834
+ segments.push(code.slice(cursor, end));
3835
+ const names = [];
3836
+ for (const segment of segments.slice(1)) {
3837
+ const name = declaratorName(segment);
3838
+ if (name === null) {
3839
+ throw new Error(
3840
+ `\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`
3841
+ );
3842
+ }
3843
+ names.push(name);
3844
+ }
3845
+ return names;
3846
+ }
3663
3847
  function transformModule(code) {
3664
3848
  let out = code;
3665
3849
  const named = [];
@@ -3722,10 +3906,20 @@ function transformModule(code) {
3722
3906
  return `${lead}${indent}const ${temp} = require(${source})${pad}`;
3723
3907
  }
3724
3908
  );
3909
+ const destructured = /(^|\n)[ \t]*export\s+(const|let|var)\s*[{[]/.exec(out);
3910
+ if (destructured !== null) {
3911
+ const offset = destructured.index + (destructured[1] === "\n" ? 1 : 0);
3912
+ throw new Error(
3913
+ `\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\``
3914
+ );
3915
+ }
3725
3916
  out = out.replace(
3726
3917
  /export\s+(const|let|var)\s+([A-Za-z_$][\w$]*)/g,
3727
- (_match, kw, name) => {
3918
+ (match, kw, name, offset, source) => {
3728
3919
  named.push({ local: name, exported: name });
3920
+ for (const extra of restDeclarators(source, offset + match.length)) {
3921
+ named.push({ local: extra, exported: extra });
3922
+ }
3729
3923
  return `${kw} ${name}`;
3730
3924
  }
3731
3925
  );
@@ -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.5",
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,9 +29,10 @@
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",
32
+ "zod": "^4.6.6",
33
+ "@adep/cf-compat": "0.1.4",
34
+ "@adep/app-runtime": "0.1.1",
35
+ "@adep/runtime": "0.3.5",
35
36
  "@adep/crypto": "0.1.1",
36
37
  "@adep/vite-plugin": "0.1.9"
37
38
  },
@@ -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
  ```