Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions app/metro.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const path = require('node:path');
const fs = require('node:fs');
const findYarnWorkspaceRoot = require('find-yarn-workspace-root');

const defaultConfig = getDefaultConfig(__dirname);
Expand Down Expand Up @@ -112,6 +113,35 @@ const config = {
'react-native-gesture-handler':
'react-native-gesture-handler/lib/commonjs/index.js',
};
const sdkAlphaPath = path.resolve(
workspaceRoot,
'packages/mobile-sdk-alpha',
);

// Custom resolver to handle Node.js modules and dynamic flow imports
if (moduleName.startsWith('@selfxyz/mobile-sdk-alpha/')) {
const subPath = moduleName.replace('@selfxyz/mobile-sdk-alpha/', '');

// Check if it's a flow import (onboarding/* or disclosing/*)
if (
subPath.startsWith('onboarding/') ||
subPath.startsWith('disclosing/')
) {
const flowPath = path.resolve(
sdkAlphaPath,
'dist/esm/flows',
`${subPath}.js`,
);

// Check if the file exists
if (fs.existsSync(flowPath)) {
return {
type: 'sourceFile',
filePath: flowPath,
};
}
}
}
Comment on lines +121 to +144
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Harden flow resolution: add try/catch and index.js fallback; always delegate on miss.

Prevent resolver breakage and support folder indexes.

Apply this diff:

       if (moduleName.startsWith('@selfxyz/mobile-sdk-alpha/')) {
         const subPath = moduleName.replace('@selfxyz/mobile-sdk-alpha/', '');
 
         // Check if it's a flow import (onboarding/* or disclosing/*)
         if (
           subPath.startsWith('onboarding/') ||
           subPath.startsWith('disclosing/')
         ) {
-          const flowPath = path.resolve(
-            sdkAlphaPath,
-            'dist/esm/flows',
-            `${subPath}.js`,
-          );
-
-          // Check if the file exists
-          if (fs.existsSync(flowPath)) {
-            return {
-              type: 'sourceFile',
-              filePath: flowPath,
-            };
-          }
+          try {
+            const flowPath = path.resolve(
+              sdkAlphaPath,
+              'dist/esm/flows',
+              `${subPath}.js`,
+            );
+            const flowIndexPath = path.resolve(
+              sdkAlphaPath,
+              'dist/esm/flows',
+              subPath,
+              'index.js',
+            );
+            if (fs.existsSync(flowPath)) {
+              return { type: 'sourceFile', filePath: flowPath };
+            }
+            if (fs.existsSync(flowIndexPath)) {
+              return { type: 'sourceFile', filePath: flowIndexPath };
+            }
+          } catch (e) {
+            console.warn(`Flow resolve error for ${moduleName}:`, e?.message ?? e);
+          }
+          // Delegate to default resolution on miss
+          return context.resolveRequest(context, moduleName, platform);
         }
       }

