miaoda-expo-devkit 0.1.1-beta.52 → 0.1.1-beta.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/biome-config.json CHANGED
@@ -8,6 +8,9 @@
8
8
  "includes": [
9
9
  "src/**/*.{js,jsx,ts,tsx,css,scss}",
10
10
  "tailwind.config.js"
11
+ ],
12
+ "experimentalScannerIgnores": [
13
+ "src/components/ui/**"
11
14
  ]
12
15
  },
13
16
  "linter": {
package/dist/metro.d.mts CHANGED
@@ -298,14 +298,20 @@ declare function withExpoMediaLibraryStub(config: MetroConfig): MetroConfig;
298
298
  declare function withExpoCalendarStub(config: MetroConfig): MetroConfig;
299
299
 
300
300
  /**
301
- * withExpoFileSystemStub — Web 端将 expo-file-system/legacy 替换为 stub
301
+ * withExpoFileSystemStub — Web 端将 expo-file-system 系列模块替换为 stub
302
+ *
303
+ * expo-file-system 的文件 API 在 Web 端不可用:
304
+ * - legacy API:底层 shim 方法均缺失(会抛 UnavailabilityError)
305
+ * - 新版 API(File / Directory / Paths):ExpoFileSystem.web.ts 基类缺少 validatePath(),
306
+ * new File(...) / new Directory(...) 会抛 TypeError: this.validatePath is not a function
302
307
  *
303
- * expo-file-system 的文件 API 在 Web 端不可用(底层 shim 方法均缺失)。
304
308
  * stub 在运行时通过 Platform.OS 判断:
305
309
  * - Web:no-op(核心 API 弹 Dialog 提示,不崩溃)
306
- * - Expo Go / Development Build(原生):透传真实 expo-file-system/legacy,功能完全正常
310
+ * - Expo Go / Development Build(原生):透传真实模块,功能完全正常
307
311
  *
308
- * 注意:此 wrapper 拦截 expo-file-system/legacy 子路径,不拦截新版 expo-file-system(File API)。
312
+ * 拦截路径:
313
+ * - expo-file-system/legacy → expo-file-system-stub.js (legacy API)
314
+ * - expo-file-system → expo-file-system-next-stub.js (新版 File/Directory/Paths API)
309
315
  */
310
316
 
311
317
  /**
package/dist/metro.d.ts CHANGED
@@ -298,14 +298,20 @@ declare function withExpoMediaLibraryStub(config: MetroConfig): MetroConfig;
298
298
  declare function withExpoCalendarStub(config: MetroConfig): MetroConfig;
299
299
 
300
300
  /**
301
- * withExpoFileSystemStub — Web 端将 expo-file-system/legacy 替换为 stub
301
+ * withExpoFileSystemStub — Web 端将 expo-file-system 系列模块替换为 stub
302
+ *
303
+ * expo-file-system 的文件 API 在 Web 端不可用:
304
+ * - legacy API:底层 shim 方法均缺失(会抛 UnavailabilityError)
305
+ * - 新版 API(File / Directory / Paths):ExpoFileSystem.web.ts 基类缺少 validatePath(),
306
+ * new File(...) / new Directory(...) 会抛 TypeError: this.validatePath is not a function
302
307
  *
303
- * expo-file-system 的文件 API 在 Web 端不可用(底层 shim 方法均缺失)。
304
308
  * stub 在运行时通过 Platform.OS 判断:
305
309
  * - Web:no-op(核心 API 弹 Dialog 提示,不崩溃)
306
- * - Expo Go / Development Build(原生):透传真实 expo-file-system/legacy,功能完全正常
310
+ * - Expo Go / Development Build(原生):透传真实模块,功能完全正常
307
311
  *
308
- * 注意:此 wrapper 拦截 expo-file-system/legacy 子路径,不拦截新版 expo-file-system(File API)。
312
+ * 拦截路径:
313
+ * - expo-file-system/legacy → expo-file-system-stub.js (legacy API)
314
+ * - expo-file-system → expo-file-system-next-stub.js (新版 File/Directory/Paths API)
309
315
  */
310
316
 
311
317
  /**
package/dist/metro.js CHANGED
@@ -410,17 +410,18 @@ function withExpoCalendarStub(config) {
410
410
 
411
411
  // src/metro/withExpoFileSystemStub.ts
412
412
  var import_path11 = __toESM(require("path"));
413
- var EXPO_FILE_SYSTEM_STUB_FILENAME = "expo-file-system-stub.js";
414
- var EXPO_FILE_SYSTEM_STUB_PATH = import_path11.default.resolve(
415
- __dirname,
416
- "stubs",
417
- EXPO_FILE_SYSTEM_STUB_FILENAME
418
- );
413
+ var LEGACY_STUB_FILENAME = "expo-file-system-stub.js";
414
+ var LEGACY_STUB_PATH = import_path11.default.resolve(__dirname, "stubs", LEGACY_STUB_FILENAME);
415
+ var NEXT_STUB_FILENAME = "expo-file-system-next-stub.js";
416
+ var NEXT_STUB_PATH = import_path11.default.resolve(__dirname, "stubs", NEXT_STUB_FILENAME);
419
417
  function withExpoFileSystemStub(config) {
420
418
  const upstream = config.resolver?.resolveRequest ?? null;
421
419
  const resolveRequest = (context, moduleName, platform) => {
422
- if (moduleName === "expo-file-system/legacy" && !context.originModulePath.includes(EXPO_FILE_SYSTEM_STUB_FILENAME)) {
423
- return { filePath: EXPO_FILE_SYSTEM_STUB_PATH, type: "sourceFile" };
420
+ if (moduleName === "expo-file-system/legacy" && !context.originModulePath.includes(LEGACY_STUB_FILENAME)) {
421
+ return { filePath: LEGACY_STUB_PATH, type: "sourceFile" };
422
+ }
423
+ if (moduleName === "expo-file-system" && !context.originModulePath.includes(NEXT_STUB_FILENAME)) {
424
+ return { filePath: NEXT_STUB_PATH, type: "sourceFile" };
424
425
  }
425
426
  if (upstream) return upstream(context, moduleName, platform);
426
427
  return context.resolveRequest(context, moduleName, platform);
package/dist/metro.mjs CHANGED
@@ -365,17 +365,18 @@ function withExpoCalendarStub(config) {
365
365
 
366
366
  // src/metro/withExpoFileSystemStub.ts
367
367
  import path11 from "path";
368
- var EXPO_FILE_SYSTEM_STUB_FILENAME = "expo-file-system-stub.js";
369
- var EXPO_FILE_SYSTEM_STUB_PATH = path11.resolve(
370
- __dirname,
371
- "stubs",
372
- EXPO_FILE_SYSTEM_STUB_FILENAME
373
- );
368
+ var LEGACY_STUB_FILENAME = "expo-file-system-stub.js";
369
+ var LEGACY_STUB_PATH = path11.resolve(__dirname, "stubs", LEGACY_STUB_FILENAME);
370
+ var NEXT_STUB_FILENAME = "expo-file-system-next-stub.js";
371
+ var NEXT_STUB_PATH = path11.resolve(__dirname, "stubs", NEXT_STUB_FILENAME);
374
372
  function withExpoFileSystemStub(config) {
375
373
  const upstream = config.resolver?.resolveRequest ?? null;
376
374
  const resolveRequest = (context, moduleName, platform) => {
377
- if (moduleName === "expo-file-system/legacy" && !context.originModulePath.includes(EXPO_FILE_SYSTEM_STUB_FILENAME)) {
378
- return { filePath: EXPO_FILE_SYSTEM_STUB_PATH, type: "sourceFile" };
375
+ if (moduleName === "expo-file-system/legacy" && !context.originModulePath.includes(LEGACY_STUB_FILENAME)) {
376
+ return { filePath: LEGACY_STUB_PATH, type: "sourceFile" };
377
+ }
378
+ if (moduleName === "expo-file-system" && !context.originModulePath.includes(NEXT_STUB_FILENAME)) {
379
+ return { filePath: NEXT_STUB_PATH, type: "sourceFile" };
379
380
  }
380
381
  if (upstream) return upstream(context, moduleName, platform);
381
382
  return context.resolveRequest(context, moduleName, platform);
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ var import_react_native = require("react-native");
3
+ var import_web_stub_dialog = require("./web-stub-dialog");
4
+ if (import_react_native.Platform.OS !== "web") {
5
+ module.exports = require("expo-file-system");
6
+ } else {
7
+ let joinUris = function(...parts) {
8
+ const strings = parts.map((p) => typeof p === "string" ? p : p.uri);
9
+ if (strings.length === 0) return "";
10
+ let result = strings[0] ?? "";
11
+ for (let i = 1; i < strings.length; i++) {
12
+ const part = strings[i] ?? "";
13
+ result = result.replace(/\/?$/, "/") + part.replace(/^\//, "");
14
+ }
15
+ return result;
16
+ }, uriBasename = function(uri) {
17
+ return uri.split("/").filter(Boolean).pop() ?? "";
18
+ }, uriDirname = function(uri) {
19
+ const parts = uri.split("/");
20
+ parts.pop();
21
+ return parts.join("/") || "/";
22
+ };
23
+ var joinUris2 = joinUris, uriBasename2 = uriBasename, uriDirname2 = uriDirname;
24
+ console.warn("[devkit] expo-file-system-next-stub loaded (web)");
25
+ class Directory {
26
+ constructor(...uris) {
27
+ this.uri = joinUris(...uris);
28
+ }
29
+ /** 真实基类方法,此处 no-op 防止 TypeError */
30
+ validatePath() {
31
+ }
32
+ get exists() {
33
+ return false;
34
+ }
35
+ create(_options) {
36
+ (0, import_web_stub_dialog.showWebStubDialog)({ title: "\u521B\u5EFA\u76EE\u5F55", details: [`URI: ${this.uri}`] });
37
+ }
38
+ delete() {
39
+ (0, import_web_stub_dialog.showWebStubDialog)({ title: "\u5220\u9664\u76EE\u5F55", details: [`URI: ${this.uri}`] });
40
+ }
41
+ list() {
42
+ return [];
43
+ }
44
+ listAsRecords() {
45
+ return [];
46
+ }
47
+ get name() {
48
+ return uriBasename(this.uri);
49
+ }
50
+ get parentDirectory() {
51
+ return new Directory(uriDirname(this.uri));
52
+ }
53
+ createFile(name, _mimeType) {
54
+ return new File(this, name);
55
+ }
56
+ createDirectory(name) {
57
+ return new Directory(this, name);
58
+ }
59
+ }
60
+ class File {
61
+ constructor(...uris) {
62
+ this.uri = joinUris(...uris);
63
+ }
64
+ /** 真实基类方法,此处 no-op 防止 TypeError */
65
+ validatePath() {
66
+ }
67
+ get exists() {
68
+ return false;
69
+ }
70
+ get size() {
71
+ return 0;
72
+ }
73
+ get type() {
74
+ return "";
75
+ }
76
+ get extension() {
77
+ const name = uriBasename(this.uri);
78
+ const dot = name.lastIndexOf(".");
79
+ return dot >= 0 ? name.slice(dot) : "";
80
+ }
81
+ get name() {
82
+ return uriBasename(this.uri);
83
+ }
84
+ get parentDirectory() {
85
+ return new Directory(uriDirname(this.uri));
86
+ }
87
+ copy(destination) {
88
+ (0, import_web_stub_dialog.showWebStubDialog)({
89
+ title: "\u590D\u5236\u6587\u4EF6",
90
+ details: [`\u4ECE: ${this.uri}`, `\u5230: ${destination.uri}`]
91
+ });
92
+ }
93
+ move(destination) {
94
+ (0, import_web_stub_dialog.showWebStubDialog)({
95
+ title: "\u79FB\u52A8\u6587\u4EF6",
96
+ details: [`\u4ECE: ${this.uri}`, `\u5230: ${destination.uri}`]
97
+ });
98
+ }
99
+ delete() {
100
+ (0, import_web_stub_dialog.showWebStubDialog)({ title: "\u5220\u9664\u6587\u4EF6", details: [`URI: ${this.uri}`] });
101
+ }
102
+ open() {
103
+ return null;
104
+ }
105
+ // ─── Blob interface(Web 环境所需,返回空值)
106
+ async arrayBuffer() {
107
+ return new ArrayBuffer(0);
108
+ }
109
+ async text() {
110
+ return "";
111
+ }
112
+ async bytes() {
113
+ return new Uint8Array(0);
114
+ }
115
+ bytesSync() {
116
+ return new Uint8Array(0);
117
+ }
118
+ slice(_start, _end, _contentType) {
119
+ return new Blob([]);
120
+ }
121
+ stream() {
122
+ return new ReadableStream();
123
+ }
124
+ readableStream() {
125
+ return new ReadableStream();
126
+ }
127
+ writableStream() {
128
+ return new WritableStream();
129
+ }
130
+ }
131
+ File.downloadFileAsync = async (_url, destination) => {
132
+ (0, import_web_stub_dialog.showWebStubDialog)({ title: "\u4E0B\u8F7D\u6587\u4EF6", details: [`\u76EE\u6807: ${destination.uri}`] });
133
+ return new File(destination.uri);
134
+ };
135
+ File.pickFileAsync = async () => {
136
+ (0, import_web_stub_dialog.showWebStubDialog)({ title: "\u9009\u62E9\u6587\u4EF6", details: [] });
137
+ return new File("document:/stub-pick");
138
+ };
139
+ Directory.pickDirectoryAsync = async () => {
140
+ (0, import_web_stub_dialog.showWebStubDialog)({ title: "\u9009\u62E9\u76EE\u5F55", details: [] });
141
+ return new Directory("document:/stub-pick");
142
+ };
143
+ const Paths = {
144
+ get document() {
145
+ return new Directory("document:/");
146
+ },
147
+ get cache() {
148
+ return new Directory("cache:/");
149
+ },
150
+ get bundle() {
151
+ return new Directory("bundle:/");
152
+ },
153
+ get totalDiskSpace() {
154
+ return 0;
155
+ },
156
+ get availableDiskSpace() {
157
+ return 0;
158
+ },
159
+ get appleSharedContainers() {
160
+ return {};
161
+ },
162
+ join(...uris) {
163
+ return joinUris(...uris);
164
+ },
165
+ basename(uri) {
166
+ return uriBasename(uri);
167
+ },
168
+ dirname(uri) {
169
+ return uriDirname(uri);
170
+ },
171
+ extname(uri) {
172
+ const name = uriBasename(uri);
173
+ const dot = name.lastIndexOf(".");
174
+ return dot >= 0 ? name.slice(dot) : "";
175
+ },
176
+ normalize(uri) {
177
+ return uri;
178
+ },
179
+ isAbsolute(_uri) {
180
+ return true;
181
+ },
182
+ resolve(...uris) {
183
+ return joinUris(...uris);
184
+ },
185
+ relative(_from, to) {
186
+ return to;
187
+ },
188
+ info(_uri) {
189
+ return { isDirectory: false };
190
+ },
191
+ sep: "/",
192
+ delimiter: ":"
193
+ };
194
+ const handlers = { File, Directory, Paths };
195
+ const noop = async () => void 0;
196
+ module.exports = new Proxy(handlers, {
197
+ get(target, key) {
198
+ if (key in target) return target[key];
199
+ return noop;
200
+ }
201
+ });
202
+ }
203
+ //# sourceMappingURL=expo-file-system-next-stub.js.map
@@ -105,6 +105,18 @@ function openCameraDialog(facingMode) {
105
105
  "font-weight:600",
106
106
  "font-family:sans-serif"
107
107
  ].join(";");
