@thanhphi/create-360-miniapp 0.0.0-stage → 0.2.1

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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +328 -2
  3. package/bin/create-360-miniapp.cjs +54 -0
  4. package/lib/create.cjs +56 -0
  5. package/package.json +24 -4
  6. package/template/.eslintrc.cjs +1 -0
  7. package/template/.prettierrc.cjs +1 -0
  8. package/template/.watchmanconfig +1 -0
  9. package/template/App.tsx +2 -0
  10. package/template/README.md +215 -0
  11. package/template/__tests__/auth.test.ts +287 -0
  12. package/template/aliases.cjs +16 -0
  13. package/template/app.config.js +23 -0
  14. package/template/app.json +17 -0
  15. package/template/assets/.gitkeep +0 -0
  16. package/template/babel.config.js +7 -0
  17. package/template/declare/.gitkeep +0 -0
  18. package/template/declare/assets.d.ts +11 -0
  19. package/template/env/beta.json +16 -0
  20. package/template/env/prod.json +16 -0
  21. package/template/env/staging.json +16 -0
  22. package/template/gitignore +19 -0
  23. package/template/index.js +2 -0
  24. package/template/index.repack.js +14 -0
  25. package/template/jest.config.cjs +29 -0
  26. package/template/metro.config.js +6 -0
  27. package/template/package.json +88 -0
  28. package/template/pipelines/beta.yml +91 -0
  29. package/template/pipelines/production.yml +91 -0
  30. package/template/pipelines/staging.yml +91 -0
  31. package/template/pipelines/templates/build-and-zip.yml +89 -0
  32. package/template/pipelines/templates/upload-and-register.yml +89 -0
  33. package/template/react-native.config.js +3 -0
  34. package/template/rspack.config.cjs +53 -0
  35. package/template/scripts/clean-mini-app-output.js +9 -0
  36. package/template/scripts/sync-env.js +29 -0
  37. package/template/src/App.tsx +50 -0
  38. package/template/src/StandaloneApp.tsx +43 -0
  39. package/template/src/assets/icons/.gitkeep +1 -0
  40. package/template/src/assets/images/.gitkeep +1 -0
  41. package/template/src/assets/svg/.gitkeep +1 -0
  42. package/template/src/bootstrap.tsx +7 -0
  43. package/template/src/common/mini-app/provider.tsx +45 -0
  44. package/template/src/common/mini-app/types.ts +13 -0
  45. package/template/src/config/.gitkeep +0 -0
  46. package/template/src/constants/.gitkeep +0 -0
  47. package/template/src/hooks/.gitkeep +0 -0
  48. package/template/src/library/components/.gitkeep +0 -0
  49. package/template/src/library/networking/client.ts +53 -0
  50. package/template/src/library/networking/errors.ts +25 -0
  51. package/template/src/library/utils/responsive.ts +7 -0
  52. package/template/src/library/utils/storage/.gitkeep +0 -0
  53. package/template/src/modules/__MODULE__/assets/.gitkeep +0 -0
  54. package/template/src/modules/__MODULE__/components/ProfileCard.tsx +38 -0
  55. package/template/src/modules/__MODULE__/constants/.gitkeep +0 -0
  56. package/template/src/modules/__MODULE__/navigation/ModuleNavigator.tsx +18 -0
  57. package/template/src/modules/__MODULE__/redux/home-slice.ts +13 -0
  58. package/template/src/modules/__MODULE__/screens/home/hooks/use-home.ts +30 -0
  59. package/template/src/modules/__MODULE__/screens/home/index.tsx +100 -0
  60. package/template/src/modules/__MODULE__/services/profile.service.ts +32 -0
  61. package/template/src/modules/__MODULE__/types/.gitkeep +0 -0
  62. package/template/src/modules/auth/components/.gitkeep +0 -0
  63. package/template/src/modules/auth/constants/.gitkeep +0 -0
  64. package/template/src/modules/auth/hooks/use-auth.ts +55 -0
  65. package/template/src/modules/auth/navigation/AuthenticationNavigator.tsx +16 -0
  66. package/template/src/modules/auth/redux/auth-slice.ts +36 -0
  67. package/template/src/modules/auth/redux/selectors.ts +6 -0
  68. package/template/src/modules/auth/screens/LoginScreen.tsx +78 -0
  69. package/template/src/modules/auth/services/oidc.ts +64 -0
  70. package/template/src/modules/auth/services/standalone-session.ts +121 -0
  71. package/template/src/navigation/MainStack.tsx +46 -0
  72. package/template/src/redux/RootReducers.ts +8 -0
  73. package/template/src/redux/hooks.ts +5 -0
  74. package/template/src/redux/store.ts +10 -0
  75. package/template/src/services/application.ts +87 -0
  76. package/template/src/themes/colors/dark.ts +90 -0
  77. package/template/src/themes/colors/light.ts +90 -0
  78. package/template/src/themes/index.ts +38 -0
  79. package/template/src/themes/standalone.ts +6 -0
  80. package/template/src/themes/ui-geometry.ts +12 -0
  81. package/template/src/types/.gitkeep +0 -0
  82. package/template/tsconfig.json +65 -0
