@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.
- package/LICENSE +21 -0
- package/README.md +328 -2
- package/bin/create-360-miniapp.cjs +54 -0
- package/lib/create.cjs +56 -0
- package/package.json +24 -4
- package/template/.eslintrc.cjs +1 -0
- package/template/.prettierrc.cjs +1 -0
- package/template/.watchmanconfig +1 -0
- package/template/App.tsx +2 -0
- package/template/README.md +215 -0
- package/template/__tests__/auth.test.ts +287 -0
- package/template/aliases.cjs +16 -0
- package/template/app.config.js +23 -0
- package/template/app.json +17 -0
- package/template/assets/.gitkeep +0 -0
- package/template/babel.config.js +7 -0
- package/template/declare/.gitkeep +0 -0
- package/template/declare/assets.d.ts +11 -0
- package/template/env/beta.json +16 -0
- package/template/env/prod.json +16 -0
- package/template/env/staging.json +16 -0
- package/template/gitignore +19 -0
- package/template/index.js +2 -0
- package/template/index.repack.js +14 -0
- package/template/jest.config.cjs +29 -0
- package/template/metro.config.js +6 -0
- package/template/package.json +88 -0
- package/template/pipelines/beta.yml +91 -0
- package/template/pipelines/production.yml +91 -0
- package/template/pipelines/staging.yml +91 -0
- package/template/pipelines/templates/build-and-zip.yml +89 -0
- package/template/pipelines/templates/upload-and-register.yml +89 -0
- package/template/react-native.config.js +3 -0
- package/template/rspack.config.cjs +53 -0
- package/template/scripts/clean-mini-app-output.js +9 -0
- package/template/scripts/sync-env.js +29 -0
- package/template/src/App.tsx +50 -0
- package/template/src/StandaloneApp.tsx +43 -0
- package/template/src/assets/icons/.gitkeep +1 -0
- package/template/src/assets/images/.gitkeep +1 -0
- package/template/src/assets/svg/.gitkeep +1 -0
- package/template/src/bootstrap.tsx +7 -0
- package/template/src/common/mini-app/provider.tsx +45 -0
- package/template/src/common/mini-app/types.ts +13 -0
- package/template/src/config/.gitkeep +0 -0
- package/template/src/constants/.gitkeep +0 -0
- package/template/src/hooks/.gitkeep +0 -0
- package/template/src/library/components/.gitkeep +0 -0
- package/template/src/library/networking/client.ts +53 -0
- package/template/src/library/networking/errors.ts +25 -0
- package/template/src/library/utils/responsive.ts +7 -0
- package/template/src/library/utils/storage/.gitkeep +0 -0
- package/template/src/modules/__MODULE__/assets/.gitkeep +0 -0
- package/template/src/modules/__MODULE__/components/ProfileCard.tsx +38 -0
- package/template/src/modules/__MODULE__/constants/.gitkeep +0 -0
- package/template/src/modules/__MODULE__/navigation/ModuleNavigator.tsx +18 -0
- package/template/src/modules/__MODULE__/redux/home-slice.ts +13 -0
- package/template/src/modules/__MODULE__/screens/home/hooks/use-home.ts +30 -0
- package/template/src/modules/__MODULE__/screens/home/index.tsx +100 -0
- package/template/src/modules/__MODULE__/services/profile.service.ts +32 -0
- package/template/src/modules/__MODULE__/types/.gitkeep +0 -0
- package/template/src/modules/auth/components/.gitkeep +0 -0
- package/template/src/modules/auth/constants/.gitkeep +0 -0
- package/template/src/modules/auth/hooks/use-auth.ts +55 -0
- package/template/src/modules/auth/navigation/AuthenticationNavigator.tsx +16 -0
- package/template/src/modules/auth/redux/auth-slice.ts +36 -0
- package/template/src/modules/auth/redux/selectors.ts +6 -0
- package/template/src/modules/auth/screens/LoginScreen.tsx +78 -0
- package/template/src/modules/auth/services/oidc.ts +64 -0
- package/template/src/modules/auth/services/standalone-session.ts +121 -0
- package/template/src/navigation/MainStack.tsx +46 -0
- package/template/src/redux/RootReducers.ts +8 -0
- package/template/src/redux/hooks.ts +5 -0
- package/template/src/redux/store.ts +10 -0
- package/template/src/services/application.ts +87 -0
- package/template/src/themes/colors/dark.ts +90 -0
- package/template/src/themes/colors/light.ts +90 -0
- package/template/src/themes/index.ts +38 -0
- package/template/src/themes/standalone.ts +6 -0
- package/template/src/themes/ui-geometry.ts +12 -0
- package/template/src/types/.gitkeep +0 -0
- 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,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 };
|