108
+ const btnLocal = document.createElement("button");
109
+ btnLocal.textContent = "\u{1F4C1} \u672C\u5730\u9009\u62E9";
110
+ btnLocal.style.cssText = [
111
+ "padding:10px 20px",
112
+ "border-radius:8px",
113
+ "border:1px solid #7C3AED",
114
+ "cursor:pointer",
115
+ "background:transparent",
116
+ "color:#a78bfa",
117
+ "font-size:15px",
118
+ "font-family:sans-serif"
119
+ ].join(";");
108
120
  const btnCancel = document.createElement("button");
109
121
  btnCancel.textContent = "\u53D6\u6D88";
110
122
  btnCancel.style.cssText = [
@@ -120,6 +132,7 @@ function openCameraDialog(facingMode) {
120
132
  const errorMsg = document.createElement("p");
121
133
  errorMsg.style.cssText = "margin:0;color:#f87171;font-size:13px;font-family:sans-serif;display:none";
122
134
  btnRow.appendChild(btnCapture);
135
+ btnRow.appendChild(btnLocal);
123
136
  btnRow.appendChild(btnCancel);
124
137
  body.appendChild(video);
125
138
  body.appendChild(btnRow);
@@ -156,6 +169,32 @@ function openCameraDialog(facingMode) {
156
169
  cleanup();
157
170
  resolve({ canceled: false, dataUrl, mimeType: "image/jpeg" });
158
171
  });
172
+ btnLocal.addEventListener("click", () => {
173
+ const fileInput = document.createElement("input");
174
+ fileInput.type = "file";
175
+ fileInput.accept = "image/*";
176
+ fileInput.addEventListener("change", () => {
177
+ const file = fileInput.files?.[0];
178
+ if (!file) return;
179
+ const reader = new FileReader();
180
+ reader.onload = (e) => {
181
+ const dataUrl = e.target?.result;
182
+ if (!dataUrl) {
183
+ cleanup();
184
+ resolve({ canceled: true });
185
+ return;
186
+ }
187
+ cleanup();
188
+ resolve({ canceled: false, dataUrl, mimeType: file.type || "image/jpeg" });
189
+ };
190
+ reader.onerror = () => {
191
+ cleanup();
192
+ resolve({ canceled: true });
193
+ };
194
+ reader.readAsDataURL(file);
195
+ });
196
+ fileInput.click();
197
+ });
159
198
  btnCancel.addEventListener("click", () => {
160
199
  cleanup();
161
200
  resolve({ canceled: true });
@@ -207,9 +246,6 @@ if (import_react_native.Platform.OS === "web") {
207
246
  showMobilePassthroughToast();
208
247
  return ExpoImagePicker.launchCameraAsync(options);
209
248
  }
210
- if (typeof navigator?.mediaDevices?.getUserMedia !== "function") {
211
- return ExpoImagePicker.launchCameraAsync(options);
212
- }
213
249
  const facingMode = getFacingMode(options);
214
250
  const result = await openCameraDialog(facingMode);
215
251
  if (result.canceled || !result.dataUrl) {
@@ -51,7 +51,8 @@
51
51
  "babel.config.js",
52
52
  ".pnpmfile.cjs",
53
53
  "output/**",
54
- "supabase/**"
54
+ "supabase/**",
55
+ "src/components/ui/**"
55
56
  ],
56
57
 
57
58
  "overrides": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miaoda-expo-devkit",
3
- "version": "0.1.1-beta.52",
3
+ "version": "0.1.1-beta.54",
4
4
  "description": "Expo 应用开发工具集:Sentry DSN 替换 stub、错误/网络捕获、Metro 符号化",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",