This avoids hard failures and supports directory-based flows (…/foo/index.js).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Custom resolver to handle Node.js modules and dynamic flow imports
if (moduleName.startsWith('@selfxyz/mobile-sdk-alpha/')) {
const subPath = moduleName.replace('@selfxyz/mobile-sdk-alpha/', '');
// Check if it's a flow import (onboarding/* or disclosing/*)
if (
subPath.startsWith('onboarding/') ||
subPath.startsWith('disclosing/')
) {
const flowPath = path.resolve(
sdkAlphaPath,
'dist/esm/flows',
`${subPath}.js`,
);
// Check if the file exists
if (fs.existsSync(flowPath)) {
return {
type: 'sourceFile',
filePath: flowPath,
};
}
}
}
// Custom resolver to handle Node.js modules and dynamic flow imports
if (moduleName.startsWith('@selfxyz/mobile-sdk-alpha/')) {
const subPath = moduleName.replace('@selfxyz/mobile-sdk-alpha/', '');
// Check if it's a flow import (onboarding/* or disclosing/*)
if (
subPath.startsWith('onboarding/') ||
subPath.startsWith('disclosing/')
) {
try {
const flowPath = path.resolve(
sdkAlphaPath,
'dist/esm/flows',
`${subPath}.js`,
);
const flowIndexPath = path.resolve(
sdkAlphaPath,
'dist/esm/flows',
subPath,
'index.js',
);
if (fs.existsSync(flowPath)) {
return { type: 'sourceFile', filePath: flowPath };
}
if (fs.existsSync(flowIndexPath)) {
return { type: 'sourceFile', filePath: flowIndexPath };
}
} catch (e) {
console.warn(`Flow resolve error for ${moduleName}:`, e?.message ?? e);
}
// Delegate to default Metro resolver on miss
return context.resolveRequest(context, moduleName, platform);
}
}
🤖 Prompt for AI Agents
In app/metro.config.cjs around lines 121 to 144, the custom resolver for
'@selfxyz/mobile-sdk-alpha/*' can throw on fs operations and doesn't handle
directory index files or properly delegate when a flow isn't found; wrap the
existence check and path resolution in a try/catch to avoid breaking the
resolver, attempt an additional fallback path that resolves to an index.js
inside the flow directory if the direct <subPath>.js is missing, and if neither
file exists return null (or call the default resolver) so resolution is
delegated rather than causing a hard failure.


if (appLevelModules[moduleName]) {
try {
Expand Down
14 changes: 6 additions & 8 deletions app/src/screens/prove/ConfirmBelongingScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import { ActivityIndicator, View } from 'react-native';
import type { StaticScreenProps } from '@react-navigation/native';
import { usePreventRemove } from '@react-navigation/native';

import {
usePrepareDocumentProof,
useSelfClient,
} from '@selfxyz/mobile-sdk-alpha';
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
import {
PassportEvents,
ProofEvents,
} from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import {
getPreRegistrationDescription,
usePrepareDocumentProof,
} from '@selfxyz/mobile-sdk-alpha/onboarding/confirm-identification';

import successAnimation from '@/assets/animations/loading/success.json';
import { PrimaryButton } from '@/components/buttons/PrimaryButton';
Expand Down Expand Up @@ -109,10 +110,7 @@ const ConfirmBelongingScreen: React.FC<ConfirmBelongingScreenProps> = () => {
>
<Title textAlign="center">Confirm your identity</Title>
<Description textAlign="center" paddingBottom={20}>
By continuing, you certify that this passport, biometric ID or
Aadhaar card belongs to you and is not stolen or forged. Once
registered with Self, this document will be permanently linked to
your identity and can't be linked to another one.
{getPreRegistrationDescription()}
</Description>
<PrimaryButton
trackEvent={PassportEvents.OWNERSHIP_CONFIRMED}
Expand Down
3 changes: 2 additions & 1 deletion app/src/screens/system/Loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import failAnimation from '@/assets/animations/loading/fail.json';
import proveLoadingAnimation from '@/assets/animations/loading/prove.json';
import CloseWarningIcon from '@/images/icons/close-warning.svg';
import { loadPassportDataAndSecret } from '@/providers/passportDataProvider';
import { useSettingStore } from '@/stores/settingStore';
import { black, slate400, white, zinc500, zinc900 } from '@/utils/colors';
import { extraYPadding } from '@/utils/constants';
import { advercase, dinot } from '@/utils/fonts';
Expand Down Expand Up @@ -60,7 +61,7 @@ const LoadingScreen: React.FC<LoadingScreenProps> = ({}) => {

// Get current state from proving machine, default to 'idle' if undefined
const currentState = useProvingStore(state => state.currentState) ?? 'idle';
const fcmToken = useProvingStore(state => state.fcmToken);
const fcmToken = useSettingStore(state => state.fcmToken);
const isFocused = useIsFocused();
const { bottom } = useSafeAreaInsets();

Expand Down
6 changes: 6 additions & 0 deletions app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
"../packages/mobile-sdk-alpha/src",
"../packages/mobile-sdk-alpha/dist"
],
"@selfxyz/mobile-sdk-alpha/onboarding/*": [
"../packages/mobile-sdk-alpha/dist/esm/flows/onboarding/*"
],
"@selfxyz/mobile-sdk-alpha/disclosing/*": [
"../packages/mobile-sdk-alpha/dist/esm/flows/disclosing/*"
],
"@/*": ["./src/*"]
}
},
Expand Down
10 changes: 10 additions & 0 deletions packages/mobile-sdk-alpha/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
"import": "./dist/esm/browser.js",
"require": "./dist/cjs/browser.cjs"
},
"./onboarding/*": {
"types": "./dist/esm/flows/onboarding/*.d.ts",
"import": "./dist/esm/flows/onboarding/*.js",
"require": "./dist/cjs/flows/onboarding/*.cjs"
},
"./disclosing/*": {
"types": "./dist/esm/flows/disclosing/*.d.ts",
"import": "./dist/esm/flows/disclosing/*.js",
"require": "./dist/cjs/flows/disclosing/*.cjs"
},
"./constants": {
"types": "./dist/esm/constants/index.d.ts",
"react-native": "./dist/esm/constants/index.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/mobile-sdk-alpha/src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export { type ProvingStateType } from './proving/provingMachine';
export { SCANNER_ERROR_CODES, notImplemented, sdkError } from './errors';
export { SdkEvents } from './types/events';

export { SelfClientContext, SelfClientProvider, usePrepareDocumentProof, useSelfClient } from './context';
export { SelfClientContext, SelfClientProvider, useSelfClient } from './context';

export {
clearPassportData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const NativeMRZScannerView = requireNativeComponent<SelfMRZScannerViewProps>(
})!,
);

