diff --git a/configs/jsactions/rollup.config.mjs b/configs/jsactions/rollup.config.mjs index d8691fac1..5b194955d 100644 --- a/configs/jsactions/rollup.config.mjs +++ b/configs/jsactions/rollup.config.mjs @@ -22,7 +22,9 @@ export default async args => { const jsActionTargetFolder = `javascriptsource/${args.configProject ?? "nativemobileresources"}/actions`; const result = []; const posixPath = join(cwd, "src", "**/*.ts").split(sep).join(posix.sep); // Always use forward slashes - const files = await fg([posixPath]); // fast-glob only works with forward slashes + // `src/native` holds shared helpers, not actions, so it gets no bundle of its own; it lives under + // `src` because these packages have no tsconfig and TypeScript infers the source root from there. + const files = await fg([posixPath], { ignore: ["**/src/native/**"] }); // fast-glob only works with forward slashes const outDir = join(cwd, "dist"); const nodeResolvePlugin = nodeResolve({ preferBuiltins: false, mainFields: ["module", "browser", "main"] }); @@ -143,6 +145,7 @@ export default async args => { const nativeExternal = [ /^mendix\//, + /^mendix-native(\/|$)/, /^react-native(\/|$)/, /^react-native-windows(\/|$)/, /^react-native-web(\/|$)/, diff --git a/eslint.config.js b/eslint.config.js index 089556974..371e2a7f2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -19,5 +19,37 @@ module.exports = [ ] }, // Then extend from the legacy .eslintrc.js file - ...compat.extends(path.resolve(__dirname, ".eslintrc.js")) -]; \ No newline at end of file + ...compat.extends(path.resolve(__dirname, ".eslintrc.js")), + // Nanoflow Commons actions also run on web, where `mendix-native` does not resolve at all, so a + // static import would break the web bundle even on paths that never touch native. These actions + // must go through src/native/mendix-native.ts, which owns the lazy import; src/native itself is + // exempt. Native-only packages such as mobile-resources-native import `mendix-native` directly. + { + files: ["packages/jsActions/nanoflow-actions-native/src/**/*.{ts,tsx,js,jsx}"], + ignores: ["packages/jsActions/nanoflow-actions-native/src/native/**"], + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [ + { + regex: "^mendix-native(/|$)", + message: + "Import from src/native/mendix-native instead, which owns the import of 'mendix-native'. In Nanoflow Commons a direct import also breaks the web bundle." + } + ] + } + ], + // no-restricted-imports does not flag `import()` expressions, so the dynamic form is + // matched separately. + "no-restricted-syntax": [ + "error", + { + selector: "ImportExpression > Literal[value=/^mendix-native(\\u002F|$)/]", + message: + "Import from src/native/mendix-native instead; that module owns the dynamic import of 'mendix-native'." + } + ] + } + } +]; diff --git a/packages/jsActions/mobile-resources-native/package.json b/packages/jsActions/mobile-resources-native/package.json index 0e6ec7bbb..cb1fb62d8 100644 --- a/packages/jsActions/mobile-resources-native/package.json +++ b/packages/jsActions/mobile-resources-native/package.json @@ -49,6 +49,7 @@ "@types/querystringify": "^2.0.0", "@types/url-parse": "^1.4.3", "mendix": "11.8.0", + "mendix-native": "https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz", "rimraf": "^6.1.2" } -} \ No newline at end of file +} diff --git a/packages/jsActions/mobile-resources-native/src/camera/TakePicture.ts b/packages/jsActions/mobile-resources-native/src/camera/TakePicture.ts index f80254be5..a41d3d835 100644 --- a/packages/jsActions/mobile-resources-native/src/camera/TakePicture.ts +++ b/packages/jsActions/mobile-resources-native/src/camera/TakePicture.ts @@ -7,6 +7,7 @@ // Other code you write will be lost the next time you deploy the project. import { Big } from "big.js"; import { Alert, Linking, NativeModules, Platform } from "react-native"; +import { NativeFileSystem } from "mendix-native"; import { CameraOptions, ErrorCode, @@ -118,7 +119,7 @@ export async function TakePicture( async function safeRemove(filePath: string): Promise { try { - await NativeModules.MxFileSystem.remove(filePath); + await NativeFileSystem.remove(filePath); } catch (error) { console.warn(`Failed to remove file at ${filePath}. Error: ${error}`); // ignore error @@ -127,8 +128,8 @@ export async function TakePicture( function storeFile(imageObject: mendix.lib.MxObject, uri: string): Promise { return new Promise((resolve, reject) => { - NativeModules.MxFileSystem.read(uri.replace("file://", "")) - .then((nativeBlob: unknown) => { + NativeFileSystem.read(uri.replace("file://", "")) + .then(nativeBlob => { const blob = new Blob(); Object.assign(blob, { data: nativeBlob }); // eslint-disable-next-line no-useless-escape diff --git a/packages/jsActions/mobile-resources-native/src/camera/TakePictureAdvanced.ts b/packages/jsActions/mobile-resources-native/src/camera/TakePictureAdvanced.ts index d283dab5f..66d4d3228 100644 --- a/packages/jsActions/mobile-resources-native/src/camera/TakePictureAdvanced.ts +++ b/packages/jsActions/mobile-resources-native/src/camera/TakePictureAdvanced.ts @@ -7,6 +7,7 @@ // Other code you write will be lost the next time you deploy the project. import { Big } from "big.js"; import { Alert, Linking, NativeModules, Platform } from "react-native"; +import { NativeFileSystem } from "mendix-native"; import { CameraOptions, ErrorCode, @@ -171,7 +172,7 @@ export async function TakePictureAdvanced( async function safeRemove(filePath: string): Promise { try { - await NativeModules.MxFileSystem.remove(filePath); + await NativeFileSystem.remove(filePath); } catch (error) { console.warn(`Failed to remove file at ${filePath}. Error: ${error}`); // ignore error @@ -180,8 +181,8 @@ export async function TakePictureAdvanced( function storeFile(imageObject: mendix.lib.MxObject, uri: string): Promise { return new Promise((resolve, reject) => { - NativeModules.MxFileSystem.read(uri.replace("file://", "")) - .then((nativeBlob: unknown) => { + NativeFileSystem.read(uri.replace("file://", "")) + .then(nativeBlob => { const blob = new Blob(); Object.assign(blob, { data: nativeBlob }); // eslint-disable-next-line no-useless-escape diff --git a/packages/jsActions/nanoflow-actions-native/package.json b/packages/jsActions/nanoflow-actions-native/package.json index 45b34d15a..3d14fa741 100644 --- a/packages/jsActions/nanoflow-actions-native/package.json +++ b/packages/jsActions/nanoflow-actions-native/package.json @@ -35,6 +35,7 @@ }, "devDependencies": { "@mendix/pluggable-widgets-tools": "*", - "mendix": "11.8.0" + "mendix": "11.8.0", + "mendix-native": "https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz" } -} \ No newline at end of file +} diff --git a/packages/jsActions/nanoflow-actions-native/src/native/mendix-native.ts b/packages/jsActions/nanoflow-actions-native/src/native/mendix-native.ts new file mode 100644 index 000000000..e019ff9e0 --- /dev/null +++ b/packages/jsActions/nanoflow-actions-native/src/native/mendix-native.ts @@ -0,0 +1,10 @@ +// Single entry point for `mendix-native`. +// +// The package is provided at runtime by native-template (native code) and the AppDev client (JS), +// and is listed in `nativeExternal` in configs/jsactions/rollup.config.mjs so it is never bundled +// into the MPK. Nanoflow Commons actions also run on web, where the module does not resolve at all, +// so actions must reach it through the dynamic import below instead of a static one. Rollup inlines +// this module into each action bundle and leaves the `import()` untouched. +export type MendixNative = typeof import("mendix-native"); + +export const loadMendixNative = async (): Promise => import("mendix-native"); diff --git a/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts b/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts index 8a8f5c4cf..7d0f8e2ec 100644 --- a/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts +++ b/packages/jsActions/nanoflow-actions-native/src/other/Base64DecodeToImage.ts @@ -7,7 +7,7 @@ // Other code you write will be lost the next time you deploy the project. import { Base64 } from "js-base64"; import RNBlobUtil from "react-native-blob-util"; -import { NativeModules } from "react-native"; +import { loadMendixNative } from "../native/mendix-native"; // BEGIN EXTRA CODE // END EXTRA CODE @@ -56,7 +56,8 @@ export async function Base64DecodeToImage(base64: string, image: mendix.lib.MxOb // NativeFileBackend.storeFile calls NativeFileSystem.save(blob.data, path) // and blob.close() — a plain object has no .data getter or .close(), which // crashes iOS via [NSInvocation invokeWithTarget:]. - const nativeBlob = await NativeModules.MxFileSystem.read(tempPath.replace("file://", "")); + const { NativeFileSystem } = await loadMendixNative(); + const nativeBlob = await NativeFileSystem.read(tempPath.replace("file://", "")); // Normalize: MxFileSystem.read may return 'length' instead of 'size'. const blobData = { ...(nativeBlob as any) }; if (blobData.size === undefined && blobData.length !== undefined) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d3dd5bbb..d24c9956c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,6 +186,9 @@ importers: mendix: specifier: 11.8.0 version: 11.8.0 + mendix-native: + specifier: https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz + version: https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz(@op-engineering/op-sqlite@18.1.4(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(@react-native-async-storage/async-storage@2.2.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)))(react-native-gesture-handler@2.31.2(patch_hash=5be6c2cce8be6bd8afdbbf6c289d8b210686eafce0b41bf8cae2c29388aaa3b8)(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) rimraf: specifier: ^6.1.2 version: 6.1.3 @@ -217,6 +220,9 @@ importers: mendix: specifier: 11.8.0 version: 11.8.0 + mendix-native: + specifier: https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz + version: https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz(@op-engineering/op-sqlite@18.1.4(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(@react-native-async-storage/async-storage@2.2.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)))(react-native-gesture-handler@2.31.2(patch_hash=5be6c2cce8be6bd8afdbbf6c289d8b210686eafce0b41bf8cae2c29388aaa3b8)(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) packages/modules/atlas-content-native: devDependencies: @@ -2062,6 +2068,16 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@op-engineering/op-sqlite@18.1.4': + resolution: {integrity: sha512-8gjHXMyigCSDy/xdn46SQdnOW9pgJuTyrxqZj9ro75Lkp6dg+xs8wzyO+QH+khTWkY6TSofP3NcttJ1uiLdUFA==} + peerDependencies: + '@sqlite.org/sqlite-wasm': '*' + react: 19.2.3 + react-native: 0.84.1 + peerDependenciesMeta: + '@sqlite.org/sqlite-wasm': + optional: true + '@parcel/watcher-android-arm64@2.6.0': resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} engines: {node: '>= 10.0.0'} @@ -5452,6 +5468,16 @@ packages: mendix-client@7.15.8: resolution: {integrity: sha512-RazCdCHoLVNKUUeKDkSkIL6Lxx6fUaa4iiy+Ltp9ra8mLQhwyNqD33TIN7YZJ3HDjHc3eWh9cjiZWwh6Jg/cQg==} + mendix-native@https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz: + resolution: {integrity: sha512-CqGEqSy+c+zoYHmphVg5CXR9JdAr7wZkkjgPgO41Pfar9KESIKcE7frfk+MHlpnhZH254xuH19XkIBQGpuFNeA==, tarball: https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz} + version: 0.5.2 + peerDependencies: + '@op-engineering/op-sqlite': '*' + '@react-native-async-storage/async-storage': '*' + react: 19.2.3 + react-native: 0.84.1 + react-native-gesture-handler: '*' + mendix@11.8.0: resolution: {integrity: sha512-txq9wAuVcXpGCCbBzwtmb7afFpQzv4j6qccDoS1eMYe1JH9Pu/jpe4EsyfSSimoSgtSi55iG9wa+Iuy2M2RDgw==} @@ -9439,6 +9465,11 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@op-engineering/op-sqlite@18.1.4(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + dependencies: + react: 19.2.3 + react-native: 0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + '@parcel/watcher-android-arm64@2.6.0': optional: true @@ -13417,6 +13448,14 @@ snapshots: '@types/big.js': 0.0.31 '@types/dojo': 1.9.49 + mendix-native@https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz(@op-engineering/op-sqlite@18.1.4(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(@react-native-async-storage/async-storage@2.2.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)))(react-native-gesture-handler@2.31.2(patch_hash=5be6c2cce8be6bd8afdbbf6c289d8b210686eafce0b41bf8cae2c29388aaa3b8)(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + '@op-engineering/op-sqlite': 18.1.4(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@react-native-async-storage/async-storage': 2.2.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + react: 19.2.3 + react-native: 0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + react-native-gesture-handler: 2.31.2(patch_hash=5be6c2cce8be6bd8afdbbf6c289d8b210686eafce0b41bf8cae2c29388aaa3b8)(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + mendix@11.8.0: dependencies: '@types/big.js': 6.2.2