@thanhphi/create-360-miniapp 0.0.0-stage → 0.2.0
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 +79 -2
- package/bin/create-360-miniapp.cjs +54 -0
- package/lib/create.cjs +56 -0
- package/package.json +9 -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 +234 -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 +44 -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 +37 -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 +97 -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 +75 -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/types/.gitkeep +0 -0
- package/template/tsconfig.json +65 -0
|
@@ -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
|
+
'@thanhphi/miniapp-sdk', '@thanhphi/ui360',
|
|
12
|
+
'@react-navigation/native', '@react-navigation/native-stack',
|
|
13
|
+
'react-native-gesture-handler', 'react-native-safe-area-context',
|
|
14
|
+
'react-native-screens', 'react-native-svg', 'react-native-calendars',
|
|
15
|
+
'react-native-reanimated', 'react-native-worklets',
|
|
16
|
+
];
|
|
17
|
+
const shared = Object.fromEntries(sharedNames.map(name => [name, {
|
|
18
|
+
singleton: true,
|
|
19
|
+
strictVersion: name === '@thanhphi/ui360',
|
|
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 };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ActivityIndicator, View } from 'react-native';
|
|
3
|
+
import { ApplicationProvider } from './common/mini-app/provider';
|
|
4
|
+
import type { MiniAppProps } from './common/mini-app/types';
|
|
5
|
+
import { MainStack } from './navigation/MainStack';
|
|
6
|
+
import {
|
|
7
|
+
createApplicationServices,
|
|
8
|
+
type ApplicationServices,
|
|
9
|
+
} from './services/application';
|
|
10
|
+
|
|
11
|
+
export type { MiniAppProps } from './common/mini-app/types';
|
|
12
|
+
|
|
13
|
+
/** Federation entry. Host authentication never falls back to standalone OIDC. */
|
|
14
|
+
export function MyApp(props: MiniAppProps) {
|
|
15
|
+
const { hostCapabilities } = props;
|
|
16
|
+
const generation = React.useRef(0);
|
|
17
|
+
const [instance, setInstance] = React.useState<{
|
|
18
|
+
services: ApplicationServices;
|
|
19
|
+
key: number;
|
|
20
|
+
} | null>(null);
|
|
21
|
+
|
|
22
|
+
React.useLayoutEffect(() => {
|
|
23
|
+
if (!hostCapabilities) return;
|
|
24
|
+
const services = createApplicationServices(hostCapabilities);
|
|
25
|
+
setInstance({ services, key: ++generation.current });
|
|
26
|
+
return () => {
|
|
27
|
+
services.dispose();
|
|
28
|
+
};
|
|
29
|
+
}, [hostCapabilities]);
|
|
30
|
+
|
|
31
|
+
// Fail at the integration boundary rather than treating an old host as standalone.
|
|
32
|
+
if (!hostCapabilities) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
'Mini app chưa được truyền hostCapabilities. Vui lòng kiểm tra cấu hình tích hợp.',
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!instance)
|
|
39
|
+
return (
|
|
40
|
+
<View style={{ flex: 1, justifyContent: 'center' }}>
|
|
41
|
+
<ActivityIndicator />
|
|
42
|
+
</View>
|
|
43
|
+
);
|
|
44
|
+
return (
|
|
45
|
+
<ApplicationProvider key={instance.key} services={instance.services}>
|
|
46
|
+
<MainStack />
|
|
47
|
+
</ApplicationProvider>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
export default MyApp;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import './themes/standalone';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ActivityIndicator, View } from 'react-native';
|
|
4
|
+
import { ApplicationProvider } from './common/mini-app/provider';
|
|
5
|
+
import { createStandaloneSession } from './modules/auth/services/standalone-session';
|
|
6
|
+
import { oidcOperations } from './modules/auth/services/oidc';
|
|
7
|
+
import { MainStack } from './navigation/MainStack';
|
|
8
|
+
import {
|
|
9
|
+
createApplicationServices,
|
|
10
|
+
type ApplicationServices,
|
|
11
|
+
} from './services/application';
|
|
12
|
+
|
|
13
|
+
export default function StandaloneApp() {
|
|
14
|
+
const [services, setServices] = React.useState<ApplicationServices | null>(
|
|
15
|
+
null,
|
|
16
|
+
);
|
|
17
|
+
const generation = React.useRef(0);
|
|
18
|
+
// Allocate in the effect so StrictMode's setup/cleanup/setup gets fresh resources.
|
|
19
|
+
React.useLayoutEffect(() => {
|
|
20
|
+
const session = createStandaloneSession(oidcOperations);
|
|
21
|
+
const instance = createApplicationServices(
|
|
22
|
+
session.capabilities,
|
|
23
|
+
session.actions,
|
|
24
|
+
);
|
|
25
|
+
generation.current += 1;
|
|
26
|
+
setServices(instance);
|
|
27
|
+
return () => {
|
|
28
|
+
instance.dispose();
|
|
29
|
+
session.dispose();
|
|
30
|
+
};
|
|
31
|
+
}, []);
|
|
32
|
+
if (!services)
|
|
33
|
+
return (
|
|
34
|
+
<View style={{ flex: 1, justifyContent: 'center' }}>
|
|
35
|
+
<ActivityIndicator />
|
|
36
|
+
</View>
|
|
37
|
+
);
|
|
38
|
+
return (
|
|
39
|
+
<ApplicationProvider key={generation.current} services={services}>
|
|
40
|
+
<MainStack />
|
|
41
|
+
</ApplicationProvider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import './themes/standalone';
|
|
2
|
+
import 'react-native-gesture-handler';
|
|
3
|
+
import { registerRootComponent } from 'expo';
|
|
4
|
+
import StandaloneApp from './StandaloneApp';
|
|
5
|
+
|
|
6
|
+
// Expo prebuild uses "main"; federation exposes src/App.tsx independently.
|
|
7
|
+
registerRootComponent(StandaloneApp);
|