interface MRZScannerViewProps {
export interface MRZScannerViewProps {
style?: ViewStyle;
height?: DimensionValue;
width?: DimensionValue;
Expand Down
32 changes: 1 addition & 31 deletions packages/mobile-sdk-alpha/src/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import { createContext, type PropsWithChildren, useContext, useEffect, useMemo } from 'react';
import { createContext, type PropsWithChildren, useContext, useMemo } from 'react';

import { createSelfClient } from './client';
import { loadSelectedDocument } from './documents/utils';
import { SdkEvents } from './types/events';
import type { Adapters, Config, SelfClient } from './types/public';

Expand Down Expand Up @@ -57,35 +56,6 @@ export function SelfClientProvider({
return <SelfClientContext.Provider value={client}>{children}</SelfClientContext.Provider>;
}

export function usePrepareDocumentProof() {
const selfClient = useSelfClient();
const { useProvingStore } = selfClient;
const currentState = useProvingStore(state => state.currentState);
const init = useProvingStore(state => state.init);
const setUserConfirmed = useProvingStore(state => state.setUserConfirmed);
const isReadyToProve = currentState === 'ready_to_prove';

useEffect(() => {
const initializeProving = async () => {
try {
const selectedDocument = await loadSelectedDocument(selfClient);
if (selectedDocument?.data?.documentCategory === 'aadhaar') {
init(selfClient, 'register');
} else {
init(selfClient, 'dsc');
}
} catch (error) {
console.error('Error loading selected document:', error);
init(selfClient, 'dsc');
}
};

initializeProving();
}, [init, selfClient]);

return { setUserConfirmed, isReadyToProve };
}

/**
* Retrieves the current {@link SelfClient} from context.
*
Expand Down
13 changes: 13 additions & 0 deletions packages/mobile-sdk-alpha/src/flows/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Read This

This folder contains folders for flows ie steps to complete a task like onboarding aka document registration or disclosing. In each folder each file represents roughly 1 step. Usually this means 1 screen but can be multiple depending on how error and bad states are represented in the UI. This helps with implementation as consumers of the api when building out their screens will more easily know which functions, hooks, Components, and constants to use together.

The files here and their structure are part of the external mobile sdk API.

convention is for folder for each flow to end in --ing and for file names to be verb-noun.ts

read-mrz
scan-nfc
import-aadhaar
confirm-ownership
generate-proof
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import { useEffect } from 'react';

import { useSelfClient } from '../../context';
import { loadSelectedDocument } from '../../documents/utils';

/*Add a comment on lines R7 to R9Add diff commentMarkdown input: edit mode selected.WritePreviewAdd a suggestionHeadingBoldItalicQuoteCodeLinkUnordered listNumbered listTask listMentionReferenceSaved repliesAdd FilesPaste, drop, or click to add filesCancelCommentStart a reviewReturn to code
Display this to users before they confirm ownership of a document
*/
export function getPreRegistrationDescription() {
return "By continuing, you certify that this passport, biometric ID or Aadhaar card belongs to you and is not stolen or forged. Once registered with Self, this document will be permanently linked to your identity and can't be linked to another one.";
}

/*
Hook to prepare for proving a document by initializing the proving state machine.
It loads the selected document and initializes the proving process based on the document type.
returns functions to set FCM token and mark user confirmation, along with a boolean indicating readiness to prove.
Usage:
use `isReadyToProve` to enable/disable the confirmation button.
call `setUserConfirmed` when the user presses your confirm button.
after calling `setUserConfirmed`, the proving process will start. You MUST Navigate to wait-generation screen.
*/
export function usePrepareDocumentProof() {
const selfClient = useSelfClient();
const { useProvingStore } = selfClient;
const currentState = useProvingStore(state => state.currentState);
const init = useProvingStore(state => state.init);
const setUserConfirmed = useProvingStore(state => state.setUserConfirmed);
const isReadyToProve = currentState === 'ready_to_prove';

useEffect(() => {
const initializeProving = async () => {
try {
const selectedDocument = await loadSelectedDocument(selfClient);
if (selectedDocument?.data?.documentCategory === 'aadhaar') {
init(selfClient, 'register');
} else {
init(selfClient, 'dsc');
}
} catch (error) {
console.error('Error loading selected document:', error);
init(selfClient, 'dsc');
}
Comment on lines +45 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Redact error logging to avoid potential PII exposure.

The error may contain document details (PII). Log a generic message only.

Apply this diff:

-      } catch (error) {
-        console.error('Error loading selected document:', error);
-        init(selfClient, 'dsc');
+      } catch (_error) {
+        console.error('Error loading selected document: [redacted]');
+        init(selfClient, 'dsc');
       }

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.error('Error loading selected document:', error);
init(selfClient, 'dsc');
}
} catch (_error) {
console.error('Error loading selected document: [redacted]');
init(selfClient, 'dsc');
}
🤖 Prompt for AI Agents
In packages/mobile-sdk-alpha/src/flows/onboarding/confirm-identification.ts
around lines 45 to 47, the console.error call logs the caught error which may
contain PII from document details; change it to log only a generic message
(e.g., "Error loading selected document") without including the error object or
document data, or remove the stack/details entirely, then proceed to call
init(selfClient, 'dsc') as before.

};

initializeProving();
}, [init, selfClient]);

return { setUserConfirmed, isReadyToProve };
}
8 changes: 8 additions & 0 deletions packages/mobile-sdk-alpha/src/flows/onboarding/read-mrz.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