@@ -0,0 +1,29 @@
1
+ const aliases = require('./aliases.cjs');
2
+ const moduleNameMapper = {};
3
+ for (const [alias, target] of Object.entries(aliases)) {
4
+ const escaped = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
5
+ moduleNameMapper[`^${escaped}$`] = `<rootDir>/${target.replace(/^\.\//, '')}`;
6
+ moduleNameMapper[`^${escaped}/(.*)$`] = `<rootDir>/${target.replace(
7
+ /^\.\//,
8
+ '',
9
+ )}/$1`;
10
+ }
11
+
12
+ module.exports = {
13
+ preset: 'react-native',
14
+ testMatch: ['<rootDir>/__tests__/**/*.test.[jt]s?(x)'],
15
+ transform: {
16
+ '^.+\\.[jt]sx?$': [
17
+ 'babel-jest',
18
+ {
19
+ presets: ['module:@react-native/babel-preset'],
20
+ babelrc: false,
21
+ configFile: false,
22
+ },
23
+ ],
24
+ },
25
+ transformIgnorePatterns: [
26
+ 'node_modules/(?!((jest-)?react-native|@react-native|@thanhphi/miniapp-sdk|immer)/)',
27
+ ],
28
+ moduleNameMapper,
29
+ };
@@ -0,0 +1,6 @@
1
+ const { getDefaultConfig } = require('expo/metro-config');
2
+ const config = getDefaultConfig(__dirname);
3
+ config.transformer.babelTransformerPath = require.resolve('react-native-svg-transformer/expo');
4
+ config.resolver.assetExts = config.resolver.assetExts.filter(ext => ext !== 'svg');
5
+ config.resolver.sourceExts.push('svg');
6
+ module.exports = config;
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "android": "expo run:android",
8
+ "ios": "expo run:ios",
9
+ "lint": "eslint src App.tsx",
10
+ "start": "expo start",
11
+ "start:expo": "expo start",
12
+ "start:mf": "REPACK=1 react-native start --port __PORT__ --config rspack.config.cjs",
13
+ "start:mf:device": "REPACK=1 react-native start --host 0.0.0.0 --port __PORT__ --config rspack.config.cjs",
14
+ "start:mf:reset": "REPACK=1 react-native start --port __PORT__ --config rspack.config.cjs --reset-cache",
15
+ "bundle:mf:ios": "node scripts/clean-mini-app-output.js ios && REPACK=1 react-native bundle --platform ios --dev false --minify true --entry-file index.repack.js --config rspack.config.cjs --bundle-output build/generated/ios/index.bundle --assets-dest build/generated/ios",
16
+ "bundle:mf:android": "node scripts/clean-mini-app-output.js android && REPACK=1 react-native bundle --platform android --dev false --minify true --entry-file index.repack.js --config rspack.config.cjs --bundle-output build/generated/android/index.bundle --assets-dest build/generated/android",
17
+ "env:staging": "node scripts/sync-env.js staging",
18
+ "env:beta": "node scripts/sync-env.js beta",
19
+ "env:prod": "node scripts/sync-env.js prod",
20
+ "start:staging": "node scripts/sync-env.js staging && expo start",
21
+ "start:beta": "node scripts/sync-env.js beta && expo start",
22
+ "start:prod": "node scripts/sync-env.js prod && expo start",
23
+ "ios:staging": "node scripts/sync-env.js staging && expo run:ios",
24
+ "ios:beta": "node scripts/sync-env.js beta && expo run:ios",
25
+ "ios:prod": "node scripts/sync-env.js prod && expo run:ios",
26
+ "android:staging": "node scripts/sync-env.js staging && expo run:android",
27
+ "android:beta": "node scripts/sync-env.js beta && expo run:android",
28
+ "android:prod": "node scripts/sync-env.js prod && expo run:android",
29
+ "typecheck": "tsc --noEmit",
30
+ "test": "jest --runInBand --watchman=false"
31
+ },
32
+ "dependencies": {
33
+ "@react-navigation/native": "7.2.2",
34
+ "@react-navigation/native-stack": "7.14.12",
35
+ "@reduxjs/toolkit": "2.11.2",
36
+ "@tanstack/react-query": "5.62.11",
37
+ "@thanhphi/miniapp-sdk": "0.1.0",
38
+ "@thanhphi/ui360": "0.2.0",
39
+ "expo": "~55.0.0",
40
+ "expo-dev-client": "~55.0.32",
41
+ "react": "19.2.0",
42
+ "react-native": "0.83.6",
43
+ "react-native-app-auth": "8.1.0",
44
+ "react-native-calendars": "1.1314.0",
45
+ "react-native-gesture-handler": "~2.30.0",
46
+ "react-native-reanimated": "4.3.1",
47
+ "react-native-safe-area-context": "~5.6.2",
48
+ "react-native-screens": "~4.23.0",
49
+ "react-native-svg": "15.15.4",
50
+ "react-native-worklets": "0.8.1",
51
+ "react-redux": "9.2.0",
52
+ "react-native-unistyles": "3.2.5",
53
+ "react-native-nitro-modules": "0.36.5",
54
+ "expo-image": "~55.0.10"
55
+ },
56
+ "devDependencies": {
57
+ "@babel/core": "^7.25.2",
58
+ "@babel/runtime": "^7.25.0",
59
+ "@callstack/repack": "^5.2.5",
60
+ "@callstack/repack-plugin-expo-modules": "^5.2.5",
61
+ "@callstack/repack-plugin-reanimated": "^5.2.5",
62
+ "@module-federation/enhanced": "^0.19.1",
63
+ "@react-native-community/cli": "20.0.0",
64
+ "@react-native-community/cli-platform-android": "20.0.0",
65
+ "@react-native-community/cli-platform-ios": "20.0.0",
66
+ "@react-native/babel-preset": "0.83.6",
67
+ "@react-native/eslint-config": "0.83.6",
68
+ "@react-native/metro-config": "0.83.6",
69
+ "@react-native/typescript-config": "0.83.6",
70
+ "@rspack/core": "1.7.12",
71
+ "@svgr/webpack": "8.1.0",
72
+ "@types/jest": "29.5.13",
73
+ "@types/react": "19.2.0",
74
+ "@types/react-test-renderer": "19.1.0",
75
+ "babel-jest": "29.6.3",
76
+ "babel-plugin-module-resolver": "5.0.3",
77
+ "babel-preset-expo": "~55.0.8",
78
+ "eslint": "8.19.0",
79
+ "jest": "29.6.3",
80
+ "prettier": "2.8.8",
81
+ "react-native-svg-transformer": "1.5.3",
82
+ "react-test-renderer": "19.2.0",
83
+ "typescript": "5.8.3"
84
+ },
85
+ "engines": {
86
+ "node": ">=20"
87
+ }
88
+ }
@@ -0,0 +1,91 @@
1
+ # Configure optional deployment variables in Azure Pipelines or add a variable
2
+ # group below. Build-only CI needs no Azure connection, storage or database.
3
+ parameters:
4
+ - name: deploy
5
+ displayName: 'Upload and register the built mini app'
6
+ type: boolean
7
+ default: false
8
+ - name: agentPool
9
+ type: object
10
+ default:
11
+ vmImage: 'ubuntu-latest'
12
+ - name: nodeVersion
13
+ type: string
14
+ default: '22.x'
15
+
16
+ pool: ${{ parameters.agentPool }}
17
+
18
+ trigger:
19
+ branches:
20
+ include:
21
+ - beta
22
+ paths:
23
+ include:
24
+ - src/**
25
+ - assets/**
26
+ - declare/**
27
+ - env/**
28
+ - android/**
29
+ - ios/**
30
+ - scripts/**
31
+ - plugins/**
32
+ - pipelines/**
33
+ - __tests__/**
34
+ - package.json
35
+ - package-lock.json
36
+ - yarn.lock
37
+ - App.tsx
38
+ - aliases.cjs
39
+ - index.js
40
+ - index.repack.js
41
+ - app.json
42
+ - app.config.*
43
+ - babel.config.*
44
+ - metro.config.*
45
+ - rspack.config.*
46
+ - react-native.config.*
47
+ - tsconfig*.json
48
+ - jest.config.*
49
+ - jest.setup.*
50
+ - eslint.config.*
51
+ - .eslintrc*
52
+ - .npmrc
53
+
54
+ variables:
55
+ # Add your environment-specific Azure variable group here when deploy = true:
56
+ # - group: '<your-miniapp-variable-group>'
57
+ - name: envBuild
58
+ value: 'beta'
59
+ - name: envFolder
60
+ value: 'beta'
61
+ - name: appName
62
+ value: '__APP_NAME__'
63
+ - name: appId
64
+ value: '__APP_ID__'
65
+ - name: appCode
66
+ value: '__APP_CODE__'
67
+ - name: miniAppVersion
68
+ value: '$(Build.BuildNumber)'
69
+
70
+ stages:
71
+ - stage: Build
72
+ displayName: 'Verify and bundle beta mini app'
73
+ jobs:
74
+ - job: Build
75
+ displayName: 'Build Android and iOS remotes'
76
+ steps:
77
+ - checkout: self
78
+ clean: true
79
+ - template: templates/build-and-zip.yml
80
+ parameters:
81
+ nodeVersion: '${{ parameters.nodeVersion }}'
82
+ - ${{ if eq(parameters.deploy, true) }}:
83
+ - template: templates/upload-and-register.yml
84
+ parameters:
85
+ platform: android
86
+ checksum: '$(CHECKSUMANDROID)'
87
+ - template: templates/upload-and-register.yml
88
+ parameters:
89
+ platform: ios
90
+ checksum: '$(CHECKSUMIOS)'
91
+
@@ -0,0 +1,91 @@
1
+ # Configure optional deployment variables in Azure Pipelines or add a variable
2
+ # group below. Build-only CI needs no Azure connection, storage or database.
3
+ parameters:
4
+ - name: deploy
5
+ displayName: 'Upload and register the built mini app'
6
+ type: boolean
7
+ default: false
8
+ - name: agentPool
9
+ type: object
10
+ default:
11
+ vmImage: 'ubuntu-latest'
12
+ - name: nodeVersion
13
+ type: string
14
+ default: '22.x'
15
+
16
+ pool: ${{ parameters.agentPool }}
17
+
18
+ trigger:
19
+ tags:
20
+ include:
21
+ - '*'
22
+ paths:
23
+ include:
24
+ - src/**
25
+ - assets/**
26
+ - declare/**
27
+ - env/**
28
+ - android/**
29
+ - ios/**
30
+ - scripts/**
31
+ - plugins/**
32
+ - pipelines/**
33
+ - __tests__/**
34
+ - package.json
35
+ - package-lock.json
36
+ - yarn.lock
37
+ - App.tsx
38
+ - aliases.cjs
39
+ - index.js
40
+ - index.repack.js
41
+ - app.json
42
+ - app.config.*
43
+ - babel.config.*
44
+ - metro.config.*
45
+ - rspack.config.*
46
+ - react-native.config.*
47
+ - tsconfig*.json
48
+ - jest.config.*
49
+ - jest.setup.*
50
+ - eslint.config.*
51
+ - .eslintrc*
52
+ - .npmrc
53
+
54
+ variables:
55
+ # Add your environment-specific Azure variable group here when deploy = true:
56
+ # - group: '<your-miniapp-variable-group>'
57
+ - name: envBuild
58
+ value: 'prod'
59
+ - name: envFolder
60
+ value: 'production'
61
+ - name: appName
62
+ value: '__APP_NAME__'
63
+ - name: appId
64
+ value: '__APP_ID__'
65
+ - name: appCode
66
+ value: '__APP_CODE__'
67
+ - name: miniAppVersion
68
+ value: '$(Build.BuildNumber)'
69
+
70
+ stages:
71
+ - stage: Build
72
+ displayName: 'Verify and bundle production mini app'
73
+ jobs:
74
+ - job: Build
75
+ displayName: 'Build Android and iOS remotes'
76
+ steps:
77
+ - checkout: self
78
+ clean: true
79
+ - template: templates/build-and-zip.yml
80
+ parameters:
81
+ nodeVersion: '${{ parameters.nodeVersion }}'
82
+ - ${{ if eq(parameters.deploy, true) }}:
83
+ - template: templates/upload-and-register.yml
84
+ parameters:
85
+ platform: android
86
+ checksum: '$(CHECKSUMANDROID)'
87
+ - template: templates/upload-and-register.yml
88
+ parameters:
89
+ platform: ios
90
+ checksum: '$(CHECKSUMIOS)'
91
+
@@ -0,0 +1,91 @@
1
+ # Configure optional deployment variables in Azure Pipelines or add a variable
2
+ # group below. Build-only CI needs no Azure connection, storage or database.
3
+ parameters:
4
+ - name: deploy
5
+ displayName: 'Upload and register the built mini app'
6
+ type: boolean
7
+ default: false
8
+ - name: agentPool
9
+ type: object
10
+ default:
11
+ vmImage: 'ubuntu-latest'
12
+ - name: nodeVersion
13
+ type: string
14
+ default: '22.x'
15
+
16
+ pool: ${{ parameters.agentPool }}
17
+
18
+ trigger:
19
+ branches:
20
+ include:
21
+ - staging
22
+ paths:
23
+ include:
24
+ - src/**
25
+ - assets/**
26
+ - declare/**
27
+ - env/**
28
+ - android/**
29
+ - ios/**
30
+ - scripts/**
31
+ - plugins/**
32
+ - pipelines/**
33
+ - __tests__/**
34
+ - package.json
35
+ - package-lock.json
36
+ - yarn.lock
37
+ - App.tsx
38
+ - aliases.cjs
39
+ - index.js
40
+ - index.repack.js
41
+ - app.json
42
+ - app.config.*
43
+ - babel.config.*
44
+ - metro.config.*
45
+ - rspack.config.*
46
+ - react-native.config.*
47
+ - tsconfig*.json
48
+ - jest.config.*
49
+ - jest.setup.*
50
+ - eslint.config.*
51
+ - .eslintrc*
52
+ - .npmrc
53
+
54
+ variables:
55
+ # Add your environment-specific Azure variable group here when deploy = true:
56
+ # - group: '<your-miniapp-variable-group>'
57
+ - name: envBuild
58
+ value: 'staging'
59
+ - name: envFolder
60
+ value: 'staging'
61
+ - name: appName
62
+ value: '__APP_NAME__'
63
+ - name: appId
64
+ value: '__APP_ID__'
65
+ - name: appCode
66
+ value: '__APP_CODE__'
67
+ - name: miniAppVersion
68
+ value: '$(Build.BuildNumber)'
69
+
70
+ stages:
71
+ - stage: Build
72
+ displayName: 'Verify and bundle staging mini app'
73
+ jobs:
74
+ - job: Build
75
+ displayName: 'Build Android and iOS remotes'
76
+ steps:
77
+ - checkout: self
78
+ clean: true
79
+ - template: templates/build-and-zip.yml
80
+ parameters:
81
+ nodeVersion: '${{ parameters.nodeVersion }}'
82
+ - ${{ if eq(parameters.deploy, true) }}:
83
+ - template: templates/upload-and-register.yml
84
+ parameters:
85
+ platform: android
86
+ checksum: '$(CHECKSUMANDROID)'
87
+ - template: templates/upload-and-register.yml
88
+ parameters:
89
+ platform: ios
90
+ checksum: '$(CHECKSUMIOS)'
91
+
@@ -0,0 +1,89 @@
1
+ parameters:
2
+ - name: nodeVersion
3
+ type: string
4
+ default: '22.x'
5
+
6
+ steps:
7
+ - task: NodeTool@0
8
+ displayName: 'Use Node.js ${{ parameters.nodeVersion }}'
9
+ inputs:
10
+ versionSpec: '${{ parameters.nodeVersion }}'
11
+
12
+ - task: Bash@3
13
+ displayName: 'Install, verify and bundle mini app'
14
+ inputs:
15
+ targetType: 'inline'
16
+ workingDirectory: '$(Build.SourcesDirectory)'
17
+ script: |
18
+ set -euo pipefail
19
+
20
+ if [ -f package-lock.json ]; then
21
+ npm ci
22
+ else
23
+ # The scaffold has no dependency lock yet. Commit the first generated
24
+ # package-lock.json so subsequent builds use reproducible npm ci.
25
+ echo 'No package-lock.json found; using npm install for the first build.'
26
+ npm install
27
+ fi
28
+
29
+ node scripts/sync-env.js "$MINIAPP_ENV_BUILD"
30
+ npm run typecheck
31
+ npm run lint
32
+ npm run test
33
+ npm run bundle:mf:android
34
+ npm run bundle:mf:ios
35
+ env:
36
+ CI: 'true'
37
+ MINIAPP_ENV_BUILD: '$(envBuild)'
38
+
39
+ - task: Bash@3
40
+ displayName: 'Archive remote bundles and compute SHA-1'
41
+ inputs:
42
+ targetType: 'inline'
43
+ workingDirectory: '$(Build.SourcesDirectory)'
44
+ script: |
45
+ set -euo pipefail
46
+
47
+ if [[ ! "$MINIAPP_VERSION" =~ ^[A-Za-z0-9._-]+$ ]]; then
48
+ echo 'miniAppVersion must contain only letters, digits, dots, underscores or hyphens.' >&2
49
+ exit 1
50
+ fi
51
+
52
+ mkdir -p "$ARTIFACT_DIRECTORY"
53
+ for platform in android ios; do
54
+ remoteDirectory="$BUILD_CONTEXT/build/outputs/$platform/remotes"
55
+ archiveDirectory="$BUILD_CONTEXT/build/generated/$platform"
56
+ archive="$archiveDirectory/$MINIAPP_VERSION.zip"
57
+ test -f "$remoteDirectory/$MINIAPP_CONTAINER_NAME.container.js.bundle"
58
+ mkdir -p "$archiveDirectory" "$ARTIFACT_DIRECTORY/$platform"
59
+
60
+ (
61
+ cd "$remoteDirectory"
62
+ # A container may depend on every other chunk in this directory.
63
+ # generated/ contains standalone/compiler output and is not shipped.
64
+ zip -r "$archive" . -x '*.map'
65
+ )
66
+
67
+ checksum=$(sha1sum "$archive" | awk '{print $1}')
68
+ if [ "$platform" = android ]; then
69
+ echo "##vso[task.setvariable variable=CHECKSUMANDROID]$checksum"
70
+ else
71
+ echo "##vso[task.setvariable variable=CHECKSUMIOS]$checksum"
72
+ fi
73
+
74
+ cp "$archive" "$ARTIFACT_DIRECTORY/$platform/"
75
+ printf '%s %s.zip\n' "$checksum" "$MINIAPP_VERSION" > "$ARTIFACT_DIRECTORY/$platform/$MINIAPP_VERSION.zip.sha1"
76
+ done
77
+ env:
78
+ BUILD_CONTEXT: '$(Build.SourcesDirectory)'
79
+ ARTIFACT_DIRECTORY: '$(Build.ArtifactStagingDirectory)/miniapp'
80
+ MINIAPP_VERSION: '$(miniAppVersion)'
81
+ MINIAPP_CONTAINER_NAME: '$(appName)'
82
+
83
+ - task: PublishPipelineArtifact@1
84
+ displayName: 'Publish Android and iOS mini app archives'
85
+ condition: succeeded()
86
+ inputs:
87
+ targetPath: '$(Build.ArtifactStagingDirectory)/miniapp'
88
+ artifact: 'miniapp-$(envBuild)'
89
+ publishLocation: 'pipeline'
@@ -0,0 +1,89 @@
1
+ parameters:
2
+ - name: platform
3
+ type: string
4
+ values:
5
+ - android
6
+ - ios
7
+ - name: checksum
8
+ type: string
9
+
10
+ steps:
11
+ - task: AzureCLI@2
12
+ displayName: 'Upload ${{ parameters.platform }} mini app archive'
13
+ inputs:
14
+ azureSubscription: '$(MINIAPP_AZURE_SUBSCRIPTION)'
15
+ scriptType: 'bash'
16
+ scriptLocation: 'inlineScript'
17
+ inlineScript: |
18
+ set -euo pipefail
19
+
20
+ for variable in STORAGE_ACCOUNT STORAGE_CONTAINER APP_ID ENV_FOLDER MINIAPP_VERSION; do
21
+ case "${!variable:-}" in
22
+ ''|'$('*) echo "Required pipeline variable is missing: $variable" >&2; exit 1 ;;
23
+ esac
24
+ done
25
+
26
+ az storage blob upload \
27
+ --file "$BUILD_CONTEXT/build/generated/$PLATFORM/$MINIAPP_VERSION.zip" \
28
+ --container-name "$STORAGE_CONTAINER" \
29
+ --name "$APP_ID/$ENV_FOLDER/$PLATFORM/$MINIAPP_VERSION.zip" \
30
+ --account-name "$STORAGE_ACCOUNT" \
31
+ --auth-mode login \
32
+ --overwrite false \
33
+ --only-show-errors
34
+ env:
35
+ BUILD_CONTEXT: '$(Build.SourcesDirectory)'
36
+ STORAGE_ACCOUNT: '$(MINIAPP_STORAGE_ACCOUNT)'
37
+ STORAGE_CONTAINER: '$(MINIAPP_STORAGE_CONTAINER)'
38
+ APP_ID: '$(appId)'
39
+ ENV_FOLDER: '$(envFolder)'
40
+ PLATFORM: '${{ parameters.platform }}'
41
+ MINIAPP_VERSION: '$(miniAppVersion)'
42
+
43
+ - task: Bash@3
44
+ displayName: 'Register ${{ parameters.platform }} mini app version'
45
+ inputs:
46
+ targetType: 'inline'
47
+ script: |
48
+ set -euo pipefail
49
+
50
+ for variable in PGHOST PGUSER PGDATABASE PGPASSWORD APP_ID APP_NAME APP_CODE STORAGE_CONTAINER MINIAPP_VERSION CHECKSUM; do
51
+ case "${!variable:-}" in
52
+ ''|'$('*) echo "Required pipeline variable is missing: $variable" >&2; exit 1 ;;
53
+ esac
54
+ done
55
+
56
+ archivePath="$APP_ID/$ENV_FOLDER/$PLATFORM/$MINIAPP_VERSION.zip"
57
+ psql --no-psqlrc --set=ON_ERROR_STOP=1 \
58
+ --set=app_id="$APP_ID" \
59
+ --set=app_name="$APP_NAME" \
60
+ --set=app_code="$APP_CODE" \
61
+ --set=miniapp_version="$MINIAPP_VERSION" \
62
+ --set=platform="$PLATFORM" \
63
+ --set=checksum="$CHECKSUM" \
64
+ --set=storage_container="$STORAGE_CONTAINER" \
65
+ --set=archive_path="$archivePath" <<'SQL'
66
+ BEGIN;
67
+ INSERT INTO mini_app (id, bundle_name, name, created_at, updated_at, forced, disabled, app_code)
68
+ VALUES (:'app_id', :'app_id', :'app_name', now(), now(), false, false, :'app_code')
69
+ ON CONFLICT (id) DO NOTHING;
70
+
71
+ INSERT INTO mini_app_version (id, id_version, platform, checkSum, container, path, created_at, updated_at)
72
+ VALUES (:'app_id', :'miniapp_version', :'platform', :'checksum', :'storage_container', :'archive_path', now(), now());
73
+ COMMIT;
74
+ SQL
75
+ env:
76
+ PGHOST: '$(DB_HOST)'
77
+ PGPORT: '5432'
78
+ PGUSER: '$(DB_LOGIN)'
79
+ PGDATABASE: '$(DB_DATABASE)'
80
+ PGPASSWORD: '$(DB_PASSWORD)'
81
+ PGSSLMODE: 'require'
82
+ APP_ID: '$(appId)'
83
+ APP_NAME: '$(appName)'
84
+ APP_CODE: '$(appCode)'
85
+ ENV_FOLDER: '$(envFolder)'
86
+ PLATFORM: '${{ parameters.platform }}'
87
+ MINIAPP_VERSION: '$(miniAppVersion)'
88
+ CHECKSUM: '${{ parameters.checksum }}'
89
+ STORAGE_CONTAINER: '$(MINIAPP_STORAGE_CONTAINER)'
@@ -0,0 +1,3 @@
1
+ module.exports = process.env.REPACK === '1'
2
+ ? { commands: require('@callstack/repack/commands/rspack') }
3
+ : {};
@@ -0,0 +1,53 @@
1
+ const path = require('node:path');
2
+ const Repack = require('@callstack/repack');
3
+ const { ExpoModulesPlugin } = require('@callstack/repack-plugin-expo-modules');
4
+ const { ReanimatedPlugin } = require('@callstack/repack-plugin-reanimated');
5
+ const pkg = require('./package.json');
6
+
7
+ const aliases = Object.fromEntries(Object.entries(require('./aliases.cjs')).map(([name, target]) => [name, path.resolve(__dirname, target)]));
8
+ const sharedNames = [
9
+ 'react', 'react-native', 'expo', 'expo-image',
10
+ 'react-native-unistyles', 'react-native-nitro-modules',
11
+ // UI360 stays in this remote bundle, independent of the host's UI version.
12
+ '@thanhphi/miniapp-sdk',
13
+ '@react-navigation/native', '@react-navigation/native-stack',
14
+ 'react-native-gesture-handler', 'react-native-safe-area-context',
15
+ 'react-native-screens', 'react-native-svg', 'react-native-calendars',
16
+ 'react-native-reanimated', 'react-native-worklets',
17
+ ];
18
+ const shared = Object.fromEntries(sharedNames.map(name => [name, {
19
+ singleton: true,
20
+ eager: false,
21
+ packageName: name,
22
+ requiredVersion: pkg.dependencies[name],
23
+ version: require(`${name}/package.json`).version,
24
+ }]));
25
+
26
+ module.exports = Repack.defineRspackConfig(({ mode, context, platform }) => ({
27
+ mode, context,
28
+ devtool: mode === 'production' ? false : 'source-map',
29
+ entry: './index.repack.js',
30
+ resolve: { ...Repack.getResolveOptions({ enablePackageExports: true }), alias: aliases },
31
+ output: { path: '[context]/build/generated/[platform]', uniqueName: '__APP_NAME__' },
32
+ module: { rules: [
33
+ { test: /\.[cm]?[jt]sx?$/, type: 'javascript/auto', use: {
34
+ loader: '@callstack/repack/babel-loader', options: {
35
+ configFile: path.resolve(__dirname, 'babel.config.js'),
36
+ caller: { name: '@callstack/repack', platform },
37
+ },
38
+ } },
39
+ { oneOf: [
40
+ { test: /\.svg$/, use: [{ loader: '@svgr/webpack', options: { native: true } }] },
41
+ ...Repack.getAssetTransformRules({ inline: true, maxInlineSize: 90 * 1024 }),
42
+ ] },
43
+ ] },
44
+ plugins: [
45
+ new Repack.RepackPlugin({ extraChunks: [{ include: /.*/, type: 'remote', outputPath: `build/outputs/${platform}/remotes` }] }),
46
+ new ExpoModulesPlugin(),
47
+ new ReanimatedPlugin({ unstable_disableTransform: true }),
48
+ new Repack.plugins.ModuleFederationPluginV2({
49
+ name: '__APP_NAME__', filename: '__APP_NAME__.container.js.bundle',
50
+ exposes: { './MiniApp': './src/App.tsx' }, dts: false, shared,
51
+ }),
52
+ ],
53
+ }));
@@ -0,0 +1,9 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ module.exports = function clean(platform) {
4
+ if (!['ios', 'android'].includes(platform)) throw new Error('Platform must be ios or android.');
5
+ for (const directory of [`build/generated/${platform}`, `build/outputs/${platform}`]) {
6
+ fs.rmSync(path.resolve(__dirname, '..', directory), { recursive: true, force: true });
7
+ }
8
+ };
9
+ if (require.main === module) module.exports(process.argv[2]);
@@ -0,0 +1,29 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+
4
+ function syncEnv(flavor, root = path.resolve(__dirname, '..')) {
5
+ if (!['staging', 'beta', 'prod'].includes(flavor)) throw new Error('Use staging, beta or prod.');
6
+ const input = JSON.parse(fs.readFileSync(path.join(root, 'env', `${flavor}.json`), 'utf8'));
7
+ if (input.secure) throw new Error('Do not put secrets in client env files. Use public app config only.');
8
+ const config = input.public || input;
9
+ if (config.ENV !== flavor) throw new Error(`ENV must match ${flavor}.`);
10
+ const api = new URL(config.BASE_URL);
11
+ if (!['https:', 'http:'].includes(api.protocol) || api.username || api.password || api.search || api.hash) throw new Error('BASE_URL must be an HTTP(S) URL without credentials/query/hash.');
12
+ if (!Number.isInteger(config.API_TIMEOUT_MS) || config.API_TIMEOUT_MS <= 0) throw new Error('API_TIMEOUT_MS must be a positive integer.');
13
+ if (!config.AUTH || typeof config.AUTH.clientId !== 'string' || typeof config.AUTH.issuer !== 'string' || typeof config.AUTH.redirectUrl !== 'string' || !Array.isArray(config.AUTH.scopes) || config.AUTH.scopes.some(scope => typeof scope !== 'string')) throw new Error('AUTH requires issuer, clientId, redirectUrl and scopes.');
14
+ const issuer = new URL(config.AUTH.issuer);
15
+ const redirect = new URL(config.AUTH.redirectUrl);
16
+ if (issuer.protocol !== 'https:' || issuer.username || issuer.password) throw new Error('AUTH.issuer must use HTTPS without credentials.');
17
+ if (!/^[a-z][a-z0-9+.-]*:\/\//.test(config.AUTH.redirectUrl) || ['http:', 'https:'].includes(redirect.protocol) || redirect.username || redirect.password) throw new Error('Use a custom OAuth redirect scheme:// URL without credentials for this native base.');
18
+ const contents = `// Generated by scripts/sync-env.js (${flavor}). Values are public in the bundle.\nconst config = ${JSON.stringify(config, null, 2)} as const;\nexport default config;\nexport const { ENV, BASE_URL, API_TIMEOUT_MS, AUTH } = config;\n`;
19
+ fs.writeFileSync(path.join(root, 'env', 'env-config.ts'), contents);
20
+ // app.config.js uses the same selected env for native OAuth redirect setup.
21
+ fs.writeFileSync(path.join(root, 'env', 'selected.json'), JSON.stringify({ flavor }) + '\n');
22
+ return config;
23
+ }
24
+
25
+ if (require.main === module) {
26
+ try { syncEnv(process.argv[2]); console.log(`Selected ${process.argv[2]} environment. Restart Metro/Re.Pack after changing env.`); }
27
+ catch (error) { console.error(error.message); process.exitCode = 1; }
28
+ }
29
+ module.exports = { syncEnv };