export { MRZScannerView, MRZScannerViewProps } from '../../components/MRZScannerView';
export function mrzReadInstructions() {
return 'Lay your document flat and position the machine readable text in the viewfinder';
}
3 changes: 3 additions & 0 deletions packages/mobile-sdk-alpha/src/flows/onboarding/scan-nfc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
5 changes: 2 additions & 3 deletions packages/mobile-sdk-alpha/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,13 @@ export {
} from './errors';
export { NFCScannerScreen } from './components/screens/NFCScannerScreen';
export { PassportCameraScreen } from './components/screens/PassportCameraScreen';
export { type ProvingStateType } from './proving/provingMachine';

// Context and Client
export { QRCodeScreen } from './components/screens/QRCodeScreen';
export { SdkEvents } from './types/events';

// Components
export { SelfClientContext, SelfClientProvider, usePrepareDocumentProof, useSelfClient } from './context';

export { SelfClientContext, SelfClientProvider, useSelfClient } from './context';
// Documents utils
export { SelfMobileSdk } from './entry';

Expand Down
29 changes: 29 additions & 0 deletions packages/mobile-sdk-alpha/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,44 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import * as fs from 'node:fs';
import * as path from 'node:path';

import { defineConfig } from 'tsup';

const banner = `// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11`;

// Dynamically find all flow files
function findFlowFiles(dir: string, basePath = ''): Record<string, string> {
const entries: Record<string, string> = {};

if (!fs.existsSync(dir)) return entries;

const items = fs.readdirSync(dir, { withFileTypes: true });

for (const item of items) {
const itemPath = path.join(dir, item.name);
const relativePath = basePath ? path.join(basePath, item.name) : item.name;

if (item.isDirectory()) {
Object.assign(entries, findFlowFiles(itemPath, relativePath));
} else if (item.isFile() && item.name.endsWith('.ts')) {
const key = path.join('flows', relativePath).replace(/\.ts$/, '');
entries[key] = path.join('src', 'flows', relativePath);
}
}

return entries;
}
Comment on lines +12 to +33
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Make flow discovery OS-agnostic, support .tsx and index files, and exclude tests.

Current logic risks:

  • path.join in entry keys creates OS-specific separators.
  • index.ts ends up as flows/.../index (Metro expects flows/...).
  • .tsx flow files aren’t bundled; tests/.d.ts may slip in.

Refactor finder accordingly.

Apply this diff:

-// Dynamically find all flow files
-function findFlowFiles(dir: string, basePath = ''): Record<string, string> {
-  const entries: Record<string, string> = {};
-
-  if (!fs.existsSync(dir)) return entries;
-
-  const items = fs.readdirSync(dir, { withFileTypes: true });
-
-  for (const item of items) {
-    const itemPath = path.join(dir, item.name);
-    const relativePath = basePath ? path.join(basePath, item.name) : item.name;
-
-    if (item.isDirectory()) {
-      Object.assign(entries, findFlowFiles(itemPath, relativePath));
-    } else if (item.isFile() && item.name.endsWith('.ts')) {
-      const key = path.join('flows', relativePath).replace(/\.ts$/, '');
-      entries[key] = path.join('src', 'flows', relativePath);
-    }
-  }
-
-  return entries;
-}
+// Dynamically find all flow files
+function findFlowFiles(dir: string, basePath = ''): Record<string, string> {
+  const entries: Record<string, string> = {};
+  if (!fs.existsSync(dir)) return entries;
+
+  const items = fs.readdirSync(dir, { withFileTypes: true });
+
+  for (const item of items) {
+    const itemPath = path.join(dir, item.name);
+    const nextBasePosix = basePath
+      ? path.posix.join(basePath, item.name)
+      : item.name;
+
+    if (item.isDirectory()) {
+      Object.assign(entries, findFlowFiles(itemPath, nextBasePosix));
+      continue;
+    }
+
+    if (!item.isFile()) continue;
+    // include .ts/.tsx, exclude .d.ts and tests
+    if (!/\.(ts|tsx)$/.test(item.name)) continue;
+    if (/\.d\.ts$/.test(item.name)) continue;
+    if (/\.(test|spec)\.(ts|tsx)$/.test(item.name)) continue;
+
+    const relPosix = nextBasePosix;
+    const withoutExt = relPosix.replace(/\.(ts|tsx)$/, '');
+    const keyPath = withoutExt.endsWith('/index')
+      ? withoutExt.slice(0, -('/index'.length))
+      : withoutExt;
+    const key = path.posix.join('flows', keyPath);
+
+    entries[key] = path.join('src', 'flows', relPosix);
+  }
+
+  return entries;
+}

This ensures stable subpath outputs, covers component files, and avoids bundling tests. As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Dynamically find all flow files
function findFlowFiles(dir: string, basePath = ''): Record<string, string> {
const entries: Record<string, string> = {};
if (!fs.existsSync(dir)) return entries;
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
const itemPath = path.join(dir, item.name);
const relativePath = basePath ? path.join(basePath, item.name) : item.name;
if (item.isDirectory()) {
Object.assign(entries, findFlowFiles(itemPath, relativePath));
} else if (item.isFile() && item.name.endsWith('.ts')) {
const key = path.join('flows', relativePath).replace(/\.ts$/, '');
entries[key] = path.join('src', 'flows', relativePath);
}
}
return entries;
}
// Dynamically find all flow files
function findFlowFiles(dir: string, basePath = ''): Record<string, string> {
const entries: Record<string, string> = {};
if (!fs.existsSync(dir)) return entries;
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
const itemPath = path.join(dir, item.name);
const nextBasePosix = basePath
? path.posix.join(basePath, item.name)
: item.name;
if (item.isDirectory()) {
Object.assign(entries, findFlowFiles(itemPath, nextBasePosix));
continue;
}
if (!item.isFile()) continue;
// include .ts/.tsx, exclude .d.ts and tests
if (!/\.(ts|tsx)$/.test(item.name)) continue;
if (/\.d\.ts$/.test(item.name)) continue;
if (/\.(test|spec)\.(ts|tsx)$/.test(item.name)) continue;
const relPosix = nextBasePosix;
const withoutExt = relPosix.replace(/\.(ts|tsx)$/, '');
const keyPath = withoutExt.endsWith('/index')
? withoutExt.slice(0, -('/index'.length))
: withoutExt;
const key = path.posix.join('flows', keyPath);
entries[key] = path.join('src', 'flows', relPosix);
}
return entries;
}
🤖 Prompt for AI Agents
In packages/mobile-sdk-alpha/tsup.config.ts around lines 12 to 33, the flow
discovery currently uses OS-dependent path.join for entry keys, ignores .tsx,
includes index files as .../index (Metro expects parent path), and can include
tests or .d.ts; update the finder to: recurse as before but skip any file or
directory whose path includes /test/ or matching /\.test\.|\.spec\.|\.d\.ts$/;
treat files ending with .ts or .tsx only; for entry keys build a stable
POSIX-style key using path.posix.join('flows', basePath, nameWithoutExt) and if
the file is an index.(ts|tsx) use the POSIX key for the parent directory (i.e.
path.posix.join('flows', basePath) instead of .../index); keep the entry value
as the filesystem path to the source (you can use path.join for that), ensuring
keys use forward slashes so output is OS-agnostic and tests/type-declarations
are excluded.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is for converting ts/x files into cjs and mjs files and d,ts files so i dont see why we need to worry about ignoring. d.ts files here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronmgdr, you're right—since this is discovering source files in src/flows/ to feed tsup as entry points, there shouldn't be any .d.ts files sitting in your source directory. That check was overly defensive.

The more important improvements in my suggestion are:

  1. OS-agnostic keys: Using path.posix.join for entry keys ensures consistent forward-slash paths regardless of platform (Windows vs Unix)
  2. .tsx support: Current regex only matches .ts; if you add React components in flows, they won't be bundled
  3. index file handling: An index.ts currently becomes flows/onboarding/index but Metro/package.json exports likely expect flows/onboarding
  4. Test exclusion: Prevents *.test.ts or *.spec.ts files from becoming entry points

If you're confident your flows will only be .ts files (no components), no index patterns, and no test files in that directory, the current implementation works. Otherwise, those four points are worth addressing for robustness. Let me know if you'd like a revised diff focusing on just those concerns!


const flowEntries = findFlowFiles('src/flows');

const entry = {
index: 'src/index.ts',
browser: 'src/browser.ts',
'constants/analytics': 'src/constants/analytics.ts',
stores: 'src/stores/index.ts',
...flowEntries,
};

export default defineConfig([
Expand Down
Loading